diff --git a/src/Serein.Cli/Program.cs b/src/Serein.Cli/Program.cs index 44f3feab..e1b936d8 100644 --- a/src/Serein.Cli/Program.cs +++ b/src/Serein.Cli/Program.cs @@ -98,23 +98,28 @@ private static void BuildApp() var serverSwitcher = app.Services.GetRequiredService(); if (SereinAppBuilder.StartForTheFirstTime) + { ShowWelcomePage(logger); + } if (FileLoggerProvider.IsEnabled) + { ShowWarningOfLogMode(logger); - + } app.Start(); serverSwitcher.Initialize(); - updateChecker.Updated += (_, e) => + updateChecker.Updated += (_, _) => { if (updateChecker.Newest is not null) + { logger.LogInformation( "发现新版本:{}{}发布地址:{}", updateChecker.Newest.TagName, Environment.NewLine, updateChecker.Newest.Url ); + } }; app.WaitForShutdown(); diff --git a/src/Serein.Cli/Services/Interaction/CancelKeyHandlingService.cs b/src/Serein.Cli/Services/Interaction/CancelKeyHandlingService.cs index 6ca31bea..779f05ef 100644 --- a/src/Serein.Cli/Services/Interaction/CancelKeyHandlingService.cs +++ b/src/Serein.Cli/Services/Interaction/CancelKeyHandlingService.cs @@ -45,6 +45,8 @@ private void OnCancelKeyPress(object? sender, ConsoleCancelEventArgs e) _logger.LogError("当前还有以下{}个服务器未关闭", servers.Count()); foreach (var kv in servers) + { _logger.LogError("▫ {} (Id:{})", kv.Value.Configuration.Name, kv.Key); + } } } diff --git a/src/Serein.Cli/Services/Interaction/CommandPromptCallbacks.Items.cs b/src/Serein.Cli/Services/Interaction/CommandPromptCallbacks.Items.cs index dbe8672e..447cbeb7 100644 --- a/src/Serein.Cli/Services/Interaction/CommandPromptCallbacks.Items.cs +++ b/src/Serein.Cli/Services/Interaction/CommandPromptCallbacks.Items.cs @@ -36,7 +36,7 @@ private IEnumerable GetServerCompletionItem() { return _serverManager.Servers.Select( (kv) => - new CompletionItem(kv.Key, getExtendedDescription: (token) => GetDescription(kv)) + new CompletionItem(kv.Key, getExtendedDescription: (_) => GetDescription(kv)) ); static Task GetDescription(KeyValuePair kv) @@ -67,14 +67,18 @@ private IEnumerable GetPluginIdCompletionItem() var dictionary = new Dictionary(); foreach (var kv in _jsPluginLoader.Plugins) + { dictionary.TryAdd(kv.Key, kv.Value); + } foreach (var kv in _netPluginLoader.Plugins) + { dictionary.TryAdd(kv.Key, kv.Value); + } return dictionary.Select( (kv) => - new CompletionItem(kv.Key, getExtendedDescription: (token) => GetDescription(kv)) + new CompletionItem(kv.Key, getExtendedDescription: (_) => GetDescription(kv)) ); static Task GetDescription(KeyValuePair kv) @@ -106,7 +110,7 @@ private static IEnumerable LoadFrom() (attr) => new CompletionItem( attr.Command, - getExtendedDescription: (token) => + getExtendedDescription: (_) => Task.FromResult(new FormattedString(attr.Description)) ) ); diff --git a/src/Serein.Cli/Services/Interaction/CommandPromptCallbacks.cs b/src/Serein.Cli/Services/Interaction/CommandPromptCallbacks.cs index 10cf2310..02b1ede9 100644 --- a/src/Serein.Cli/Services/Interaction/CommandPromptCallbacks.cs +++ b/src/Serein.Cli/Services/Interaction/CommandPromptCallbacks.cs @@ -39,6 +39,7 @@ CancellationToken cancellationToken StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ); if (args.Length <= 1) // 根命令 + { return Task.FromResult>( [ .. _commandProvider @@ -48,6 +49,7 @@ .. _commandProvider .ThenBy((item) => item.ReplacementText[0]), ] ); + } if (args.Length > 0) switch (args[0]) @@ -113,11 +115,16 @@ .. GetServerCompletionItem() private static int CalculateRelevance(string word, string input) { for (int i = word.Length; i > 0; i--) + { if (input == word[..i]) + { return i; + } else if (input.Contains(word[..i])) + { return i - 10; - + } + } return int.MinValue; } } diff --git a/src/Serein.Cli/Services/Interaction/CommandProvider.cs b/src/Serein.Cli/Services/Interaction/CommandProvider.cs index 222ad289..57a4455f 100644 --- a/src/Serein.Cli/Services/Interaction/CommandProvider.cs +++ b/src/Serein.Cli/Services/Interaction/CommandProvider.cs @@ -48,12 +48,14 @@ public CommandProvider(IServiceProvider serviceProvider) var type = command.GetType(); var attribute = type.GetCustomAttribute(); if (attribute is null) + { continue; - + } dict[attribute.RootCommand] = command; if (attribute.RootCommand == "help") + { dict["?"] = command; - + } GenerateHelpPage(list, stringBuilder, type); } @@ -71,23 +73,28 @@ Type type var nameAttribute = type.GetCustomAttribute(); var descriptionAttribute = type.GetCustomAttribute(); if (nameAttribute is null || descriptionAttribute is null) + { return; + } stringBuilder.AppendLine($"■ {nameAttribute.RootCommand} {nameAttribute.Name}"); stringBuilder.AppendLine(" ▢ 描述"); foreach (var line in descriptionAttribute.Lines) + { stringBuilder.AppendLine($" ▫ {line}"); - + } var childrenAttributes = type.GetCustomAttributes(); if (childrenAttributes.Any()) { stringBuilder.AppendLine(" ▢ 用法"); foreach (var child in childrenAttributes) + { stringBuilder.AppendLine( - $" ▫ {nameAttribute.RootCommand} {child.Command} {child.Description}" - ); + $" ▫ {nameAttribute.RootCommand} {child.Command} {child.Description}" + ); + } } stringBuilder.AppendLine(); @@ -101,7 +108,7 @@ CommandDescriptionAttribute descriptionAttribute { return new( nameAttribute.RootCommand, - getExtendedDescription: (cancellationToken) => + getExtendedDescription: (_) => { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine(nameAttribute.Name); @@ -111,7 +118,9 @@ CommandDescriptionAttribute descriptionAttribute { stringBuilder.AppendLine("描述"); foreach (var line in descriptionAttribute.Lines) + { stringBuilder.AppendLine($"▫ {line}"); + } } return Task.FromResult( diff --git a/src/Serein.Cli/Services/Interaction/Handlers/ClearScreenHandler.cs b/src/Serein.Cli/Services/Interaction/Handlers/ClearScreenHandler.cs index fdc001ea..c11d3875 100644 --- a/src/Serein.Cli/Services/Interaction/Handlers/ClearScreenHandler.cs +++ b/src/Serein.Cli/Services/Interaction/Handlers/ClearScreenHandler.cs @@ -7,7 +7,7 @@ namespace Serein.Cli.Services.Interaction.Handlers; [CommandName("clear", "清屏")] [CommandDescription(["清除控制台所有输出"])] -public sealed class ClearScreenHandler() : CommandHandler +public sealed class ClearScreenHandler : CommandHandler { public override void Invoke(IReadOnlyList args) { diff --git a/src/Serein.Cli/Services/Interaction/Handlers/CommandHandler.cs b/src/Serein.Cli/Services/Interaction/Handlers/CommandHandler.cs index 84ba681f..f06b11d7 100644 --- a/src/Serein.Cli/Services/Interaction/Handlers/CommandHandler.cs +++ b/src/Serein.Cli/Services/Interaction/Handlers/CommandHandler.cs @@ -2,7 +2,7 @@ namespace Serein.Cli.Services.Interaction.Handlers; -public abstract class CommandHandler() +public abstract class CommandHandler { public abstract void Invoke(IReadOnlyList args); } diff --git a/src/Serein.Cli/Services/Interaction/Handlers/ConnectionHandler.cs b/src/Serein.Cli/Services/Interaction/Handlers/ConnectionHandler.cs index 20a2f59c..5fd744fa 100644 --- a/src/Serein.Cli/Services/Interaction/Handlers/ConnectionHandler.cs +++ b/src/Serein.Cli/Services/Interaction/Handlers/ConnectionHandler.cs @@ -24,7 +24,9 @@ WsConnectionManager wsConnectionManager public override void Invoke(IReadOnlyList args) { if (args.Count == 1) + { throw new InvalidArgumentException("缺少参数。可用值:\"info\"、\"open\"和\"close\""); + } switch (args[1].ToLowerInvariant()) { diff --git a/src/Serein.Cli/Services/Interaction/Handlers/ExitHandler.cs b/src/Serein.Cli/Services/Interaction/Handlers/ExitHandler.cs index aafc15fa..8f1e46c1 100644 --- a/src/Serein.Cli/Services/Interaction/Handlers/ExitHandler.cs +++ b/src/Serein.Cli/Services/Interaction/Handlers/ExitHandler.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging; using Serein.Cli.Models; -using Serein.Core.Models.Server; using Serein.Core.Services.Servers; namespace Serein.Cli.Services.Interaction.Handlers; @@ -30,7 +29,10 @@ public override void Invoke(IReadOnlyList args) var servers = _serverManager.Servers.Where((kv) => kv.Value.Status); _logger.LogError("当前还有以下{}个服务器未关闭", servers.Count()); + foreach (var kv in servers) + { _logger.LogError("- {} (Id={})", kv.Value.Configuration.Name, kv.Key); + } } } diff --git a/src/Serein.Cli/Services/Interaction/Handlers/PluginHandler.cs b/src/Serein.Cli/Services/Interaction/Handlers/PluginHandler.cs index bd5fb3ef..c03d2389 100644 --- a/src/Serein.Cli/Services/Interaction/Handlers/PluginHandler.cs +++ b/src/Serein.Cli/Services/Interaction/Handlers/PluginHandler.cs @@ -64,19 +64,33 @@ public override void Invoke(IReadOnlyList args) case "disable" when args.Count == 3: if (args.Count == 2) + { throw new InvalidArgumentException("缺少插件Id"); + } if (_jsPluginLoader.Plugins.TryGetValue(args[2], out var jsPlugin)) + { if (jsPlugin.IsEnabled) + { jsPlugin.Disable(); + } else + { throw new InvalidOperationException("插件已经被禁用"); + } + } if (_netPluginLoader.Plugins.TryGetValue(args[2], out var netPlugin)) + { if (netPlugin.IsEnabled) + { netPlugin.Disable(); + } else + { throw new InvalidOperationException("插件已经被禁用"); + } + } break; default: diff --git a/src/Serein.Cli/Services/Interaction/Handlers/ServerHandler.cs b/src/Serein.Cli/Services/Interaction/Handlers/ServerHandler.cs index 6b41ac8d..01e42b74 100644 --- a/src/Serein.Cli/Services/Interaction/Handlers/ServerHandler.cs +++ b/src/Serein.Cli/Services/Interaction/Handlers/ServerHandler.cs @@ -33,9 +33,11 @@ ServerSwitcher serverSwitcher public override void Invoke(IReadOnlyList args) { if (args.Count == 1) + { throw new InvalidArgumentException( "缺少参数。可用值:\"info\"、\"start\"、\"stop\"、\"terminate\"和\"switch\"" ); + } if (args[1].Equals("list", StringComparison.InvariantCultureIgnoreCase)) { @@ -62,18 +64,24 @@ public override void Invoke(IReadOnlyList args) args[1].Equals("switch", StringComparison.InvariantCultureIgnoreCase) && args.Count == 2 ) + { throw new InvalidArgumentException("缺少服务器Id。"); + } var id = args.Count == 3 ? args[2] : _serverSwitcher.CurrentId; if (string.IsNullOrEmpty(id)) + { throw new InvalidArgumentException( "缺少服务器Id。" + "你可以在命令末尾添加服务器Id或使用\"server switch \"选择你要控制的服务器" ); + } if (!_serverManager.Servers.TryGetValue(id, out Server? server)) + { throw new InvalidArgumentException("指定的服务器不存在"); + } switch (args[1].ToLowerInvariant()) { @@ -119,7 +127,9 @@ public override void Invoke(IReadOnlyList args) server.Start(); if (string.IsNullOrEmpty(_settingProvider.Value.Application.CliCommandHeader)) + { _settingProvider.Value.Application.CliCommandHeader = "//"; + } _logger.LogWarning( "服务器已启动,输入的命令将转发至服务器。若要执行Serein的命令,你需要在命令前加上\"{}\"", diff --git a/src/Serein.Cli/Services/Interaction/Handlers/VersionHandler.cs b/src/Serein.Cli/Services/Interaction/Handlers/VersionHandler.cs index 1fa3b8df..56c3fe74 100644 --- a/src/Serein.Cli/Services/Interaction/Handlers/VersionHandler.cs +++ b/src/Serein.Cli/Services/Interaction/Handlers/VersionHandler.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serein.Cli.Models; diff --git a/src/Serein.Cli/Services/Interaction/InputHandler.cs b/src/Serein.Cli/Services/Interaction/InputHandler.cs index 70bb5e1b..1e3c32bc 100644 --- a/src/Serein.Cli/Services/Interaction/InputHandler.cs +++ b/src/Serein.Cli/Services/Interaction/InputHandler.cs @@ -21,8 +21,11 @@ public void Handle(IReadOnlyList args) } if (!_commandProvider.Handlers.TryGetValue(args[0].ToLowerInvariant(), out var parser)) + { _logger.LogError("未知命令。请使用\"help\"查看所有命令"); + } else + { try { parser.Invoke(args); @@ -35,5 +38,6 @@ public void Handle(IReadOnlyList args) { _logger.LogError(e, "运行命令时出现异常"); } + } } } diff --git a/src/Serein.Cli/Services/Interaction/InputLoopService.cs b/src/Serein.Cli/Services/Interaction/InputLoopService.cs index 07ac9bb4..b6f611f1 100644 --- a/src/Serein.Cli/Services/Interaction/InputLoopService.cs +++ b/src/Serein.Cli/Services/Interaction/InputLoopService.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,7 +9,6 @@ using PrettyPrompt; using PrettyPrompt.Highlighting; -using Serein.Core.Models.Server; using Serein.Core.Services.Data; using Serein.Core.Services.Servers; using Serein.Core.Utils.Extensions; @@ -46,15 +44,13 @@ public Task StartAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken) { _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); return Task.CompletedTask; } private void Loop(CancellationToken cancellationToken) { - if ( - !Console.IsInputRedirected - && CliConsole.IsColorful - ) + if (!Console.IsInputRedirected && CliConsole.IsColorful) { var flag = false; var prompt = new Prompt( @@ -104,7 +100,9 @@ out var server _logger.LogWarning("若要体验此功能,请在终端内运行 Serein.Cli"); while (!cancellationToken.IsCancellationRequested) + { ProcessInput(Console.ReadLine()); + } } } @@ -113,20 +111,25 @@ out var server private void ProcessInput(string? input) { if (input is null) + { return; - + } if (!string.IsNullOrEmpty(_serverSwitcher.Value.CurrentId)) + { if ( _serverManager.Servers.TryGetValue(_serverSwitcher.Value.CurrentId, out var server) && server.Status ) if (input.StartsWith(_settingProvider.Value.Application.CliCommandHeader)) + { input = input[_settingProvider.Value.Application.CliCommandHeader.Length..]; + } else { server.Input(input); return; } + } _inputHandler.Handle( input.Split( diff --git a/src/Serein.Cli/Services/Interaction/ServerSwitcher.cs b/src/Serein.Cli/Services/Interaction/ServerSwitcher.cs index a9764c60..4732ef7f 100644 --- a/src/Serein.Cli/Services/Interaction/ServerSwitcher.cs +++ b/src/Serein.Cli/Services/Interaction/ServerSwitcher.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging; -using Serein.Core.Models.Server; using Serein.Core.Services.Data; using Serein.Core.Services.Servers; @@ -35,8 +34,9 @@ public void SwitchTo(string id) } if (!_serverManager.Servers.TryGetValue(id, out var server)) + { throw new InvalidOperationException("选择的服务器不存在"); - + } if ( !string.IsNullOrEmpty(CurrentId) && _serverManager.Servers.TryGetValue(CurrentId, out var oldServer) @@ -50,7 +50,9 @@ public void SwitchTo(string id) if (server.Status) { if (string.IsNullOrEmpty(_settingProvider.Value.Application.CliCommandHeader)) + { _settingProvider.Value.Application.CliCommandHeader = "//"; + } _logger.LogWarning( "此服务器正在运行中,输入的命令将转发至服务器。若要执行Serein的命令,你需要在命令前加上\"{}\"", _settingProvider.Value.Application.CliCommandHeader @@ -68,7 +70,7 @@ public void Initialize() "添加更多服务器配置后,你可以用\"server switch \"选择要进行操作的服务器" ); - SwitchTo( _serverManager.Servers.First().Key); + SwitchTo(_serverManager.Servers.First().Key); } else if (_serverManager.Servers.Count > 1) { @@ -81,8 +83,9 @@ public void Initialize() private void LogToConsole(object? sender, ServerOutputEventArgs e) { if (sender is not Server server) + { return; - + } switch (e.OutputType) { case ServerOutputType.Raw: diff --git a/src/Serein.Cli/Services/Loggers/CliLogger.cs b/src/Serein.Cli/Services/Loggers/CliLogger.cs index dc43c32e..ca4d5fee 100644 --- a/src/Serein.Cli/Services/Loggers/CliLogger.cs +++ b/src/Serein.Cli/Services/Loggers/CliLogger.cs @@ -37,12 +37,16 @@ public void Log( ) { if (!IsEnabled(logLevel)) + { return; + } var text = state?.ToString() ?? string.Empty; if (exception != null) + { text += Environment.NewLine + (EnableDebug ? exception.ToString() : exception.GetDetailString()); + } CliConsole.WriteLine(logLevel, $"[{_name}] {text}"); } diff --git a/src/Serein.Cli/Services/Loggers/CliLoggerProvider.cs b/src/Serein.Cli/Services/Loggers/CliLoggerProvider.cs index 4be4baf6..f6099e78 100644 --- a/src/Serein.Cli/Services/Loggers/CliLoggerProvider.cs +++ b/src/Serein.Cli/Services/Loggers/CliLoggerProvider.cs @@ -13,8 +13,9 @@ public ILogger CreateLogger(string categoryName) lock (_loggers) { if (!_loggers.TryGetValue(categoryName, out var logger)) + { logger = _loggers[categoryName] = new(categoryName); - + } return logger; } } diff --git a/src/Serein.Cli/Services/TitleUpdater.cs b/src/Serein.Cli/Services/TitleUpdater.cs index 3088effc..65dfcf3f 100644 --- a/src/Serein.Cli/Services/TitleUpdater.cs +++ b/src/Serein.Cli/Services/TitleUpdater.cs @@ -34,8 +34,9 @@ public Task StopAsync(CancellationToken cancellationToken) private void Update() { if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { return; - + } var text = _commandParser.ApplyVariables( _settingProvider.Value.Application.CustomTitle, null diff --git a/src/Serein.Cli/Utils/CliConsole.cs b/src/Serein.Cli/Utils/CliConsole.cs index 7b8efd37..66ce1171 100644 --- a/src/Serein.Cli/Utils/CliConsole.cs +++ b/src/Serein.Cli/Utils/CliConsole.cs @@ -21,41 +21,57 @@ public static void WriteLine(LogLevel logLevel, string line) case LogLevel.Debug: if (IsColorful) + { Console.WriteLine( $"\x1b[0m{DateTime.Now:T} \x1b[38;2;95;95;135mDebug\x1b[0m {line}" ); + } else Console.WriteLine($"{DateTime.Now:T} Debug {line}"); break; case LogLevel.Information: if (IsColorful) + { Console.WriteLine( $"\x1b[0m{DateTime.Now:T} \x1b[38;2;95;175;175mInfo\x1b[0m {line}" ); + } else Console.WriteLine($"{DateTime.Now:T} Info {line}"); break; case LogLevel.Warning: if (IsColorful) + { Console.WriteLine($"\x1b[0m{DateTime.Now:T} \x1b[1;93mWarn {line}\x1b[0m"); + } else + { Console.WriteLine($"{DateTime.Now:T} Warn {line}"); + } break; case LogLevel.Error: if (IsColorful) + { Console.WriteLine($"\x1b[0m{DateTime.Now:T} \x1b[1;91mError {line}\x1b[0m"); + } else + { Console.WriteLine($"{DateTime.Now:T} Error {line}"); + } break; case LogLevel.Critical: if (IsColorful) + { Console.WriteLine($"\x1b[0m{DateTime.Now:T} \x1b[1;31mCritical {line}\x1b[0m"); + } else + { Console.WriteLine($"{DateTime.Now:T} Critical {line}"); + } break; case LogLevel.None: break; diff --git a/src/Serein.Cli/Utils/CommandLineParserBuilder.cs b/src/Serein.Cli/Utils/CommandLineParserBuilder.cs index c36ee93b..99471655 100644 --- a/src/Serein.Cli/Utils/CommandLineParserBuilder.cs +++ b/src/Serein.Cli/Utils/CommandLineParserBuilder.cs @@ -62,9 +62,13 @@ private static void OnException(Exception e, InvocationContext? context) Console.ResetColor(); if (!Console.IsInputRedirected) + { Console.ReadLine(); + } if (e.HResult != 0 && context is not null) + { context.ExitCode = e.HResult; + } } } \ No newline at end of file diff --git a/src/Serein.Core/Models/Commands/Match.cs b/src/Serein.Core/Models/Commands/Match.cs index ea53798f..e4f07acf 100644 --- a/src/Serein.Core/Models/Commands/Match.cs +++ b/src/Serein.Core/Models/Commands/Match.cs @@ -23,8 +23,9 @@ public string RegExp try { if (string.IsNullOrEmpty(_regExp)) + { throw new ArgumentException("正则表达式不得为空", nameof(RegExp)); - + } RegexObj = new Regex(_regExp); } catch diff --git a/src/Serein.Core/Models/Commands/Schedule.cs b/src/Serein.Core/Models/Commands/Schedule.cs index f2072fcd..45c5b5ff 100644 --- a/src/Serein.Core/Models/Commands/Schedule.cs +++ b/src/Serein.Core/Models/Commands/Schedule.cs @@ -24,8 +24,9 @@ public string? Expression try { if (string.IsNullOrEmpty(_expression)) + { throw new ArgumentException("Cron表达式不得为空", nameof(Expression)); - + } Cron = CrontabSchedule.Parse(_expression); NextTime = Cron?.GetNextOccurrence(DateTime.Now); } diff --git a/src/Serein.Core/Models/Plugins/Js/JsPlugin.cs b/src/Serein.Core/Models/Plugins/Js/JsPlugin.cs index c2ee572b..126f4415 100644 --- a/src/Serein.Core/Models/Plugins/Js/JsPlugin.cs +++ b/src/Serein.Core/Models/Plugins/Js/JsPlugin.cs @@ -65,8 +65,10 @@ JsPluginConfig config public void Dispose() { Disable(); + _eventHandlers.Clear(); Engine.Dispose(); + _cancellationTokenSource.Dispose(); GC.SuppressFinalize(this); } @@ -80,18 +82,23 @@ public bool Invoke(Event @event, CancellationToken cancellationToken, params obj || !EventHandlers.TryGetValue(@event, out Function? func) || func is null ) + { return false; - + } if (!Monitor.TryEnter(Engine, 1000)) + { throw new TimeoutException("等待引擎超时"); - + } entered = true; if (cancellationToken.IsCancellationRequested) + { return false; - + } var result = Engine.Invoke(func, args); - return result.IsBoolean() && result.AsBoolean(); + { + return result.IsBoolean() && result.AsBoolean(); + } } catch (Exception e) { @@ -105,15 +112,18 @@ public bool Invoke(Event @event, CancellationToken cancellationToken, params obj finally { if (entered) + { Monitor.Exit(Engine); + } } } public void Disable() { if (!_cancellationTokenSource.IsCancellationRequested) + { _cancellationTokenSource.Cancel(); - + } PropertyChanged?.Invoke(this, new(nameof(IsEnabled))); } } diff --git a/src/Serein.Core/Services/Bindings/BindingManager.cs b/src/Serein.Core/Services/Bindings/BindingManager.cs index 3fc1c993..acd71b3b 100644 --- a/src/Serein.Core/Services/Bindings/BindingManager.cs +++ b/src/Serein.Core/Services/Bindings/BindingManager.cs @@ -34,7 +34,9 @@ public void ValidateGameId(string gameId) var regex = new Regex(_settingProvider.Value.Application.RegexForCheckingGameId); if (!regex.IsMatch(gameId)) + { throw new BindingFailureException("游戏名称格式不正确"); + } } public List Records => [.. BindingRecordDbContext.Records]; @@ -55,7 +57,9 @@ private bool TryGetValue( ) { lock (_lock) + { bindingRecord = context.Records.FirstOrDefault((v) => v.UserId == id); + } return bindingRecord is not null; } @@ -73,8 +77,12 @@ private void CheckConflict(BindingRecordDbContext context, long id, string gameI lock (_lock) { foreach (var record in context.Records) + { if (record.UserId != id && record.GameIds.Contains(gameId)) + { throw new BindingFailureException("此Id已被占用"); + } + } } } @@ -101,13 +109,18 @@ public void Add(long id, string gameId, string? shownName = null) else { if (!string.IsNullOrEmpty(shownName)) + { record.ShownName = shownName; + } if (record.GameIds.Contains(gameId)) + { throw new BindingFailureException("已经绑定过此Id了"); + } else + { record.GameIds.Add(gameId); - + } record.Update(); } @@ -121,8 +134,9 @@ public void Remove(long id, string gameId) { using var context = BindingRecordDbContext; if (!TryGetValue(context, id, out var record) || !record.GameIds.Remove(gameId)) + { throw new BindingFailureException("未绑定此Id"); - + } record.Update(); context.SaveChanges(); diff --git a/src/Serein.Core/Services/Commands/CommandRunner.cs b/src/Serein.Core/Services/Commands/CommandRunner.cs index 33857dc3..09dcefc2 100644 --- a/src/Serein.Core/Services/Commands/CommandRunner.cs +++ b/src/Serein.Core/Services/Commands/CommandRunner.cs @@ -52,8 +52,9 @@ BindingManager bindingManager public async Task RunAsync(Command command, CommandContext? commandContext = null) { if (command.Type == CommandType.Invalid) + { return; - + } _logger.LogDebug( "运行命令:command.Body='{}'; command.Argument='{}'", command.Body, @@ -72,39 +73,58 @@ public async Task RunAsync(Command command, CommandContext? commandContext = nul case CommandType.InputServer: Server? server = null; if (!string.IsNullOrEmpty(argumentStr)) + { _serverManager.Value.Servers.TryGetValue(argumentStr, out server); + } else if (!string.IsNullOrEmpty(commandContext?.ServerId)) + { _serverManager.Value.Servers.TryGetValue(commandContext.ServerId, out server); + } else if (_serverManager.Value.Servers.Count == 1) + { server = _serverManager.Value.Servers.Values.First(); - + } server?.InputFromCommand(body, null); break; case CommandType.SendGroupMsg: if (!string.IsNullOrEmpty(argumentStr)) + { await _wsConnectionManager.Value.SendGroupMsgAsync(argumentStr, body); + } else if (command.Argument is long groupId1) + { await _wsConnectionManager.Value.SendGroupMsgAsync(groupId1, body); + } else if (commandContext?.MessagePacket?.GroupId is long groupId2) + { await _wsConnectionManager.Value.SendGroupMsgAsync(groupId2, body); + } else if ( command.Origin != CommandOrigin.Msg && _settingProvider.Value.Connection.Groups.Length > 0 ) + { await _wsConnectionManager.Value.SendGroupMsgAsync( _settingProvider.Value.Connection.Groups[0], body ); + } break; case CommandType.SendPrivateMsg: if (!string.IsNullOrEmpty(argumentStr)) + { await _wsConnectionManager.Value.SendPrivateMsgAsync(argumentStr, body); + } else if (command.Argument is long userId1) + { await _wsConnectionManager.Value.SendGroupMsgAsync(userId1, body); + } else if (commandContext?.MessagePacket?.UserId is long userId2) + { await _wsConnectionManager.Value.SendPrivateMsgAsync(userId2, body); + } break; case CommandType.SendData: @@ -114,11 +134,13 @@ await _wsConnectionManager.Value.SendGroupMsgAsync( case CommandType.Bind: case CommandType.Unbind: if (commandContext?.MessagePacket?.UserId is not long userId3) + { break; - + } try { if (command.Type == CommandType.Bind) + { _bindingManager.Add( userId3, body, @@ -126,8 +148,11 @@ await _wsConnectionManager.Value.SendGroupMsgAsync( ? commandContext.MessagePacket.Sender.Nickname : commandContext.MessagePacket.Sender.Card ); + } else + { _bindingManager.Remove(userId3, body); + } } catch (BindingFailureException e) { @@ -141,21 +166,25 @@ await _wsConnectionManager.Value.SendGroupMsgAsync( command.Argument is not string id || !_jsPluginLoader.JsPlugins.TryGetValue(id, out var jsPlugin) ) + { break; - + } var entered = false; try { if (!Monitor.TryEnter(jsPlugin.Engine, 1000)) + { throw new TimeoutException("等待引擎超时"); - + } entered = true; jsPlugin.Engine.Execute(body); } finally { if (entered) + { Monitor.Exit(jsPlugin.Engine); + } } break; @@ -172,12 +201,16 @@ command.Argument is not string id private async Task FastReply(MessagePacket messagePacket, string msg) { if (messagePacket.MessageType == MessageType.Group && messagePacket.GroupId is long groupId) + { await _wsConnectionManager.Value.SendGroupMsgAsync(groupId, msg); + } else if ( messagePacket.MessageType == MessageType.Private && messagePacket.UserId is long userId ) + { await _wsConnectionManager.Value.SendPrivateMsgAsync(userId, msg); + } } private async Task ExecuteShellCommand(string line) @@ -212,8 +245,9 @@ private async Task ExecuteShellCommand(string line) await process.WaitForExitAsync().WaitAsync(TimeSpan.FromMinutes(1)); if (!process.HasExited) + { process.Kill(true); - + } process.Dispose(); } } diff --git a/src/Serein.Core/Services/Commands/HardwareInfoProvider.cs b/src/Serein.Core/Services/Commands/HardwareInfoProvider.cs index 4cdc3265..463736ce 100644 --- a/src/Serein.Core/Services/Commands/HardwareInfoProvider.cs +++ b/src/Serein.Core/Services/Commands/HardwareInfoProvider.cs @@ -29,7 +29,9 @@ public HardwareInfoProvider(ILogger logger) public void Update() { if (_isLoading) + { return; + } lock (_lock) try @@ -37,9 +39,13 @@ public void Update() _isLoading = true; if (Info is null) + { Info = new(); + } else + { Info.RefreshAll(); + } } catch (Exception e) { diff --git a/src/Serein.Core/Services/Commands/Matcher.cs b/src/Serein.Core/Services/Commands/Matcher.cs index 91b576b4..ec927c7f 100644 --- a/src/Serein.Core/Services/Commands/Matcher.cs +++ b/src/Serein.Core/Services/Commands/Matcher.cs @@ -14,7 +14,7 @@ public sealed class Matcher( MatchesProvider matchesProvider, CommandRunner commandRunner, SettingProvider settingProvider - ) +) { private readonly MatchesProvider _matchesProvider = matchesProvider; private readonly CommandRunner _commandRunner = commandRunner; @@ -37,12 +37,16 @@ public async Task MatchServerOutputAsync(string id, string line) || match.CommandObj.Type == CommandType.Invalid || CheckExclusions(match, id) ) + { continue; + } var matches = match.RegexObj.Match(line); if (matches.Success) + { tasks.Add(_commandRunner.RunAsync(match.CommandObj, new(matches))); + } } } @@ -66,12 +70,16 @@ public async Task MatchServerInputAsync(string id, string line) || match.CommandObj.Type == CommandType.Invalid || CheckExclusions(match, id) ) + { continue; + } var matches = match.RegexObj.Match(line); if (matches.Success) + { tasks.Add(_commandRunner.RunAsync(match.CommandObj, new(matches))); + } } } @@ -103,17 +111,22 @@ public async Task MatchMsgAsync(MessagePacket messagePacket) || match.FieldType == MatchFieldType.GroupMsg && messagePacket.MessageType == MessageType.Group || match.FieldType == MatchFieldType.PrivateMsg - && messagePacket.MessageType == MessageType.Private) + && messagePacket.MessageType == MessageType.Private + ) || CheckExclusions(match, messagePacket) ) + { continue; + } var matches = match.RegexObj.Match(messagePacket.RawMessage); if (matches.Success) + { tasks.Add( _commandRunner.RunAsync(match.CommandObj, new(matches, messagePacket)) ); + } } } @@ -125,11 +138,10 @@ private static bool CheckExclusions(Match match, string serverId) return match.MatchExclusion.Servers.Contains(serverId); } - private static bool CheckExclusions(Match match, MessagePacket messagePacket) { return match.FieldType == MatchFieldType.GroupMsg - && match.MatchExclusion.Groups.Contains(messagePacket.GroupId) + && match.MatchExclusion.Groups.Contains(messagePacket.GroupId) || match.MatchExclusion.Users.Contains(messagePacket.UserId); } diff --git a/src/Serein.Core/Services/Commands/ReactionTrigger.cs b/src/Serein.Core/Services/Commands/ReactionTrigger.cs index b8600a41..b7553bf8 100644 --- a/src/Serein.Core/Services/Commands/ReactionTrigger.cs +++ b/src/Serein.Core/Services/Commands/ReactionTrigger.cs @@ -42,10 +42,13 @@ internal void Trigger( IEnumerable commands; lock (values) + { commands = values.Select((cmd) => CommandParser.Parse(CommandOrigin.Reaction, cmd)); - + } if (!commands.Any()) + { return; + } var context = new CommandContext { Variables = variables, ServerId = target?.ServerId }; @@ -53,13 +56,19 @@ internal void Trigger( foreach (var command in commands) { if (command.Argument is null && target is not null) + { if ( command.Type == CommandType.InputServer && !string.IsNullOrEmpty(target.ServerId) ) + { command.Argument = target.ServerId; + } else if (command.Type == CommandType.SendPrivateMsg && target.UserId.HasValue) + { command.Argument = target.UserId.ToString() ?? string.Empty; + } + } tasks.Add(_commandRunner.RunAsync(command, context)); } diff --git a/src/Serein.Core/Services/Commands/ScheduleRunner.cs b/src/Serein.Core/Services/Commands/ScheduleRunner.cs index 79621bc2..9753f918 100644 --- a/src/Serein.Core/Services/Commands/ScheduleRunner.cs +++ b/src/Serein.Core/Services/Commands/ScheduleRunner.cs @@ -30,7 +30,9 @@ private void OnElapsed(object? sender, EventArgs e) || schedule.CommandObj.Type == CommandType.Invalid || schedule.Cron is null ) + { continue; + } if (schedule.NextTime < DateTime.Now) { diff --git a/src/Serein.Core/Services/CoreService.cs b/src/Serein.Core/Services/CoreService.cs index dae9e74e..e9ab5a16 100644 --- a/src/Serein.Core/Services/CoreService.cs +++ b/src/Serein.Core/Services/CoreService.cs @@ -58,11 +58,17 @@ public Task StartAsync(CancellationToken cancellationToken) _updateChecker.StartAsync(); foreach (var (_, server) in _serverManager.Servers) + { if (server.Configuration.StartWhenSettingUp) + { Try(server.Start, $"服务器{server.Configuration.Name}({server.Id})"); + } + } if (_settingProvider.Value.WebApi.IsEnabled) + { Try(_httpServer.Start, "WebApi"); + } _logger.LogInformation("Serein启动成功!"); diff --git a/src/Serein.Core/Services/Data/DataProviderBase.cs b/src/Serein.Core/Services/Data/DataProviderBase.cs index 7c607c21..526531f0 100644 --- a/src/Serein.Core/Services/Data/DataProviderBase.cs +++ b/src/Serein.Core/Services/Data/DataProviderBase.cs @@ -20,7 +20,8 @@ public async Task SaveAsyncWithDebounce() await Task.Delay(1000); if ((DateTime.Now - _last).TotalMilliseconds > 900) - Save(); + { Save(); + } } public abstract T Value { get; } diff --git a/src/Serein.Core/Services/Data/LogWriter.cs b/src/Serein.Core/Services/Data/LogWriter.cs index 2b3fcbef..732dc6cb 100644 --- a/src/Serein.Core/Services/Data/LogWriter.cs +++ b/src/Serein.Core/Services/Data/LogWriter.cs @@ -24,16 +24,21 @@ public async Task WriteAsync(string line) await Task.Delay(500); if ((DateTime.Now - _last).TotalMilliseconds > 450) + { Flush(); + } } public void Flush() { lock (_lock) + { lock (_buffer) { if (!Directory.Exists(_directory)) + { Directory.CreateDirectory(_directory); + } var path = Path.Combine(_directory, $"{DateTime.Now:yyyy-MM-dd}.log"); @@ -47,5 +52,6 @@ public void Flush() _logger.LogDebug(e, "写入“{}”失败", path); } } + } } } \ No newline at end of file diff --git a/src/Serein.Core/Services/Data/MatchesProvider.cs b/src/Serein.Core/Services/Data/MatchesProvider.cs index 4b813dd0..11d1d927 100644 --- a/src/Serein.Core/Services/Data/MatchesProvider.cs +++ b/src/Serein.Core/Services/Data/MatchesProvider.cs @@ -33,20 +33,25 @@ public override ObservableCollection Read() ); if (wrapper?.Type == typeof(ObservableCollection).ToString()) + { lock (Value) { Value.Clear(); if (wrapper.Data is not null) + { foreach (var match in wrapper.Data) { Value.Add(match); } + } } + } } else + { Save(); - + } return Value; } catch (Exception e) diff --git a/src/Serein.Core/Services/Data/PermissonGroupProvider.cs b/src/Serein.Core/Services/Data/PermissonGroupProvider.cs index e96937b9..d2be5b82 100644 --- a/src/Serein.Core/Services/Data/PermissonGroupProvider.cs +++ b/src/Serein.Core/Services/Data/PermissonGroupProvider.cs @@ -49,19 +49,25 @@ public override Dictionary Read() ); if (wrapper?.Type == typeof(Dictionary).ToString()) + { lock (Value) { Value.Clear(); if (wrapper.Data is not null) + { foreach (var kv in wrapper.Data) { Value.Add(kv.Key, kv.Value); } + } } + } } else + { Save(); + } return Value; } diff --git a/src/Serein.Core/Services/Data/ScheduleProvider.cs b/src/Serein.Core/Services/Data/ScheduleProvider.cs index f7a14f15..6d4e3993 100644 --- a/src/Serein.Core/Services/Data/ScheduleProvider.cs +++ b/src/Serein.Core/Services/Data/ScheduleProvider.cs @@ -33,20 +33,25 @@ public override ObservableCollection Read() ); if (wrapper?.Type == typeof(ObservableCollection).ToString()) + { lock (Value) { Value.Clear(); if (wrapper.Data is not null) + { foreach (var match in wrapper.Data) { Value.Add(match); } + } } + } } else + { Save(); - + } return Value; } catch (Exception e) diff --git a/src/Serein.Core/Services/Data/SettingProvider.cs b/src/Serein.Core/Services/Data/SettingProvider.cs index d5ab1d06..5cd550f8 100644 --- a/src/Serein.Core/Services/Data/SettingProvider.cs +++ b/src/Serein.Core/Services/Data/SettingProvider.cs @@ -31,7 +31,9 @@ public override Setting Read() ); if (wrapper?.Type == typeof(Setting).ToString()) + { return wrapper.Data ?? new(); + } } return new(); diff --git a/src/Serein.Core/Services/Loggers/FileLoggerProvider.cs b/src/Serein.Core/Services/Loggers/FileLoggerProvider.cs index f12c160c..46f9a7f7 100644 --- a/src/Serein.Core/Services/Loggers/FileLoggerProvider.cs +++ b/src/Serein.Core/Services/Loggers/FileLoggerProvider.cs @@ -40,8 +40,9 @@ public ILogger CreateLogger(string categoryName) lock (_loggers) { if (!_loggers.TryGetValue(categoryName, out var logger)) + { logger = _loggers[categoryName] = new FileLogger(categoryName, _buffer); - + } return logger; } } @@ -63,8 +64,9 @@ private static string GetFileName() ); if (!File.Exists(path)) + { return path; - + } id++; } } @@ -72,6 +74,7 @@ private static string GetFileName() private void Flush() { if (_buffer.Count > 0) + { lock (_buffer) { Directory.CreateDirectory(Path.Combine(PathConstants.LogDirectory, "app")); @@ -81,5 +84,6 @@ private void Flush() ); _buffer.Clear(); } + } } } diff --git a/src/Serein.Core/Services/Network/Connection/PacketHandler.cs b/src/Serein.Core/Services/Network/Connection/PacketHandler.cs index a8570994..139cd6ec 100644 --- a/src/Serein.Core/Services/Network/Connection/PacketHandler.cs +++ b/src/Serein.Core/Services/Network/Connection/PacketHandler.cs @@ -36,8 +36,9 @@ ReactionTrigger reactionTrigger public void Handle(JsonNode node) { if (!_eventDispatcher.Dispatch(Event.PacketReceived, node)) + { return; - + } switch (node["post_type"]?.ToString()) { case "message": @@ -58,8 +59,9 @@ public void Handle(JsonNode node) private void HandleNoticePacket(NoticePacket? packet) { if (packet is null || !_settingProvider.Value.Connection.Groups.Contains(packet.GroupId)) + { return; - + } switch (packet.NoticeType) { case "group_decrease": @@ -87,17 +89,21 @@ private void HandleNoticePacket(NoticePacket? packet) private void HandleMessagePacket(MessagePacket? packet) { if (packet is null) + { return; + } _connectionLogger.Value.LogReceivedMessage( - $"[{(packet.MessageType == MessageType.Group ? $"群聊({packet.GroupId})" : "私聊")}] {packet.Sender.Nickname}({packet.UserId}): {packet.RawMessage} (id={packet.MessageId})" - ); + $"[{(packet.MessageType == MessageType.Group ? $"群聊({packet.GroupId})" : "私聊")}] {packet.Sender.Nickname}({packet.UserId}): {packet.RawMessage} (id={packet.MessageId})" + ); if ( packet.MessageType == MessageType.Group && !_settingProvider.Value.Connection.Groups.Contains(packet.GroupId) ) + { return; + } if ( packet.MessageType == MessageType.Group @@ -105,7 +111,9 @@ private void HandleMessagePacket(MessagePacket? packet) || packet.MessageType == MessageType.Private && !_eventDispatcher.Dispatch(Event.PrivateMessageReceived, packet) ) + { return; + } _matcher.MatchMsgAsync(packet); } diff --git a/src/Serein.Core/Services/Network/Connection/ReverseWebSocketService.cs b/src/Serein.Core/Services/Network/Connection/ReverseWebSocketService.cs index 7c1981fc..c46e3008 100644 --- a/src/Serein.Core/Services/Network/Connection/ReverseWebSocketService.cs +++ b/src/Serein.Core/Services/Network/Connection/ReverseWebSocketService.cs @@ -28,16 +28,19 @@ public sealed class ReverseWebSocketService : IConnectionService public ReverseWebSocketService(IHost host, SettingProvider settingProvider) { _host = host; + _settingProvider = settingProvider; + _logger = new(_host.Services.GetRequiredService); + FleckLog.LogAction = (level, msg, _) => { if (level == LogLevel.Error) - Logger.Log(MsLogLevel.Error, msg); + { + _logger.Value.Log(MsLogLevel.Error, msg); + } }; - _settingProvider = settingProvider; } - private IServiceProvider Services => _host.Services; - private IConnectionLogger Logger => Services.GetRequiredService(); + private readonly Lazy _logger; public event EventHandler? MessageReceived; public event EventHandler? StatusChanged; @@ -59,14 +62,20 @@ private void ConfigServer(IWebSocketConnection webSocket) { webSocket.OnOpen += () => _webSockets.Add(GetEndPoint(), webSocket); webSocket.OnOpen += () => - Logger.Log(MsLogLevel.Information, $"[{GetEndPoint()}] 连接到反向WebSocket服务器"); + _logger.Value.Log( + MsLogLevel.Information, + $"[{GetEndPoint()}] 连接到反向WebSocket服务器" + ); webSocket.OnClose += () => _webSockets.Remove(GetEndPoint()); webSocket.OnClose += () => - Logger.Log(MsLogLevel.Information, $"[{GetEndPoint()}] 从反向WebSocket服务器断开"); + _logger.Value.Log( + MsLogLevel.Information, + $"[{GetEndPoint()}] 从反向WebSocket服务器断开" + ); webSocket.OnError += (e) => - Logger.Log( + _logger.Value.Log( MsLogLevel.Error, $"[{GetEndPoint()}] 发生错误:{Environment.NewLine}" + e.GetDetailString() ); @@ -91,11 +100,14 @@ public async Task SendAsync(string text) List tasks; lock (_webSockets) - tasks = new( - _webSockets.Values.Select( + { + tasks = + [ + .. _webSockets.Values.Select( (client) => client.IsAvailable ? client.Send(text) : Task.CompletedTask - ) - ); + ), + ]; + } await Task.WhenAll(tasks); } @@ -103,11 +115,12 @@ public async Task SendAsync(string text) public void Start() { if (Active) + { throw new InvalidOperationException(); - + } _server = CreateNew(); _server.Start(ConfigServer); - Logger.Log( + _logger.Value.Log( MsLogLevel.Information, $"反向WebSocket服务器已在{_settingProvider.Value.Connection.Uri}开启" ); @@ -120,7 +133,7 @@ public void Stop() { _server.Dispose(); StatusChanged?.Invoke(null, EventArgs.Empty); - Logger.Log(MsLogLevel.Information, "反向WebSocket服务器已停止"); + _logger.Value.Log(MsLogLevel.Information, "反向WebSocket服务器已停止"); _server = null; _webSockets.Clear(); } diff --git a/src/Serein.Core/Services/Network/Connection/WebSocketService.cs b/src/Serein.Core/Services/Network/Connection/WebSocketService.cs index b03d62b5..9617f8aa 100644 --- a/src/Serein.Core/Services/Network/Connection/WebSocketService.cs +++ b/src/Serein.Core/Services/Network/Connection/WebSocketService.cs @@ -43,7 +43,9 @@ private WebSocket CreateNew() var headers = new Dictionary(_settingProvider.Value.Connection.Headers); if (!string.IsNullOrEmpty(_settingProvider.Value.Connection.AccessToken)) + { headers["Authorization"] = $"Bearer {_settingProvider.Value.Connection.AccessToken}"; + } var client = new WebSocket( _settingProvider.Value.Connection.Uri, @@ -85,16 +87,18 @@ public void Dispose() public Task SendAsync(string text) { if (_client is not null && _client.State == WebSocketState.Open) + { _client.Send(text); - + } return Task.CompletedTask; } public void Start() { if (Connecting) + { throw new InvalidOperationException("正在连接中"); - + } _client = CreateNew(); Connecting = true; _connectedSuccessfully = _closedManually = false; @@ -134,8 +138,11 @@ _reconnectCancellationToken is not null private async Task TryReconnect() { if (_closedManually || !_connectedSuccessfully) + { return; + } + _reconnectCancellationToken?.Dispose(); _reconnectCancellationToken = new(); _connectionLogger.Value.Log( LogLevel.Information, diff --git a/src/Serein.Core/Services/Network/Connection/WsConnectionManager.cs b/src/Serein.Core/Services/Network/Connection/WsConnectionManager.cs index 8c499560..322433ea 100644 --- a/src/Serein.Core/Services/Network/Connection/WsConnectionManager.cs +++ b/src/Serein.Core/Services/Network/Connection/WsConnectionManager.cs @@ -85,43 +85,57 @@ private void OnMessageReceived(object? sender, MessageReceivedEventArgs e) PropertyChanged?.Invoke(this, _receivedArg); if (_settingProvider.Value.Connection.SaveLog) + { _logWriter.WriteAsync($"{DateTime.Now:t} [Received] {e.Message}"); + } if (_settingProvider.Value.Connection.OutputData) + { _connectionLogger.Value.LogReceivedData(e.Message); + } if (!_eventDispatcher.Dispatch(Event.WsDataReceived, e.Message)) + { return; - + } var node = JsonSerializer.Deserialize(e.Message); if (node is null) + { return; - + } _packetHandler.Handle(node); } public void Start() { if (_reverseWebSocketService.Active) + { throw new InvalidOperationException("反向WebSocket服务器未关闭"); - + } if (_webSocketService.Active) + { throw new InvalidOperationException("WebSocket连接未断开"); + } _sent = _received = 0; if (_settingProvider.Value.Connection.UseReverseWebSocket) + { _reverseWebSocketService.Start(); + } else + { _webSocketService.Start(); + } } public void Stop() { if (!Active && !_webSocketService.Connecting) + { throw new InvalidOperationException("WebSocket未连接"); - + } ConnectedAt = null; _sent = _received = 0; PropertyChanged?.Invoke(this, _sentArg); @@ -148,15 +162,22 @@ public async Task SendDataAsync(string data) PropertyChanged?.Invoke(this, _sentArg); if (_settingProvider.Value.Connection.SaveLog) + { _logWriter.WriteAsync($"{DateTime.Now:t} [Sent] {data}"); - + } if (_settingProvider.Value.Connection.OutputData) + { _connectionLogger.Value.LogSentData(data); + } if (_reverseWebSocketService.Active) + { await _reverseWebSocketService.SendAsync(data); + } else if (_webSocketService.Active) + { await _webSocketService.SendAsync(data); + } } private async Task SendActionRequestAsync(string endpoint, T @params) diff --git a/src/Serein.Core/Services/Network/UpdateChecker.cs b/src/Serein.Core/Services/Network/UpdateChecker.cs index 1af5f003..3059bdb1 100644 --- a/src/Serein.Core/Services/Network/UpdateChecker.cs +++ b/src/Serein.Core/Services/Network/UpdateChecker.cs @@ -31,7 +31,9 @@ public UpdateChecker(ILogger logger, SettingProvider settingProvi _timer.Elapsed += async (_, _) => { if (_settingProvider.Value.Application.CheckUpdate) + { await CheckAsync(); + } }; } @@ -44,7 +46,9 @@ public UpdateChecker(ILogger logger, SettingProvider settingProvi var version = new Version(release.TagName.TrimStart('v')); if (release.TagName == _last?.TagName) + { return release; + } _last = release; @@ -67,6 +71,7 @@ public UpdateChecker(ILogger logger, SettingProvider settingProvi { _logger.LogDebug("获取更新结束"); } + return null; } @@ -75,6 +80,8 @@ public async Task StartAsync() _timer.Start(); if (_settingProvider.Value.Application.CheckUpdate) + { await CheckAsync(); + } } } diff --git a/src/Serein.Core/Services/Network/WebApi/Apis/ApiHelper.cs b/src/Serein.Core/Services/Network/WebApi/Apis/ApiHelper.cs index d37472b8..27a0cc54 100644 --- a/src/Serein.Core/Services/Network/WebApi/Apis/ApiHelper.cs +++ b/src/Serein.Core/Services/Network/WebApi/Apis/ApiHelper.cs @@ -20,7 +20,9 @@ public static class ApiHelper if (httpContext.Request.HttpVerb is not HttpVerbs.Get or HttpVerbs.Head) { if (httpContext.Request.ContentType != "application/json") + { throw HttpException.BadRequest("不支持的\"ContentType\""); + } try { @@ -61,7 +63,7 @@ public static async Task SendPacketAsync( HttpStatusCode statusCode = HttpStatusCode.OK ) { - await httpContext.SendPacketAsync(null, statusCode); + await httpContext.SendPacketAsync(statusCode: statusCode); } public static async Task SendPacketAsync( @@ -94,10 +96,14 @@ await context.SendPacketAsync( public static async Task HandleException(IHttpContext context, Exception e) { if (e is InvalidOperationException ex) + { await context.SendPacketAsync(new ApiPacket { ErrorMsg = ex.Message, Code = 403 }); + } else + { await context.SendPacketAsync( - new ApiPacket { ErrorMsg = e.GetDetailString(), Code = 500 } - ); + new ApiPacket { ErrorMsg = e.GetDetailString(), Code = 500 } + ); + } } } diff --git a/src/Serein.Core/Services/Network/WebApi/Apis/AuthGate.cs b/src/Serein.Core/Services/Network/WebApi/Apis/AuthGate.cs index 681003c2..3f2ebf6c 100644 --- a/src/Serein.Core/Services/Network/WebApi/Apis/AuthGate.cs +++ b/src/Serein.Core/Services/Network/WebApi/Apis/AuthGate.cs @@ -16,18 +16,25 @@ internal class AuthGate(SettingProvider settingProvider) : WebModuleBase("/") protected override Task OnRequestAsync(IHttpContext context) { if (context.RequestedPath == "/broadcast") + { return Task.CompletedTask; - + } var auth = context.Request.Headers.Get("Authorization"); if (_settingProvider.Value.WebApi.AccessTokens.Length == 0) + { return Task.CompletedTask; + } if (string.IsNullOrEmpty(auth)) + { throw HttpException.Unauthorized(); + } if (auth.StartsWith("Bearer ")) + { auth = auth[7..]; + } return !_settingProvider.Value.WebApi.AccessTokens.Contains(auth) ? throw HttpException.Unauthorized() diff --git a/src/Serein.Core/Services/Network/WebApi/Apis/ServersApi.cs b/src/Serein.Core/Services/Network/WebApi/Apis/ServersApi.cs index c4adb929..e1b83501 100644 --- a/src/Serein.Core/Services/Network/WebApi/Apis/ServersApi.cs +++ b/src/Serein.Core/Services/Network/WebApi/Apis/ServersApi.cs @@ -45,7 +45,9 @@ public async Task RemoveServer(string id) public async Task GetServer(string id) { if (!_serverManager.Servers.TryGetValue(id, out var server)) + { throw HttpException.NotFound("未找到指定的服务器"); + } await HttpContext.SendPacketAsync(server); } @@ -54,7 +56,9 @@ public async Task GetServer(string id) public async Task StartServer(string id) { if (!_serverManager.Servers.TryGetValue(id, out var server)) + { throw HttpException.NotFound("未找到指定的服务器"); + } server.Start(); await HttpContext.SendPacketAsync(); @@ -64,7 +68,9 @@ public async Task StartServer(string id) public async Task StopServer(string id) { if (!_serverManager.Servers.TryGetValue(id, out var server)) + { throw HttpException.NotFound("未找到指定的服务器"); + } server.Stop(); await HttpContext.SendPacketAsync(HttpStatusCode.Accepted); @@ -74,7 +80,9 @@ public async Task StopServer(string id) public async Task TerminateServer(string id) { if (!_serverManager.Servers.TryGetValue(id, out var server)) + { throw HttpException.NotFound("未找到指定的服务器"); + } server.Terminate(); await HttpContext.SendPacketAsync(); @@ -84,13 +92,19 @@ public async Task TerminateServer(string id) public async Task InputServer(string id, [QueryField("line", true)] string[] lines) { if (!_serverManager.Servers.TryGetValue(id, out var server)) + { throw HttpException.NotFound("未找到指定的服务器"); + } if (!server.Status) + { throw HttpException.Forbidden("服务器未运行"); + } foreach (var l in lines) + { server.Input(l); + } await HttpContext.SendPacketAsync(); } @@ -99,16 +113,22 @@ public async Task InputServer(string id, [QueryField("line", true)] string[] lin public async Task InputServer(string id) { if (!_serverManager.Servers.TryGetValue(id, out var server)) + { throw HttpException.NotFound("未找到指定的服务器"); + } if (!server.Status) + { throw HttpException.Forbidden("服务器未运行"); + } foreach ( var l in await HttpContext.ConvertRequestAs() ?? throw HttpException.BadRequest() ) + { server.Input(l); + } await HttpContext.SendPacketAsync(); } diff --git a/src/Serein.Core/Services/Network/WebApi/BroadcastWebSocketModule.cs b/src/Serein.Core/Services/Network/WebApi/BroadcastWebSocketModule.cs index 857e2505..d0f98ee5 100644 --- a/src/Serein.Core/Services/Network/WebApi/BroadcastWebSocketModule.cs +++ b/src/Serein.Core/Services/Network/WebApi/BroadcastWebSocketModule.cs @@ -84,9 +84,10 @@ protected override Task OnClientDisconnectedAsync(IWebSocketContext context) { list.Remove(context); if (list.Count == 0) + { _clients.Remove(id); + } } - return Task.CompletedTask; } diff --git a/src/Serein.Core/Services/Network/WebApi/HttpServer.cs b/src/Serein.Core/Services/Network/WebApi/HttpServer.cs index 5abbcc83..9e0faf2f 100644 --- a/src/Serein.Core/Services/Network/WebApi/HttpServer.cs +++ b/src/Serein.Core/Services/Network/WebApi/HttpServer.cs @@ -41,15 +41,22 @@ static HttpServer() public void Start() { if (State != WebServerState.Stopped && State != WebServerState.Created) + { throw new InvalidOperationException("WebApi服务器正在运行中"); + } if (_cancellationTokenSource.IsCancellationRequested) + { + _cancellationTokenSource.Dispose(); _cancellationTokenSource = new(); + } _webServer = new WebServer(CreateOptions()); if (_settingProvider.Value.WebApi.AllowCrossOrigin) + { _webServer.WithCors(); + } _webServer.WithModule(_serviceProvider.GetRequiredService()); _webServer.WithModule(new AuthGate(_settingProvider)); @@ -79,8 +86,9 @@ public void Stop() || State == WebServerState.Created || _webServer is null ) + { throw new InvalidOperationException("WebApi服务器不在运行中"); - + } _cancellationTokenSource.Cancel(); _webServer.Dispose(); _logger.LogInformation("WebApi服务器已停止"); @@ -105,8 +113,9 @@ private WebServerOptions CreateOptions() .AutoRegisterCertificate; if (string.IsNullOrEmpty(_settingProvider.Value.WebApi.Certificate.Path)) + { return options; - + } options.Certificate = File.Exists(_settingProvider.Value.WebApi.Certificate.Path) ? new( _settingProvider.Value.WebApi.Certificate.Path!, diff --git a/src/Serein.Core/Services/Network/WebApi/IPBannerModule.cs b/src/Serein.Core/Services/Network/WebApi/IPBannerModule.cs index 65c96068..edc8b252 100644 --- a/src/Serein.Core/Services/Network/WebApi/IPBannerModule.cs +++ b/src/Serein.Core/Services/Network/WebApi/IPBannerModule.cs @@ -21,6 +21,8 @@ public IPBannerModule(SettingProvider settingProvider) public static async Task Handle403(IHttpContext context, IHttpException exception) { if (exception.StatusCode == 403) + { await ApiHelper.HandleHttpException(context, exception); + } } } diff --git a/src/Serein.Core/Services/Permissions/GroupManager.cs b/src/Serein.Core/Services/Permissions/GroupManager.cs index 52cb724e..712eaf82 100644 --- a/src/Serein.Core/Services/Permissions/GroupManager.cs +++ b/src/Serein.Core/Services/Permissions/GroupManager.cs @@ -25,13 +25,17 @@ PermissionGroupProvider permissionGroupProvider public static void ValidateGroupId(string? id) { if (string.IsNullOrEmpty(id) || !GroupIdRegex.IsMatch(id)) + { throw new InvalidOperationException("权限组Id格式不正确"); + } } public void Add(string id, Group group) { if (!_permissionGroupProvider.Value.TryAdd(id, group)) + { throw new InvalidOperationException("已经存在了相同Id的权限组"); + } _permissionGroupProvider.SaveAsyncWithDebounce(); } @@ -63,10 +67,14 @@ public Group this[string id] foreach (var kv in _permissionGroupProvider.Value) { if (visitedGroups.Contains(kv.Key)) + { continue; + } if (kv.Key == "everyone" || kv.Value.Members.Contains(userId)) + { ProcessGroup(kv.Key, kv.Value); + } } } @@ -75,13 +83,18 @@ public Group this[string id] void ProcessGroup(string groupId, Group group) { if (visitedGroups.Contains(groupId)) + { return; - + } visitedGroups.Add(groupId); foreach (var parent in group.Parents) + { if (_permissionGroupProvider.Value.TryGetValue(parent, out var parentGroup)) + { ProcessGroup(parent, parentGroup); + } + } AddNodes(group.Priority, group.Nodes); } @@ -94,18 +107,26 @@ void AddNodes(int priority, Dictionary permissions) { var keyWithoutWildcard = permission.Key[..^2]; foreach (var key in nodes) + { if (key.StartsWith(keyWithoutWildcard)) + { CompareAndAdd(key, priority, permission.Value); + } + } } else + { CompareAndAdd(permission.Key, priority, permission.Value); + } } } void CompareAndAdd(string key, int priority, bool? value) { if (!result.TryGetValue(key, out var permission) || permission.Priority <= priority) + { result[key] = (priority, value); + } } } } diff --git a/src/Serein.Core/Services/Permissions/PermissionManager.cs b/src/Serein.Core/Services/Permissions/PermissionManager.cs index b364d97e..011fdec4 100644 --- a/src/Serein.Core/Services/Permissions/PermissionManager.cs +++ b/src/Serein.Core/Services/Permissions/PermissionManager.cs @@ -16,7 +16,9 @@ public partial class PermissionManager public void Register(string id, string node, string? description = null) { if (!GetKeyRegex().IsMatch(node)) + { throw new ArgumentException("权限节点不合法", nameof(node)); + } _nodes.Add($"{id}.{node}", description); } @@ -24,7 +26,9 @@ public void Register(string id, string node, string? description = null) public void Unregister(string id, string node) { if (!_nodes.Remove($"{id}.{node}")) + { throw new KeyNotFoundException(); + } } internal void Clear() => _nodes.Clear(); diff --git a/src/Serein.Core/Services/Plugins/EventDispatcher.cs b/src/Serein.Core/Services/Plugins/EventDispatcher.cs index 9cbeebd4..1e8cb364 100644 --- a/src/Serein.Core/Services/Plugins/EventDispatcher.cs +++ b/src/Serein.Core/Services/Plugins/EventDispatcher.cs @@ -39,8 +39,12 @@ internal bool Dispatch(Event @event, params object[] args) DispatchToJsPlugins(tasks, @event, cancellationTokenSource.Token, args); foreach (var t in DispatchToNetPlugins(@event, cancellationTokenSource.Token, args)) + { if (t is Task tb) + { tasks.Add(tb); + } + } _logger.LogDebug("事件({})任务数:{}", @event, tasks.Count); @@ -52,7 +56,9 @@ internal bool Dispatch(Event @event, params object[] args) } if (_settingProvider.Value.Application.PluginEventMaxWaitingTime > 0) + { Task.WaitAll([.. tasks], _settingProvider.Value.Application.PluginEventMaxWaitingTime); + } cancellationTokenSource.Cancel(); return tasks.Select((t) => !t.IsCompleted || t.Result).Any((b) => !b); @@ -66,7 +72,9 @@ params object[] args ) { foreach ((_, var jsPlugin) in _jsPluginLoader.Plugins) + { tasks.Add(Task.Run(() => jsPlugin.Invoke(@event, cancellationToken, args))); + } } private List DispatchToNetPlugins( @@ -80,7 +88,9 @@ params object[] args foreach ((var name, var plugin) in _netPluginLoader.Plugins) { if (cancellationToken.IsCancellationRequested) + { break; + } try { diff --git a/src/Serein.Core/Services/Plugins/Js/JsPluginLoader.cs b/src/Serein.Core/Services/Plugins/Js/JsPluginLoader.cs index b8a43edb..12fed519 100644 --- a/src/Serein.Core/Services/Plugins/Js/JsPluginLoader.cs +++ b/src/Serein.Core/Services/Plugins/Js/JsPluginLoader.cs @@ -47,16 +47,20 @@ public void Load(PluginInfo pluginInfo, string dir) var entry = Path.Join(dir, pluginInfo.EntryFile ?? "index.js"); if (!File.Exists(entry)) + { throw new FileNotFoundException("找不到指定的入口点文件"); + } JsPluginConfig? jsConfig = null; var configPath = Path.Join(dir, PathConstants.JsPluginConfigFileName); if (File.Exists(configPath)) + { jsConfig = JsonSerializer.Deserialize( - File.ReadAllText(configPath), - JsonSerializerOptionsFactory.CamelCase - ); + File.ReadAllText(configPath), + JsonSerializerOptionsFactory.CamelCase + ); + } JsPlugin? jsPlugin = null; try @@ -85,7 +89,9 @@ public void LoadSingle() file.EndsWith ) ) + { continue; + } var name = Path.GetFileNameWithoutExtension(file); var id = Guid.NewGuid().ToString("N"); @@ -101,7 +107,9 @@ public void LoadSingle() ); if (_netPluginLoader.Plugins.ContainsKey(name)) + { throw new NotSupportedException($"尝试加载“{file}”插件时发现Id重复"); + } jsPlugin.Execute(File.ReadAllText(file)); } @@ -113,7 +121,9 @@ public void LoadSingle() finally { if (jsPlugin is not null) + { JsPlugins.TryAdd(id, jsPlugin); + } } } } diff --git a/src/Serein.Core/Services/Plugins/Js/Properties/ConnectionProperty.cs b/src/Serein.Core/Services/Plugins/Js/Properties/ConnectionProperty.cs index 53583ff2..5c16bfef 100644 --- a/src/Serein.Core/Services/Plugins/Js/Properties/ConnectionProperty.cs +++ b/src/Serein.Core/Services/Plugins/Js/Properties/ConnectionProperty.cs @@ -28,21 +28,21 @@ public void Stop() public void SendData(string text) { - ArgumentNullException.ThrowIfNull(text, nameof(text)); + ArgumentNullException.ThrowIfNull(text); _wsConnectionManager.SendDataAsync(text).Await(); } public void SendGroupMsg(long target, string message) { - ArgumentNullException.ThrowIfNull(message, nameof(message)); + ArgumentNullException.ThrowIfNull(message); _wsConnectionManager.SendGroupMsgAsync(target, message).Await(); } public void SendPrivateMsg(long target, string message) { - ArgumentNullException.ThrowIfNull(message, nameof(message)); + ArgumentNullException.ThrowIfNull(message); _wsConnectionManager.SendPrivateMsgAsync(target, message).Await(); } diff --git a/src/Serein.Core/Services/Plugins/Js/ScriptInstance.cs b/src/Serein.Core/Services/Plugins/Js/ScriptInstance.cs index 5a196b12..8058f1b2 100644 --- a/src/Serein.Core/Services/Plugins/Js/ScriptInstance.cs +++ b/src/Serein.Core/Services/Plugins/Js/ScriptInstance.cs @@ -66,12 +66,16 @@ public void Log(params JsValue[] jsValues) public bool Exports(string? name, JsValue jsValue) { if (string.IsNullOrEmpty(name)) + { return false; + } try { if (jsValue.IsNull() || jsValue.IsUndefined()) + { return _pluginManager.ExportedVariables.Remove(name, out _); + } _pluginManager.ExportedVariables[name] = jsValue.ToObject(); return true; diff --git a/src/Serein.Core/Services/Plugins/Js/TimerFactory.cs b/src/Serein.Core/Services/Plugins/Js/TimerFactory.cs index afde15ce..0c669506 100644 --- a/src/Serein.Core/Services/Plugins/Js/TimerFactory.cs +++ b/src/Serein.Core/Services/Plugins/Js/TimerFactory.cs @@ -43,7 +43,9 @@ internal TimerFactory(CancellationToken cancellationToken) public long SetTimeout(JsValue jsValue, long milliseconds, params JsValue[] args) { if (jsValue is not Function function) + { throw new ArgumentException("The first argument must be a function.", nameof(jsValue)); + } var timer = new Timer(milliseconds) { AutoReset = false }; var id = _timeoutTimerId++; @@ -68,7 +70,9 @@ public void ClearTimeout(long id) public long SetInterval(JsValue jsValue, long milliseconds, params JsValue[] args) { if (jsValue is not Function function) + { throw new ArgumentException("The first argument must be a function.", nameof(jsValue)); + } var timer = new Timer(milliseconds) { AutoReset = true }; var id = _intervalTimerId++; @@ -102,12 +106,16 @@ private static void SafeCall(Function function, params JsValue[] args) function.Engine.Call(function, args); } else + { throw new TimeoutException(); + } } finally { if (entered) + { Monitor.Exit(function.Engine); + } } } } diff --git a/src/Serein.Core/Services/Plugins/Net/NetPluginLoader.cs b/src/Serein.Core/Services/Plugins/Net/NetPluginLoader.cs index d6ac1d72..718dce3d 100644 --- a/src/Serein.Core/Services/Plugins/Net/NetPluginLoader.cs +++ b/src/Serein.Core/Services/Plugins/Net/NetPluginLoader.cs @@ -38,7 +38,9 @@ public void Load(PluginInfo pluginInfo, string dir) ); if (!File.Exists(entry)) + { throw new FileNotFoundException("插件入口点文件不存在", entry); + } var context = new PluginAssemblyLoadContext(entry); context.Unloading += (_) => _logger.LogDebug("插件\"{}\"上下文已卸载", pluginInfo.Id); @@ -58,7 +60,9 @@ public void Load(PluginInfo pluginInfo, string dir) finally { if (plugin is not null) + { NetPlugins.TryAdd(pluginInfo.Id, plugin); + } } } @@ -68,25 +72,36 @@ private PluginBase CreatePluginInstance(Type[] allTypes) var count = types.Count(); if (count > 1) + { throw new InvalidOperationException("该程序集存在多个插件入口点"); + } if (count == 0) + { throw new InvalidOperationException("未找到有效的插件入口点"); + } var type = types.First(); foreach (var ctor in type.GetConstructors()) { if (ctor.IsStatic || !ctor.IsPublic) + { continue; + } var args = new List(); foreach (var parameterInfo in ctor.GetParameters()) + { if (parameterInfo.ParameterType == typeof(IServiceProvider)) + { args.Add(_serviceProvider); + } else + { args.Add(_serviceProvider.GetRequiredService(parameterInfo.ParameterType)); - + } + } return ctor.Invoke([.. args]) as PluginBase ?? throw new NotSupportedException(); } @@ -117,7 +132,9 @@ public void Unload() try { if (reference.TryGetTarget(out var context)) + { context.Unload(); + } } catch { } } diff --git a/src/Serein.Core/Services/Plugins/PluginManager.cs b/src/Serein.Core/Services/Plugins/PluginManager.cs index fcf505b3..0b762e82 100644 --- a/src/Serein.Core/Services/Plugins/PluginManager.cs +++ b/src/Serein.Core/Services/Plugins/PluginManager.cs @@ -69,7 +69,9 @@ public void Load() try { if (Loading) + { throw new InvalidOperationException("正在加载插件"); + } Loading = true; @@ -86,7 +88,9 @@ public void Load() foreach (var dir in Directory.GetDirectories(PathConstants.PluginsDirectory)) { if (!File.Exists(Path.Join(dir, PathConstants.PluginInfoFileName))) + { continue; + } PluginInfo pluginInfo; try @@ -98,7 +102,9 @@ public void Load() ) ?? throw new InvalidDataException("插件信息为空"); if (string.IsNullOrWhiteSpace(pluginInfo.Id)) + { throw new InvalidOperationException("Id不可为空"); + } } catch (Exception e) { @@ -112,7 +118,9 @@ public void Load() _jsPluginLoader.JsPlugins.ContainsKey(pluginInfo.Id) || _netPluginLoader.NetPlugins.ContainsKey(pluginInfo.Id) ) + { throw new InvalidOperationException("插件Id重复"); + } _pluginLogger.Log( LogLevel.Trace, @@ -121,12 +129,17 @@ public void Load() ); if (pluginInfo.Type == PluginType.Js) + { _jsPluginLoader.Load(pluginInfo, dir); + } else if (pluginInfo.Type == PluginType.Net) + { _netPluginLoader.Load(pluginInfo, dir); + } else + { throw new NotSupportedException("未指定插件类型"); - + } _pluginLogger.Log( LogLevel.Trace, string.Empty, @@ -178,7 +191,9 @@ public void Unload() public void Reload() { if (Reloading || Loading) + { throw new InvalidOperationException("正在加载插件"); + } Reloading = true; diff --git a/src/Serein.Core/Services/Plugins/PluginService.cs b/src/Serein.Core/Services/Plugins/PluginService.cs index 61fce3f9..31d2ce8d 100644 --- a/src/Serein.Core/Services/Plugins/PluginService.cs +++ b/src/Serein.Core/Services/Plugins/PluginService.cs @@ -21,7 +21,9 @@ public Task StartAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken) { if (!_unloaded) + { Task.Run(_pluginManager.Unload, cancellationToken); + } _unloaded = true; diff --git a/src/Serein.Core/Services/Plugins/Storages/LocalStorage.cs b/src/Serein.Core/Services/Plugins/Storages/LocalStorage.cs index a34aea60..ff6b0399 100644 --- a/src/Serein.Core/Services/Plugins/Storages/LocalStorage.cs +++ b/src/Serein.Core/Services/Plugins/Storages/LocalStorage.cs @@ -26,23 +26,31 @@ public LocalStorage(ILogger logger) ); if (data?.Type == typeof(JsonObject).ToString() && data.Data is not null) + { foreach ((string key, JsonNode? value) in data.Data) + { _data.TryAdd(key, value?.ToString() ?? "null"); + } + } } else + { OnUpdated(); + } } protected override void OnUpdated() { Directory.CreateDirectory(PathConstants.PluginsDirectory); lock (_data) + { File.WriteAllText( - Path, - JsonSerializer.Serialize( - DataItemWrapper.Wrap(_data), - options: new(JsonSerializerOptionsFactory.CamelCase) { WriteIndented = true } - ) - ); + Path, + JsonSerializer.Serialize( + DataItemWrapper.Wrap(_data), + options: new(JsonSerializerOptionsFactory.CamelCase) { WriteIndented = true } + ) + ); + } } } diff --git a/src/Serein.Core/Services/Plugins/Storages/StorageBase.cs b/src/Serein.Core/Services/Plugins/Storages/StorageBase.cs index 581a1223..7c9a6afd 100644 --- a/src/Serein.Core/Services/Plugins/Storages/StorageBase.cs +++ b/src/Serein.Core/Services/Plugins/Storages/StorageBase.cs @@ -15,7 +15,9 @@ public abstract class StorageBase(ILogger logger) public void Clear() { lock (_data) + { _data.Clear(); + } OnUpdated(); } @@ -28,7 +30,9 @@ public void Clear() public void RemoveItem(string key) { lock (_data) + { _data.Remove(key); + } OnUpdated(); } @@ -36,8 +40,10 @@ public void RemoveItem(string key) public void SetItem(string key, string value) { lock (_data) + { _data[key] = value ?? "null"; - + } + _logger.LogDebug("更新:'{}'='{}'", key, value); OnUpdated(); } diff --git a/src/Serein.Core/Services/SentryReporter.cs b/src/Serein.Core/Services/SentryReporter.cs index e14e81a4..88333404 100644 --- a/src/Serein.Core/Services/SentryReporter.cs +++ b/src/Serein.Core/Services/SentryReporter.cs @@ -18,7 +18,9 @@ internal class SentryReporter(ILogger logger, SettingProvider se public void Initialize() { if (!_settingProvider.Value.Application.EnableSentry || SereinApp.Type == AppType.Unknown) + { return; + } SentrySdk.Init(options => { diff --git a/src/Serein.Core/Services/Servers/Server.cs b/src/Serein.Core/Services/Servers/Server.cs index e0cb6fe4..910d1d30 100644 --- a/src/Serein.Core/Services/Servers/Server.cs +++ b/src/Serein.Core/Services/Servers/Server.cs @@ -86,13 +86,19 @@ public void Start() _logger.LogDebug("Id={}: 请求启动", Id); if (Status) + { throw new InvalidOperationException("服务器已在运行"); + } if (string.IsNullOrEmpty(Configuration.FileName)) + { throw new InvalidOperationException("启动文件为空"); + } if (!_eventDispatcher.Dispatch(Event.ServerStarting, this)) + { return; + } _serverProcess = Process.Start( new ProcessStartInfo @@ -144,7 +150,9 @@ public void Start() _logger.LogDebug("Id={}: 正在启动", Id); if (Configuration.SaveLog) + { _logWriter.WriteAsync(DateTime.Now.ToString("T") + " 服务器已启动"); + } } public void Stop() @@ -152,15 +160,22 @@ public void Stop() _logger.LogDebug("Id={}: 请求关闭", Id); if (CancelRestart()) + { return; + } if (!Status || _serverProcess is null) + { throw new InvalidOperationException("服务器未运行"); + } if (!_eventDispatcher.Dispatch(Event.ServerStopping, this)) + { return; + } if (Configuration.StopCommands.Length == 0) + { if ( SereinApp.Type is AppType.Lite or AppType.Plus && Environment.OSVersion.Platform == PlatformID.Win32NT @@ -184,11 +199,18 @@ SereinApp.Type is AppType.Lite or AppType.Plus _logger.LogDebug("Id={}: 发送Ctrl+C事件", Id); } else + { throw new NotSupportedException("关服命令为空"); + } + } foreach (string command in Configuration.StopCommands) + { if (!string.IsNullOrEmpty(command)) + { Input(command); + } + } _logger.LogDebug("Id={}: 正在关闭", Id); } @@ -196,9 +218,13 @@ SereinApp.Type is AppType.Lite or AppType.Plus internal void InputFromCommand(string command, EncodingMap.EncodingType? encodingType = null) { if (Status) + { Input(command, encodingType); + } else if (command == "start") + { Start(); + } else if (command == "stop") { RestartStatus = RestartStatus.None; @@ -208,7 +234,7 @@ internal void InputFromCommand(string command, EncodingMap.EncodingType? encodin public void Input(string command) { - Input(command, null, false); + Input(command, fromUser: false); } internal void Input( @@ -226,10 +252,14 @@ internal void Input( ); if (_inputWriter is null || !Status) + { return; + } if (!_eventDispatcher.Dispatch(Event.ServerInput, this, command)) + { return; + } _inputWriter.Write( EncodingMap @@ -249,10 +279,14 @@ internal void Input( _commandHistory.Count > 0 && _commandHistory[^1] != command || _commandHistory.Count == 0 ) + { _commandHistory.Add(command); + } if (SereinApp.Type != AppType.Cli) + { ServerOutput?.Invoke(this, new(ServerOutputType.InputCommand, command)); + } } CommandHistoryIndex = CommandHistory.Count; @@ -265,7 +299,9 @@ public void RequestRestart() _logger.LogDebug("Id={}: 请求重启", Id); if (RestartStatus != RestartStatus.None) + { throw new InvalidOperationException("正在等待重启"); + } Stop(); @@ -277,10 +313,14 @@ public void Terminate() _logger.LogDebug("Id={}: 请求强制结束", Id); if (CancelRestart()) + { return; + } if (!Status) + { throw new InvalidOperationException("服务器未运行"); + } _serverProcess?.Kill(true); _isTerminated = true; @@ -293,7 +333,9 @@ private void OnExit(object? sender, EventArgs e) _logger.LogDebug("Id={}: 进程(PID={})退出:{}", Id, _serverProcess?.Id, exitCode); if (Configuration.SaveLog) + { _logWriter.WriteAsync(DateTime.Now.ToString("T") + " 进程退出:" + exitCode); + } ServerOutput?.Invoke( this, @@ -310,7 +352,9 @@ private void OnExit(object? sender, EventArgs e) && Configuration.AutoRestart && !_isTerminated ) + { Task.Run(WaitAndRestart); + } _serverInfo.ExitTime = _serverProcess?.ExitTime; _serverProcess = null; @@ -318,7 +362,9 @@ private void OnExit(object? sender, EventArgs e) ServerStatusChanged?.Invoke(this, EventArgs.Empty); if (!_eventDispatcher.Dispatch(Event.ServerExited, this, exitCode, DateTime.Now)) + { return; + } _reactionManager.TriggerAsync( exitCode == 0 @@ -331,23 +377,31 @@ private void OnExit(object? sender, EventArgs e) private void OnOutputDataReceived(object? sender, DataReceivedEventArgs e) { if (e.Data is null) + { return; + } _logger.LogDebug("Id={}: 输出'{}'", Id, e.Data); if (Configuration.SaveLog) + { _logWriter.WriteAsync(e.Data); + } _serverInfo.OutputLines++; ServerOutput?.Invoke(this, new(ServerOutputType.Raw, e.Data)); if (!_eventDispatcher.Dispatch(Event.ServerRawOutput, this, e.Data)) + { return; + } var filtered = OutputFilter.Clear(e.Data); if (!_eventDispatcher.Dispatch(Event.ServerOutput, this, filtered)) + { return; + } if ( _settingProvider.Value.Application.PattenForEnableMatchingMuiltLines.Any( @@ -359,7 +413,9 @@ private void OnOutputDataReceived(object? sender, DataReceivedEventArgs e) _matcher.MatchServerOutputAsync(Id, string.Join('\n', _cache)); } else + { _cache.Clear(); + } _matcher.MatchServerOutputAsync(Id, filtered); } @@ -383,6 +439,7 @@ _restartCancellationTokenSource is not null private void WaitAndRestart() { RestartStatus = RestartStatus.Preparing; + _restartCancellationTokenSource?.Dispose(); _restartCancellationTokenSource = new(); ServerOutput?.Invoke( @@ -399,6 +456,7 @@ private void WaitAndRestart() { RestartStatus = RestartStatus.None; if (!task.IsCanceled) + { try { Start(); @@ -407,6 +465,7 @@ private void WaitAndRestart() { ServerOutput?.Invoke(this, new(ServerOutputType.Error, e.Message)); } + } } ); } @@ -434,8 +493,10 @@ private async Task UpdateInfo() _prevProcessCpuTime = _serverProcess.TotalProcessorTime; if (Configuration.PortIPv4 >= 0) + { await Task.Run( () => _serverInfo.Stat = new("127.0.0.1", (ushort)Configuration.PortIPv4) ); + } } } diff --git a/src/Serein.Core/Services/Servers/ServerManager.cs b/src/Serein.Core/Services/Servers/ServerManager.cs index 50d60f56..3f821f5d 100644 --- a/src/Serein.Core/Services/Servers/ServerManager.cs +++ b/src/Serein.Core/Services/Servers/ServerManager.cs @@ -55,10 +55,14 @@ public partial class ServerManager public static void ValidateId(string? id) { if (string.IsNullOrEmpty(id) || !ServerId.IsMatch(id)) - throw new InvalidOperationException("服务器Id格式不正确"); + { + throw new ArgumentException("服务器Id格式不正确", nameof(id)); + } if (Blacklist.Contains(id.ToUpperInvariant())) - throw new InvalidOperationException("不能使用Windows的保留关键字作为Id"); + { + throw new ArgumentException("不能使用Windows的保留关键字作为Id", nameof(id)); + } } public IReadOnlyDictionary Servers => _servers; @@ -125,14 +129,20 @@ public bool Remove(string id) ValidateId(id); if (_servers.TryGetValue(id, out var server) && server.Status) + { throw new InvalidOperationException("服务器仍在运行中"); + } if (!_servers.Remove(id) || server is null) + { return false; + } var path = string.Format(PathConstants.ServerConfigFile, id); if (File.Exists(path)) + { File.Delete(path); + } _logger.LogDebug("删除服务器:{}", id); ServersUpdated?.Invoke(this, new(ServersUpdatedType.Removed, id, server)); @@ -207,7 +217,9 @@ private void OnCrash() server.Configuration.AutoStopWhenCrashing && server.Status ) + { server.Stop(); + } } catch { } } diff --git a/src/Serein.Core/Services/Servers/ServerPluginManager.cs b/src/Serein.Core/Services/Servers/ServerPluginManager.cs index ff031b0c..2e471ab3 100644 --- a/src/Serein.Core/Services/Servers/ServerPluginManager.cs +++ b/src/Serein.Core/Services/Servers/ServerPluginManager.cs @@ -46,7 +46,9 @@ internal ServerPluginManager(Server server) public void Update() { if (_updating) + { return; + } lock (_plugins) { @@ -58,7 +60,9 @@ public void Update() _plugins.Clear(); if (!File.Exists(_server.Configuration.FileName)) + { return; + } foreach (var file in EnumerateFiles()) { @@ -68,6 +72,7 @@ public void Update() .ToLowerInvariant(); if (AcceptableExtensions.Contains(extension)) + { _plugins.Add( new( file, @@ -82,6 +87,7 @@ public void Update() } ) ); + } } } finally @@ -102,7 +108,9 @@ private IEnumerable EnumerateFiles() var path = Path.GetFullPath(Path.Join(root, dir)); if (!Directory.Exists(path)) + { continue; + } CurrentPluginsDirectory = path; return Directory.EnumerateFiles(path, "*.*"); @@ -117,7 +125,9 @@ public void Add(params string[] paths) string.IsNullOrEmpty(CurrentPluginsDirectory) || !Directory.Exists(CurrentPluginsDirectory) ) + { throw new InvalidOperationException("无法获取插件文件夹"); + } foreach (var path in paths) File.Copy(path, Path.Join(CurrentPluginsDirectory, Path.GetFileName(path))); @@ -126,10 +136,14 @@ public void Add(params string[] paths) public void Remove(ServerPlugin serverPlugin) { if (!_plugins.Remove(serverPlugin)) + { throw new InvalidOperationException("无法通过此插件管理器删除该插件"); + } if (!serverPlugin.FileInfo.Exists) + { throw new FileNotFoundException($"插件\"{serverPlugin.Path}\"不存在"); + } serverPlugin.FileInfo.Delete(); } @@ -137,13 +151,18 @@ public void Remove(ServerPlugin serverPlugin) public void Disable(ServerPlugin serverPlugin) { if (!Plugins.Contains(serverPlugin)) + { throw new InvalidOperationException("无法通过此插件管理器禁用该插件"); + } if (!serverPlugin.IsEnabled) + { throw new InvalidOperationException("不能禁用已经被禁用的插件"); - + } if (!serverPlugin.FileInfo.Exists) + { throw new FileNotFoundException($"插件\"{serverPlugin.Path}\"不存在"); + } serverPlugin.FileInfo.MoveTo(serverPlugin.Path + DisabledPluginExtension); } @@ -151,20 +170,28 @@ public void Disable(ServerPlugin serverPlugin) public void Enable(ServerPlugin serverPlugin) { if (!Plugins.Contains(serverPlugin)) + { throw new InvalidOperationException("无法通过此插件管理器启用该插件"); + } if (serverPlugin.IsEnabled) + { throw new InvalidOperationException("不能禁用未被禁用的插件"); + } if (!serverPlugin.FileInfo.Exists) + { throw new FileNotFoundException($"插件\"{serverPlugin.Path}\"不存在"); + } if (Path.GetExtension(serverPlugin.Path) == DisabledPluginExtension) + { serverPlugin.FileInfo.MoveTo( - Path.Join( - CurrentPluginsDirectory, - Path.GetFileNameWithoutExtension(serverPlugin.Path) - ) - ); + Path.Join( + CurrentPluginsDirectory, + Path.GetFileNameWithoutExtension(serverPlugin.Path) + ) + ); + } } } diff --git a/src/Serein.Core/Utils/CrashHelper.cs b/src/Serein.Core/Utils/CrashHelper.cs index 478d0f69..76093896 100644 --- a/src/Serein.Core/Utils/CrashHelper.cs +++ b/src/Serein.Core/Utils/CrashHelper.cs @@ -26,7 +26,7 @@ public static string CreateLog(Exception e) sb.AppendLine("程序集:" + Assembly.GetEntryAssembly()?.FullName); sb.AppendLine("时间:" + date.ToString("s")); sb.AppendLine("文件路径:" + AppDomain.CurrentDomain.BaseDirectory); - sb.AppendLine("操作系统:" + Environment.OSVersion.ToString()); + sb.AppendLine("操作系统:" + Environment.OSVersion); sb.AppendLine("CLR版本:" + Environment.Version); sb.AppendLine(e.ToString()); sb.AppendLine(); diff --git a/src/Serein.Core/Utils/Extensions/StringExtension.cs b/src/Serein.Core/Utils/Extensions/StringExtension.cs index b1321216..e7a0221f 100644 --- a/src/Serein.Core/Utils/Extensions/StringExtension.cs +++ b/src/Serein.Core/Utils/Extensions/StringExtension.cs @@ -26,16 +26,24 @@ public static string ToUnicode(this string text) public static string ToSizeString(this long size) { if (size < 1024) + { return size.ToString() + " B"; + } if (size < 1024 * 1024) + { return ((double)size / 1024).ToString("N1") + " KB"; + } if (size < 1024 * 1024 * 1024) + { return ((double)size / 1024 / 1024).ToString("N1") + " MB"; + } if (size < (long)1024 * 1024 * 1024 * 1024) + { return ((double)size / 1024 / 1024 / 1024).ToString("N1") + " GB"; + } return ((double)size / 1024 / 1024 / 1024 / 1024).ToString("N1") + " TB"; } @@ -43,7 +51,9 @@ public static string ToSizeString(this long size) public static void OpenInExplorer(this string path) { if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { throw new PlatformNotSupportedException(); + } Process.Start( new ProcessStartInfo("explorer.exe") @@ -57,7 +67,9 @@ public static string GetHexString(this byte[] targetData) { var stringBuilder = new StringBuilder(); for (int i = 0; i < targetData.Length; i++) + { stringBuilder.Append(targetData[i].ToString("x2")); + } return stringBuilder.ToString(); } diff --git a/src/Serein.Core/Utils/OutputFilter.cs b/src/Serein.Core/Utils/OutputFilter.cs index 34c8857c..ac52e339 100644 --- a/src/Serein.Core/Utils/OutputFilter.cs +++ b/src/Serein.Core/Utils/OutputFilter.cs @@ -3,7 +3,7 @@ namespace Serein.Core.Utils; -public partial class OutputFilter +public static partial class OutputFilter { public static string RemoveControlChars(string input) { @@ -12,7 +12,9 @@ public static string RemoveControlChars(string input) { var c = input[i]; if (c > 31 && c != 127 || c == 0x1b) + { stringBuilder.Append(input[i]); + } } return stringBuilder.ToString(); } diff --git a/src/Serein.Lite/Models/LineFragment.cs b/src/Serein.Lite/Models/LineFragment.cs index 41bf9ab5..42016d9d 100644 --- a/src/Serein.Lite/Models/LineFragment.cs +++ b/src/Serein.Lite/Models/LineFragment.cs @@ -6,10 +6,11 @@ namespace Serein.Lite.Models; public record LineFragment(string Text) { - public LineFragment(string text, LineFragment from) : this(text) + public LineFragment(string text, LineFragment from) + : this(text) { Styles = new(from.Styles); - Classes = new(from.Classes); + Classes = [.. from.Classes]; } public List Classes { get; } = []; @@ -31,18 +32,24 @@ public void Reset() public override string ToString() { if (string.IsNullOrEmpty(Text)) + { return string.Empty; + } var sb = new StringBuilder(); sb.Append(" 0) + { sb.Append($" class=\"{string.Join('\x20', Classes)}\""); + } if (Styles.Count > 0) + { sb.Append( $" style=\"{string.Join('\x20', Styles.Select((s) => $"{s.Key}: {s.Value};"))}\"" ); + } sb.Append('>'); sb.Append(Text); diff --git a/src/Serein.Lite/Program.cs b/src/Serein.Lite/Program.cs index 12aeacef..4ecff5f2 100644 --- a/src/Serein.Lite/Program.cs +++ b/src/Serein.Lite/Program.cs @@ -58,7 +58,9 @@ public static void Main() // InvalidOperationException is thrown when updating binding source in another thread in debug mode // https://github.com/dotnet/winforms/issues/8582 if (Debugger.IsAttached) + { Control.CheckForIllegalCrossThreadCalls = false; + } #endif AppDomain.CurrentDomain.UnhandledException += (_, e) => diff --git a/src/Serein.Lite/ResourcesManager.cs b/src/Serein.Lite/ResourcesManager.cs index bb8f7017..dbea56d5 100644 --- a/src/Serein.Lite/ResourcesManager.cs +++ b/src/Serein.Lite/ResourcesManager.cs @@ -40,7 +40,9 @@ public void WriteConsoleHtml() private void WriteHtml() { if (_consoleHtml is null) + { throw new InvalidDataException($"{IndexHtml} 未找到"); + } lock (_lock) { @@ -55,7 +57,9 @@ private void WriteHtml() private void WriteCss() { if (_consoleCss is null) + { throw new InvalidDataException($"{CustomCss} 未找到"); + } lock (_lock) { diff --git a/src/Serein.Lite/Services/Loggers/ConnectionLogger.cs b/src/Serein.Lite/Services/Loggers/ConnectionLogger.cs index 0f00857e..ddcdba19 100644 --- a/src/Serein.Lite/Services/Loggers/ConnectionLogger.cs +++ b/src/Serein.Lite/Services/Loggers/ConnectionLogger.cs @@ -40,10 +40,12 @@ public void LogReceivedMessage(string line) _connectionPage.Invoke(() => { lock (_lock) + { _connectionPage.ConsoleWebBrowser.AppendHtmlLine( "[↓]" + LogColorizer.EscapeLog(line) ); + } }); } @@ -52,10 +54,12 @@ public void LogReceivedData(string data) _connectionPage.Invoke(() => { lock (_lock) + { _connectionPage.ConsoleWebBrowser.AppendHtmlLine( "[↓]" + LogColorizer.EscapeLog(data) ); + } }); } @@ -64,10 +68,12 @@ public void LogSentPacket(string line) _connectionPage.Invoke(() => { lock (_lock) + { _connectionPage.ConsoleWebBrowser.AppendHtmlLine( "[↑]" + LogColorizer.EscapeLog(line) ); + } }); } @@ -76,10 +82,12 @@ public void LogSentData(string data) _connectionPage.Invoke(() => { lock (_lock) + { _connectionPage.ConsoleWebBrowser.AppendHtmlLine( "[↑]" + LogColorizer.EscapeLog(data) ); + } }); } } diff --git a/src/Serein.Lite/Services/Loggers/NotificationLogger.cs b/src/Serein.Lite/Services/Loggers/NotificationLogger.cs index 3a773983..951389b2 100644 --- a/src/Serein.Lite/Services/Loggers/NotificationLogger.cs +++ b/src/Serein.Lite/Services/Loggers/NotificationLogger.cs @@ -36,7 +36,9 @@ public void Log( { Messages.Add((logLevel, formatter(state, exception))); if (Messages.Count > 50) + { Messages.RemoveAt(0); + } Debug.WriteLine($"[{logLevel}] {formatter(state, exception)}"); } diff --git a/src/Serein.Lite/Services/Loggers/PluginLogger.cs b/src/Serein.Lite/Services/Loggers/PluginLogger.cs index b2613893..c6e722c0 100644 --- a/src/Serein.Lite/Services/Loggers/PluginLogger.cs +++ b/src/Serein.Lite/Services/Loggers/PluginLogger.cs @@ -21,6 +21,7 @@ public void Log(LogLevel level, string name, string message) Debug.WriteLine($"[Plugin::{(string.IsNullOrEmpty(name) ? "Serein" : name)}] [{level}] {message}"); if (_pluginPage.Value.IsHandleCreated) + { _pluginPage.Value.Invoke(() => { lock (_lock) @@ -48,5 +49,6 @@ public void Log(LogLevel level, string name, string message) } } }); + } } } diff --git a/src/Serein.Lite/Ui/Controls/ConsoleWebBrowser.cs b/src/Serein.Lite/Ui/Controls/ConsoleWebBrowser.cs index ca3e457b..16be03a9 100644 --- a/src/Serein.Lite/Ui/Controls/ConsoleWebBrowser.cs +++ b/src/Serein.Lite/Ui/Controls/ConsoleWebBrowser.cs @@ -18,7 +18,9 @@ public ConsoleWebBrowser() public void AppendHtmlLine(string html) { if (Document?.Body?.InnerHtml is null) + { Refresh(); + } Document?.InvokeScript("appendText", [html]); } diff --git a/src/Serein.Lite/Ui/Function/ConnectionPage.cs b/src/Serein.Lite/Ui/Function/ConnectionPage.cs index 94b99005..3dd8e401 100644 --- a/src/Serein.Lite/Ui/Function/ConnectionPage.cs +++ b/src/Serein.Lite/Ui/Function/ConnectionPage.cs @@ -37,7 +37,9 @@ public ConnectionPage(WsConnectionManager wsConnectionManager) e.PropertyName == nameof(_wsConnectionManager.Active) || e.PropertyName == nameof(_wsConnectionManager.ConnectedAt) ) + { Invoke(UpadteInfo); + } }; } diff --git a/src/Serein.Lite/Ui/Function/MatchEditor.cs b/src/Serein.Lite/Ui/Function/MatchEditor.cs index b2a3644e..8893a5bd 100644 --- a/src/Serein.Lite/Ui/Function/MatchEditor.cs +++ b/src/Serein.Lite/Ui/Function/MatchEditor.cs @@ -32,7 +32,9 @@ private void FieldType_SelectedIndexChanged(object sender, EventArgs e) { RequireAdminCheckBox.Enabled = FieldTypeComboBox.SelectedIndex is 3 or 4; if (!RequireAdminCheckBox.Enabled) + { RequireAdminCheckBox.Checked = false; + } } private void ConfirmButton_Click(object sender, EventArgs e) @@ -56,8 +58,11 @@ private void Regex_Validating(object sender, CancelEventArgs e) { RegexErrorProvider.Clear(); if (string.IsNullOrEmpty(RegexTextBox.Text)) + { RegexErrorProvider.SetError(RegexTextBox, "正则内容为空"); + } else + { try { _ = new Regex(RegexTextBox.Text); @@ -66,6 +71,7 @@ private void Regex_Validating(object sender, CancelEventArgs e) { RegexErrorProvider.SetError(RegexTextBox, "正则语法不正确:\r\n" + ex.Message); } + } } private void CommandTextBox_Enter(object sender, EventArgs e) @@ -77,8 +83,11 @@ private void CommandTextBox_Validating(object sender, CancelEventArgs e) { CommandErrorProvider.Clear(); if (string.IsNullOrEmpty(CommandTextBox.Text)) + { CommandErrorProvider.SetError(CommandTextBox, "命令内容为空"); + } else + { try { CommandParser.Parse(CommandOrigin.Null, CommandTextBox.Text, true); @@ -87,5 +96,6 @@ private void CommandTextBox_Validating(object sender, CancelEventArgs e) { CommandErrorProvider.SetError(CommandTextBox, ex.Message); } + } } } diff --git a/src/Serein.Lite/Ui/Function/MatchPage.cs b/src/Serein.Lite/Ui/Function/MatchPage.cs index 9e4c8460..9861621a 100644 --- a/src/Serein.Lite/Ui/Function/MatchPage.cs +++ b/src/Serein.Lite/Ui/Function/MatchPage.cs @@ -51,6 +51,7 @@ private void LoadData() MatchListView.Items.Clear(); lock (_matchesProvider.Value) + { foreach (var match in _matchesProvider.Value) { var item = new ListViewItem(match.RegExp) { Tag = match }; @@ -68,6 +69,7 @@ match.FieldType is MatchFieldType.GroupMsg or MatchFieldType.PrivateMsg MatchListView.Items.Add(item); } + } MatchListView.EndUpdate(); UpdateText(); } @@ -104,7 +106,9 @@ private void ContextMenuStrip_Opening(object sender, CancelEventArgs e) private void MatchListView_KeyDown(object sender, KeyEventArgs e) { if (!e.Control || MatchListView.Items.Count <= 1 || MatchListView.SelectedItems.Count != 1) + { return; + } ListViewItem item; var i = MatchListView.SelectedItems[0].Index; @@ -164,7 +168,9 @@ private void EditToolStripMenuItem_Click(object sender, EventArgs e) private void DeleteToolStripMenuItem_Click(object sender, EventArgs e) { if (!MessageBoxHelper.ShowDeleteConfirmation("确定要删除所选项吗?")) + { return; + } foreach (var item in MatchListView.SelectedItems.Cast()) { @@ -177,7 +183,9 @@ private void DeleteToolStripMenuItem_Click(object sender, EventArgs e) private void ClearToolStripMenuItem_Click(object sender, EventArgs e) { if (!MessageBoxHelper.ShowDeleteConfirmation("确定要删除所有项吗?")) + { return; + } _matchesProvider.Value.Clear(); _matchesProvider.SaveAsyncWithDebounce(); diff --git a/src/Serein.Lite/Ui/Function/PluginPage.cs b/src/Serein.Lite/Ui/Function/PluginPage.cs index c734b5b6..39ca59c2 100644 --- a/src/Serein.Lite/Ui/Function/PluginPage.cs +++ b/src/Serein.Lite/Ui/Function/PluginPage.cs @@ -104,9 +104,13 @@ private void DisableToolStripMenuItem_Click(object sender, EventArgs e) private void ReloadToolStripMenuItem_Click(object sender, EventArgs e) { if (!_pluginManager.Loading && !_pluginManager.Reloading) + { Task.Run(_pluginManager.Reload); + } else + { MessageBoxHelper.ShowWarningMsgBox("正在加载插件中"); + } } private void ClearToolStripMenuItem_Click(object sender, EventArgs e) @@ -122,9 +126,13 @@ private void LookUpDocsToolStripMenuItem_Click(object sender, EventArgs e) private void ReloadButton_Click(object sender, EventArgs e) { if (!_pluginManager.Loading && !_pluginManager.Reloading) + { Task.Run(_pluginManager.Reload); + } else + { MessageBoxHelper.ShowWarningMsgBox("正在加载插件中"); + } } private void ClearConsoleButton_Click(object sender, EventArgs e) diff --git a/src/Serein.Lite/Ui/Function/ScheduleEditor.cs b/src/Serein.Lite/Ui/Function/ScheduleEditor.cs index 1796daf8..24be8a8c 100644 --- a/src/Serein.Lite/Ui/Function/ScheduleEditor.cs +++ b/src/Serein.Lite/Ui/Function/ScheduleEditor.cs @@ -6,7 +6,6 @@ using NCrontab; using Serein.Core.Models.Commands; -using Serein.Core.Services; using Serein.Core.Services.Commands; namespace Serein.Lite.Ui.Function; @@ -80,8 +79,11 @@ private void CommandTextBox_Validating(object sender, CancelEventArgs e) { CommandErrorProvider.Clear(); if (string.IsNullOrEmpty(CommandTextBox.Text)) + { CommandErrorProvider.SetError(CommandTextBox, "命令内容为空"); + } else + { try { CommandParser.Parse(CommandOrigin.Null, CommandTextBox.Text, true); @@ -90,6 +92,7 @@ private void CommandTextBox_Validating(object sender, CancelEventArgs e) { CommandErrorProvider.SetError(CommandTextBox, ex.Message); } + } } private void ConfirmButton_Click(object sender, EventArgs e) diff --git a/src/Serein.Lite/Ui/Function/SchedulePage.cs b/src/Serein.Lite/Ui/Function/SchedulePage.cs index 4b0574ef..245c19ab 100644 --- a/src/Serein.Lite/Ui/Function/SchedulePage.cs +++ b/src/Serein.Lite/Ui/Function/SchedulePage.cs @@ -60,6 +60,7 @@ private void LoadData() ScheduleListView.Items.Clear(); lock (_scheduleProvider.Value) + { foreach (var schedule in _scheduleProvider.Value) { var item = new ListViewItem(schedule.Expression) { Tag = schedule }; @@ -69,6 +70,7 @@ private void LoadData() ScheduleListView.Items.Add(item); } + } ScheduleListView.EndUpdate(); UpdateText(); } @@ -80,7 +82,9 @@ private void ScheduleListView_KeyDown(object sender, KeyEventArgs e) || ScheduleListView.Items.Count <= 1 || ScheduleListView.SelectedItems.Count != 1 ) + { return; + } ListViewItem item; var i = ScheduleListView.SelectedItems[0].Index; @@ -147,7 +151,9 @@ is Schedule schedule private void DeleteToolStripMenuItem_Click(object sender, EventArgs e) { if (!MessageBoxHelper.ShowDeleteConfirmation("确定要删除所选项吗?")) + { return; + } foreach (var item in ScheduleListView.SelectedItems.Cast()) { @@ -160,7 +166,9 @@ private void DeleteToolStripMenuItem_Click(object sender, EventArgs e) private void ClearToolStripMenuItem_Click(object sender, EventArgs e) { if (!MessageBoxHelper.ShowDeleteConfirmation("确定要删除所有项吗?")) + { return; + } _scheduleProvider.Value.Clear(); _scheduleProvider.SaveAsyncWithDebounce(); diff --git a/src/Serein.Lite/Ui/MainForm.cs b/src/Serein.Lite/Ui/MainForm.cs index 9f29b1b4..15482cdc 100644 --- a/src/Serein.Lite/Ui/MainForm.cs +++ b/src/Serein.Lite/Ui/MainForm.cs @@ -59,17 +59,25 @@ UpdateChecker updateChecker _resourcesManager = resourcesManager; _updateChecker = updateChecker; if (!File.Exists(ResourcesManager.IndexPath)) + { _resourcesManager.WriteConsoleHtml(); + } _timer = new(2000); _timer.Elapsed += (_, _) => Invoke(UpdateTitle); _settingProvider.Value.Application.PropertyChanged += (_, e) => { if (e.PropertyName == nameof(ApplicationSetting.CustomTitle)) + { if (InvokeRequired) + { Invoke(UpdateTitle); + } else + { UpdateTitle(); + } + } }; InitializeComponent(); @@ -107,7 +115,9 @@ private void SwitchPage() ChildrenPanel.Controls.Add(page); if (page is IUpdateablePage updateablePage) + { updateablePage.UpdatePage(); + } } private void ServerConsoleToolStripMenuItem_Click(object sender, EventArgs e) @@ -162,19 +172,25 @@ private void ServerToolStripMenuItem_DropDownOpening(object sender, EventArgs e) private void ServerAddToolStripMenuItem_Click(object sender, EventArgs e) { if (ChildrenPanel.Controls[0].GetType() == typeof(ServerPage)) + { SwitchPage(); + } var configuration = new Configuration(); var editor = new ConfigurationEditor(_serverManager, configuration); if (editor.ShowDialog() == DialogResult.OK) + { _serverManager.Add(editor.Id, configuration); + } } private void ServerImportToolStripMenuItem_Click(object sender, EventArgs e) { if (ChildrenPanel.Controls[0].GetType() == typeof(ServerPage)) + { SwitchPage(); + } var openFileDialog = new OpenFileDialog { @@ -186,7 +202,9 @@ private void ServerImportToolStripMenuItem_Click(object sender, EventArgs e) openFileDialog.ShowDialog() != DialogResult.OK || string.IsNullOrEmpty(openFileDialog.FileName) ) + { return; + } Configuration configuration; @@ -203,7 +221,9 @@ private void ServerImportToolStripMenuItem_Click(object sender, EventArgs e) var editor = new ConfigurationEditor(_serverManager, configuration); if (editor.ShowDialog() == DialogResult.OK) + { _serverManager.Add(editor.Id, configuration); + } } private void ServerEditToolStripMenuItem_Click(object sender, EventArgs e) @@ -211,14 +231,17 @@ private void ServerEditToolStripMenuItem_Click(object sender, EventArgs e) var serverPage = Services.GetRequiredService(); if (serverPage.MainTabControl.Controls.Count == 0) + { return; + } var panel = serverPage.MainTabControl.Controls[serverPage.MainTabControl.SelectedIndex]; var id = panel.Tag?.ToString(); if (string.IsNullOrEmpty(id) || !_serverManager.Servers.TryGetValue(id, out var server)) + { return; - + } var editor = new ConfigurationEditor(_serverManager, server.Configuration, id); editor.ShowDialog(); @@ -236,7 +259,9 @@ private void ServerRemoveToolStripMenuItem_Click(object sender, EventArgs e) serverPage.MainTabControl.Controls.Count == 0 || serverPage.MainTabControl.SelectedIndex == -1 ) + { return; + } var id = serverPage .MainTabControl.Controls[serverPage.MainTabControl.SelectedIndex] @@ -246,7 +271,9 @@ private void ServerRemoveToolStripMenuItem_Click(object sender, EventArgs e) !string.IsNullOrEmpty(id) && MessageBoxHelper.ShowDeleteConfirmation($"确定要删除此服务器配置({id})吗?") ) + { _serverManager.Remove(id); + } } private void ExitToolStripMenuItem_Click(object sender, EventArgs e) @@ -322,10 +349,14 @@ protected override void OnShown(EventArgs e) SwitchPage(); if (SereinAppBuilder.StartForTheFirstTime) + { DialogFactory.ShowWelcomeDialog(); + } if (FileLoggerProvider.IsEnabled) + { DialogFactory.ShowWarningDialogOfLogMode(); + } _timer.Start(); base.OnShown(e); diff --git a/src/Serein.Lite/Ui/Members/PermissionGroupEditor.cs b/src/Serein.Lite/Ui/Members/PermissionGroupEditor.cs index 0e4e8566..aaf282f1 100644 --- a/src/Serein.Lite/Ui/Members/PermissionGroupEditor.cs +++ b/src/Serein.Lite/Ui/Members/PermissionGroupEditor.cs @@ -57,9 +57,13 @@ private void SyncData() var item = new ListViewItem(kv.Key) { Tag = kv }; if (_permissionManager.Nodes.TryGetValue(kv.Key, out var description)) + { item.SubItems.Add(description); + } else + { item.SubItems.Add(string.Empty); + } item.SubItems.Add(kv.Value is not null ? kv.Value.ToString() : "Null"); PermissionListView.Items.Add(item); @@ -71,8 +75,12 @@ private void PermissionComboBox_DropDown(object sender, EventArgs e) PermissionComboBox.Items.Clear(); lock (_permissionManager.Nodes) + { foreach (var key in _permissionManager.Nodes.Keys) + { PermissionComboBox.Items.Add(key); + } + } } private void PermissionListView_SelectedIndexChanged(object sender, EventArgs e) @@ -108,7 +116,9 @@ out var description private void ValueComboBox_SelectedIndexChanged(object sender, EventArgs e) { if (PermissionListView.SelectedItems.Count == 1) + { PermissionListView.SelectedItems[0].SubItems[2].Text = ValueComboBox.Text; + } } private void PermissionContextMenuStrip_Opening(object sender, CancelEventArgs e) @@ -123,26 +133,35 @@ private void AddPermissionToolStripMenuItem_Click(object sender, EventArgs e) item.SubItems.Add("Null"); if (PermissionListView.SelectedItems.Count == 1) + { PermissionListView.Items.Insert(PermissionListView.SelectedItems[0].Index, item); + } else + { PermissionListView.Items.Add(item); + } } private void DeletePermissionToolStripMenuItem_Click(object sender, EventArgs e) { if (PermissionListView.SelectedItems.Count == 1) + { PermissionListView.Items.RemoveAt(PermissionListView.SelectedItems[0].Index); + } } private void ConfirmButton_Click(object sender, EventArgs e) { lock (_group) + { try { GroupManager.ValidateGroupId(Id); if (!IdTextBox.ReadOnly && _groupManager.Ids.Contains(Id)) + { throw new InvalidOperationException("此Id已被占用"); + } _group.Name = NameTextBox.Text; _group.Description = DescriptionTextBox.Text; @@ -156,23 +175,29 @@ private void ConfirmButton_Click(object sender, EventArgs e) foreach (var item in PermissionListView.Items) { if (item is ListViewItem listViewItem) + { permissions[listViewItem.Text] = listViewItem.SubItems[2].Text switch { "True" => true, "False" => false, _ => null }; + } } _group.Nodes = permissions; var list = new List(); foreach (var item in MemberListView.Items) + { if ( item is ListViewItem listViewItem && long.TryParse(listViewItem.Text, out var id) && !list.Contains(id) ) + { list.Add(id); + } + } _group.Members = [.. list]; DialogResult = DialogResult.OK; @@ -182,6 +207,7 @@ item is ListViewItem listViewItem { MessageBoxHelper.ShowWarningMsgBox(ex.Message); } + } } private void MemberContextMenuStrip_Opening(object sender, CancelEventArgs e) @@ -192,15 +218,21 @@ private void MemberContextMenuStrip_Opening(object sender, CancelEventArgs e) private void AddMemberToolStripMenuItem_Click(object sender, EventArgs e) { if (MemberListView.SelectedItems.Count == 1) + { MemberListView.Items.Insert(MemberListView.SelectedItems[0].Index, "0"); + } else + { MemberListView.Items.Add("0"); + } } private void DeleteMemberToolStripMenuItem_Click(object sender, EventArgs e) { if (MemberListView.SelectedItems.Count == 1) + { MemberListView.Items.RemoveAt(MemberListView.SelectedItems[0].Index); + } } private void IdTextBox_Enter(object sender, EventArgs e) @@ -211,11 +243,17 @@ private void IdTextBox_Enter(object sender, EventArgs e) private void IdTextBox_Validating(object sender, CancelEventArgs e) { if (string.IsNullOrEmpty(IdTextBox.Text)) + { ErrorProvider.SetError(IdTextBox, "Id不能为空"); + } else if (!IdRegex().IsMatch(IdTextBox.Text)) + { ErrorProvider.SetError(IdTextBox, "Id只能由字母、数字和下划线组成"); + } else if (IdTextBox.Text.Length <= 2) + { ErrorProvider.SetError(IdTextBox, "Id长度太短"); + } } private void ParentsTextBox_Enter(object sender, EventArgs e) @@ -233,9 +271,11 @@ private void ParentsTextBox_Validating(object sender, CancelEventArgs e) .Where((id) => !_groupManager.Ids.Contains(id)); if (nonExistent.Any()) + { ErrorProvider.SetError( ParentsTextBox, $"以下权限组不存在:\r\n" + string.Join("\r\n", nonExistent) ); + } } } diff --git a/src/Serein.Lite/Ui/Members/PermissionGroupPage.cs b/src/Serein.Lite/Ui/Members/PermissionGroupPage.cs index a0070fe9..e3ea86bd 100644 --- a/src/Serein.Lite/Ui/Members/PermissionGroupPage.cs +++ b/src/Serein.Lite/Ui/Members/PermissionGroupPage.cs @@ -44,6 +44,7 @@ private void LoadData() GroupListView.Items.Clear(); lock (_permissionGroupProvider.Value) + { foreach (var kv in _permissionGroupProvider.Value) { var item = new ListViewItem(kv.Key) { Tag = kv }; @@ -53,7 +54,7 @@ private void LoadData() GroupListView.Items.Add(item); } - + } GroupListView.EndUpdate(); } @@ -64,7 +65,9 @@ private void AddToolStripMenuItem_Click(object sender, EventArgs e) var group = new Group(); var dialog = new PermissionGroupEditor(_groupManager, _permissionManager, group); if (dialog.ShowDialog() != DialogResult.OK) + { return; + } _permissionGroupProvider.Value.TryAdd(dialog.Id, group); _permissionGroupProvider.SaveAsyncWithDebounce(); @@ -78,11 +81,15 @@ private void EditToolStripMenuItem_Click(object sender, EventArgs e) GroupListView.SelectedItems.Count != 1 || GroupListView.SelectedItems[0].Tag is not KeyValuePair kv ) + { return; + } var dialog = new PermissionGroupEditor(_groupManager, _permissionManager, kv.Value, kv.Key); if (dialog.ShowDialog() != DialogResult.OK) + { return; + } _permissionGroupProvider.SaveAsyncWithDebounce(); LoadData(); @@ -90,8 +97,13 @@ private void EditToolStripMenuItem_Click(object sender, EventArgs e) private void DeleteToolStripMenuItem_Click(object sender, EventArgs e) { - if (GroupListView.SelectedItems.Count != 1 || !MessageBoxHelper.ShowDeleteConfirmation("你确定要删除所选项吗?")) + if ( + GroupListView.SelectedItems.Count != 1 + || !MessageBoxHelper.ShowDeleteConfirmation("你确定要删除所选项吗?") + ) + { return; + } _permissionGroupProvider.Value.Remove(GroupListView.SelectedItems[0].Text); _permissionGroupProvider.SaveAsyncWithDebounce(); diff --git a/src/Serein.Lite/Ui/Servers/ConfigurationEditor.cs b/src/Serein.Lite/Ui/Servers/ConfigurationEditor.cs index e041691c..ebcb33da 100644 --- a/src/Serein.Lite/Ui/Servers/ConfigurationEditor.cs +++ b/src/Serein.Lite/Ui/Servers/ConfigurationEditor.cs @@ -67,16 +67,24 @@ private void ConfirmButton_Click(object sender, EventArgs e) ServerManager.ValidateId(IdTextBox.Text); if (!IdTextBox.ReadOnly && _serverManager.Servers.ContainsKey(Id)) + { throw new InvalidOperationException("此Id已被占用"); + } if (InputEncondingComboBox.SelectedIndex < 0) + { InputEncondingComboBox.SelectedIndex = 0; + } if (OutputEncondingComboBox.SelectedIndex < 0) + { OutputEncondingComboBox.SelectedIndex = 0; + } if (OutputStyleComboBox.SelectedIndex < 0) + { OutputStyleComboBox.SelectedIndex = 0; + } input = (EncodingMap.EncodingType)InputEncondingComboBox.SelectedIndex; output = (EncodingMap.EncodingType)OutputEncondingComboBox.SelectedIndex; @@ -117,11 +125,17 @@ private void IdTextBox_Enter(object sender, EventArgs e) private void IdTextBox_Validating(object sender, CancelEventArgs e) { if (string.IsNullOrEmpty(IdTextBox.Text)) + { ErrorProvider.SetError(IdTextBox, "Id不能为空"); + } else if (!IdRegex().IsMatch(IdTextBox.Text)) + { ErrorProvider.SetError(IdTextBox, "Id只能由字母、数字和下划线组成"); + } else if (IdTextBox.Text.Length <= 2) + { ErrorProvider.SetError(IdTextBox, "Id长度太短"); + } } [GeneratedRegex(@"^\w+$")] @@ -132,6 +146,8 @@ private void OpenFileButton_Click(object sender, EventArgs e) var openFileDialog = new OpenFileDialog { Title = "选择启动文件" }; if (openFileDialog.ShowDialog() == DialogResult.OK && !string.IsNullOrEmpty(openFileDialog.FileName)) + { FileNameTextBox.Text = openFileDialog.FileName; + } } } diff --git a/src/Serein.Lite/Ui/Servers/Panel.cs b/src/Serein.Lite/Ui/Servers/Panel.cs index ccabd3b7..1fd775cc 100644 --- a/src/Serein.Lite/Ui/Servers/Panel.cs +++ b/src/Serein.Lite/Ui/Servers/Panel.cs @@ -39,7 +39,9 @@ public Panel(Server server, MainForm mainForm) _timer.Start(); } else + { _timer.Stop(); + } }; _pluginManagerForm = new(() => new(_server)); @@ -53,25 +55,35 @@ private void OnServerOutput(object? sender, ServerOutputEventArgs e) { case ServerOutputType.Raw: lock (_lock) + { ConsoleBrowser.AppendHtmlLine( - LogColorizer.ColorLine(e.Data, _server.Configuration.OutputStyle) - ); + LogColorizer.ColorLine(e.Data, _server.Configuration.OutputStyle) + ); + } break; case ServerOutputType.InputCommand: if (_server.Configuration.OutputCommandUserInput) + { lock (_lock) + { ConsoleBrowser.AppendHtmlLine($">{LogColorizer.EscapeLog(e.Data)}"); + } + } break; case ServerOutputType.Information: lock (_lock) + { ConsoleBrowser.AppendNotice(e.Data); + } break; case ServerOutputType.Error: lock (_lock) + { ConsoleBrowser.AppendError(e.Data); + } break; default: @@ -144,13 +156,17 @@ private void InputTextBox_KeyDown(object sender, KeyEventArgs e) case Keys.Up: if (_server.CommandHistoryIndex > 0) + { _server.CommandHistoryIndex--; + } if ( _server.CommandHistoryIndex >= 0 && _server.CommandHistoryIndex < _server.CommandHistory.Count ) + { InputTextBox.Text = _server.CommandHistory[_server.CommandHistoryIndex]; + } e.Handled = true; InputTextBox.SelectionStart = InputTextBox.Text.Length; @@ -158,18 +174,24 @@ private void InputTextBox_KeyDown(object sender, KeyEventArgs e) case Keys.Down: if (_server.CommandHistoryIndex < _server.CommandHistory.Count) + { _server.CommandHistoryIndex++; + } if ( _server.CommandHistoryIndex >= 0 && _server.CommandHistoryIndex < _server.CommandHistory.Count ) + { InputTextBox.Text = _server.CommandHistory[_server.CommandHistoryIndex]; + } else if ( _server.CommandHistoryIndex == _server.CommandHistory.Count && _server.CommandHistory.Count != 0 ) + { InputTextBox.Text = string.Empty; + } e.Handled = true; InputTextBox.SelectionStart = InputTextBox.Text.Length; @@ -220,9 +242,13 @@ private void StartPluginManagerButton_Click(object sender, EventArgs e) private void OpenDirectoryButton_Click(object sender, EventArgs e) { if (File.Exists(_server.Configuration.FileName)) + { _server.Configuration.FileName.OpenInExplorer(); + } else + { MessageBoxHelper.ShowWarningMsgBox("启动文件不存在,无法打开其所在文件夹"); + } } private void Panel_DragEnter(object sender, DragEventArgs e) @@ -251,6 +277,7 @@ private void Panel_DragDrop(object sender, DragEventArgs e) + string.Join("\r\n", files.Select((f) => Path.GetFileName(f))) ) ) + { try { _server.PluginManager.Add(acceptable.ToArray()); @@ -259,6 +286,7 @@ private void Panel_DragDrop(object sender, DragEventArgs e) { MessageBoxHelper.ShowWarningMsgBox("导入失败:\r\n" + ex.Message); } + } } } } diff --git a/src/Serein.Lite/Ui/Servers/PluginManagerForm.cs b/src/Serein.Lite/Ui/Servers/PluginManagerForm.cs index cf8c606d..cbeaebce 100644 --- a/src/Serein.Lite/Ui/Servers/PluginManagerForm.cs +++ b/src/Serein.Lite/Ui/Servers/PluginManagerForm.cs @@ -44,8 +44,12 @@ private void LoadData() PluginListView.Items.Clear(); foreach (var group in PluginListView.Groups) + { if (group is ListViewGroup listViewGroup) + { listViewGroup.Items.Clear(); + } + } foreach (var plugin in _server.PluginManager.Plugins) { @@ -56,9 +60,13 @@ private void LoadData() }; if (plugin.FileInfo.Exists) + { item.SubItems.Add(plugin.FileInfo.Length.ToSizeString()); + } else + { item.SubItems.Add("-"); + } if (!plugin.IsEnabled) { @@ -132,6 +140,7 @@ private void AddToolStripMenuItem_Click(object sender, EventArgs e) { var openFileDialog = new OpenFileDialog { Filter = "可接受的插件文件|*.dll;*.jar;*.js;*.py;*.lua" }; if (openFileDialog.ShowDialog() == DialogResult.OK) + { try { _server.PluginManager.Add(openFileDialog.FileNames); @@ -140,6 +149,7 @@ private void AddToolStripMenuItem_Click(object sender, EventArgs e) { MessageBoxHelper.ShowWarningMsgBox("导入失败:\r\n" + ex.Message); } + } _server.PluginManager.Update(); LoadData(); @@ -149,7 +159,9 @@ private void EnableToolStripMenuItem_Click(object sender, EventArgs e) { var selectedPlugins = SelectedPlugins; foreach (var plugin in selectedPlugins) + { if (plugin is not null) + { try { _server.PluginManager.Enable(plugin); @@ -161,6 +173,8 @@ private void EnableToolStripMenuItem_Click(object sender, EventArgs e) ); break; } + } + } _server.PluginManager.Update(); LoadData(); @@ -170,7 +184,9 @@ private void DisableToolStripMenuItem_Click(object sender, EventArgs e) { var selectedPlugins = SelectedPlugins; foreach (var plugin in selectedPlugins) + { if (plugin is not null) + { try { _server.PluginManager.Disable(plugin); @@ -182,7 +198,8 @@ private void DisableToolStripMenuItem_Click(object sender, EventArgs e) ); break; } - + } + } _server.PluginManager.Update(); LoadData(); } @@ -200,10 +217,14 @@ private void RemoveToolStripMenuItem_Click(object sender, EventArgs e) : $"确定要删除\"{selectedPlugins.First()?.FriendlyName}\"等{count}个插件吗?" ) ) + { return; + } foreach (var plugin in selectedPlugins) + { if (plugin is not null) + { try { _server.PluginManager.Remove(plugin); @@ -215,6 +236,8 @@ private void RemoveToolStripMenuItem_Click(object sender, EventArgs e) ); break; } + } + } _server.PluginManager.Update(); LoadData(); @@ -246,6 +269,7 @@ private void PluginManagerForm_DragDrop(object sender, DragEventArgs e) "是否将以下文件作为插件导入到服务器的插件文件夹?\r\n" + string.Join("\r\n", files.Select((f) => Path.GetFileName(f))) )) + { try { _server.PluginManager.Add(acceptable.ToArray()); @@ -254,6 +278,7 @@ private void PluginManagerForm_DragDrop(object sender, DragEventArgs e) { MessageBoxHelper.ShowWarningMsgBox("导入失败:\r\n" + ex.Message); } + } } } diff --git a/src/Serein.Lite/Ui/Servers/ServerPage.cs b/src/Serein.Lite/Ui/Servers/ServerPage.cs index bd98bcfe..d02a15fd 100644 --- a/src/Serein.Lite/Ui/Servers/ServerPage.cs +++ b/src/Serein.Lite/Ui/Servers/ServerPage.cs @@ -25,7 +25,9 @@ public ServerPage(ServerManager serverManager, ResourcesManager resourcesManager _serverManager.ServersUpdated += Update; foreach (var (id, server) in _serverManager.Servers) + { Add(id, server); + } StatusStrip.Visible = _panels.Count == 0; } @@ -33,7 +35,9 @@ public ServerPage(ServerManager serverManager, ResourcesManager resourcesManager private void Add(string id, Server server) { if (!File.Exists(ResourcesManager.IndexPath)) + { _resourcesManager.WriteConsoleHtml(); + } var tabPage = new TabPage { @@ -56,7 +60,9 @@ private void Update(object? sender, ServersUpdatedEventArgs e) e.Type == ServersUpdatedType.Added && _serverManager.Servers.TryGetValue(e.Id, out var server) ) + { Add(e.Id, server); + } else if (_panels.TryGetValue(e.Id, out var page)) { MainTabControl.Controls.Remove(page); diff --git a/src/Serein.Lite/Ui/Settings/ConnectionSettingPage.cs b/src/Serein.Lite/Ui/Settings/ConnectionSettingPage.cs index 7f8ba1da..c69ecaf0 100644 --- a/src/Serein.Lite/Ui/Settings/ConnectionSettingPage.cs +++ b/src/Serein.Lite/Ui/Settings/ConnectionSettingPage.cs @@ -112,8 +112,12 @@ var id in GroupsTextBox.Text.Split( StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries ) ) + { if (long.TryParse(id, out long i)) + { list.Add(i); + } + } _settingProvider.Value.Connection.Groups = [.. list]; OnPropertyChanged(sender, e); @@ -128,8 +132,12 @@ var id in AdministratorsTextBox.Text.Split( StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries ) ) + { if (long.TryParse(id, out long i)) + { list.Add(i); + } + } _settingProvider.Value.Connection.Administrators = [.. list]; OnPropertyChanged(sender, e); diff --git a/src/Serein.Lite/Ui/Settings/ReactionSettingPage.cs b/src/Serein.Lite/Ui/Settings/ReactionSettingPage.cs index af279f3b..a127f3ed 100644 --- a/src/Serein.Lite/Ui/Settings/ReactionSettingPage.cs +++ b/src/Serein.Lite/Ui/Settings/ReactionSettingPage.cs @@ -18,7 +18,9 @@ public ReactionSettingPage(SettingProvider settingProvider) InitializeComponent(); foreach (var type in Enum.GetValues()) + { EventListBox.Items.Add(new DisplayedItem(type)); + } CommandListView.SetExploreTheme(); _settingProvider = settingProvider; @@ -27,7 +29,9 @@ public ReactionSettingPage(SettingProvider settingProvider) private void SaveData() { if (EventListBox.SelectedItem is not DisplayedItem displayedItem) + { return; + } _settingProvider.Value.Reactions[displayedItem.Type] = CommandListView .Items.Cast() @@ -47,12 +51,16 @@ EventListBox.SelectedItem is DisplayedItem displayedItem CommandListView.Items.Clear(); foreach (var command in commands) + { CommandListView.Items.Add(command); + } CommandListView.EndUpdate(); } else + { CommandListView.Items.Clear(); + } } private void CommandListView_AfterLabelEdit(object sender, LabelEditEventArgs e) @@ -68,10 +76,13 @@ private void ContextMenuStrip_Opening(object sender, CancelEventArgs e) private void AddToolStripMenuItem_Click(object sender, EventArgs e) { if (CommandListView.SelectedItems.Count == 1) + { CommandListView.Items.Insert(CommandListView.SelectedItems[0].Index, string.Empty); + } else + { CommandListView.Items.Add(string.Empty); - + } SaveData(); } diff --git a/src/Serein.Lite/Ui/Settings/WebApiSettingPage.cs b/src/Serein.Lite/Ui/Settings/WebApiSettingPage.cs index f89ecb15..c7555b8b 100644 --- a/src/Serein.Lite/Ui/Settings/WebApiSettingPage.cs +++ b/src/Serein.Lite/Ui/Settings/WebApiSettingPage.cs @@ -134,9 +134,13 @@ private void EnableCheckBox_Click(object sender, EventArgs e) try { if (EnableCheckBox.Checked) + { _httpServer.Start(); + } else + { _httpServer.Stop(); + } } catch (Exception ex) { @@ -149,6 +153,8 @@ private void OpenFileButton_Click(object sender, EventArgs e) var dialog = new OpenFileDialog { Title = "选择证书文件" }; if (dialog.ShowDialog() == DialogResult.OK && !string.IsNullOrEmpty(dialog.FileName)) + { PathTextBox.Text = dialog.FileName; + } } } diff --git a/src/Serein.Plus/App.xaml.cs b/src/Serein.Plus/App.xaml.cs index 29a526c1..8bd4eea6 100644 --- a/src/Serein.Plus/App.xaml.cs +++ b/src/Serein.Plus/App.xaml.cs @@ -118,9 +118,13 @@ private static void ShowErrorDialog(Exception e) var btn = dialog.ShowDialog(); if (btn == button1) + { UrlConstants.Issues.OpenInBrowser(); + } else if (btn == button2) + { UrlConstants.Group.OpenInBrowser(); + } } catch { } } diff --git a/src/Serein.Plus/Controls/PanelTabItem.xaml.cs b/src/Serein.Plus/Controls/PanelTabItem.xaml.cs index bd38dad1..9bd54d74 100644 --- a/src/Serein.Plus/Controls/PanelTabItem.xaml.cs +++ b/src/Serein.Plus/Controls/PanelTabItem.xaml.cs @@ -80,7 +80,9 @@ private void Output(object? sender, ServerOutputEventArgs e) case ServerOutputType.InputCommand: if (Server.Configuration.OutputCommandUserInput) + { Dispatcher.Invoke(() => Console.AppendLine($">{e.Data}")); + } break; case ServerOutputType.Information: @@ -120,9 +122,13 @@ private void ControlButton_Click(object sender, RoutedEventArgs e) case "OpenInExplorer": if (File.Exists(Server.Configuration.FileName)) + { Server.Configuration.FileName.OpenInExplorer(); + } else + { throw new InvalidOperationException("启动文件不存在,无法打开其所在文件夹"); + } break; case "PluginManager": @@ -154,13 +160,17 @@ private void InputBox_PreviewKeyDown(object sender, KeyEventArgs e) case Key.Up: if (Server.CommandHistoryIndex > 0) + { Server.CommandHistoryIndex--; + } if ( Server.CommandHistoryIndex >= 0 && Server.CommandHistoryIndex < Server.CommandHistory.Count ) + { InputBox.Text = Server.CommandHistory[Server.CommandHistoryIndex]; + } e.Handled = true; InputBox.SelectionStart = InputBox.Text.Length; @@ -168,18 +178,24 @@ private void InputBox_PreviewKeyDown(object sender, KeyEventArgs e) case Key.Down: if (Server.CommandHistoryIndex < Server.CommandHistory.Count) + { Server.CommandHistoryIndex++; + } if ( Server.CommandHistoryIndex >= 0 && Server.CommandHistoryIndex < Server.CommandHistory.Count ) + { InputBox.Text = Server.CommandHistory[Server.CommandHistoryIndex]; + } else if ( Server.CommandHistoryIndex == Server.CommandHistory.Count && Server.CommandHistory.Count != 0 ) + { InputBox.Text = string.Empty; + } e.Handled = true; InputBox.SelectionStart = InputBox.Text.Length; @@ -237,7 +253,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) var editor = new ServerConfigurationEditor(_serverManager, config, _id); if (editor.ShowDialog() != true) + { return; + } editor.Configuration.ShallowCloneTo(Server.Configuration); Header = Server.Configuration.Name; @@ -251,6 +269,7 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) (task) => { if (task.Result) + { try { _serverManager.Remove(_id); @@ -263,6 +282,7 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) InfoBarSeverity.Error ); } + } } ); break; diff --git a/src/Serein.Plus/Converters/ServerPluginTypeKeyConverter.cs b/src/Serein.Plus/Converters/ServerPluginTypeKeyConverter.cs index 0b98dde3..f4b9758b 100644 --- a/src/Serein.Plus/Converters/ServerPluginTypeKeyConverter.cs +++ b/src/Serein.Plus/Converters/ServerPluginTypeKeyConverter.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Windows.Data; + using Serein.Core.Models.Server; namespace Serein.Plus.Converters; @@ -10,10 +11,14 @@ public class ServerPluginTypeKeyConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is PluginType pluginType) + { if (pluginType == PluginType.Library) + { return "动态链接库"; + } else return pluginType.ToString(); + } throw new NotSupportedException(); } diff --git a/src/Serein.Plus/Dialogs/PermissionEditorDialog.xaml.cs b/src/Serein.Plus/Dialogs/PermissionEditorDialog.xaml.cs index 6bf2decf..2f4276eb 100644 --- a/src/Serein.Plus/Dialogs/PermissionEditorDialog.xaml.cs +++ b/src/Serein.Plus/Dialogs/PermissionEditorDialog.xaml.cs @@ -51,13 +51,17 @@ private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTex Update(); if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + { sender.ItemsSource = _permissionManager.Nodes.Keys.Select((key) => key.Contains(Node)); + } } private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { if (args.ChosenSuggestion is not null) + { Node = args.ChosenSuggestion.ToString() ?? string.Empty; + } } private void ValueComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) diff --git a/src/Serein.Plus/MainWindow.xaml.cs b/src/Serein.Plus/MainWindow.xaml.cs index 0b0d448d..c4587df4 100644 --- a/src/Serein.Plus/MainWindow.xaml.cs +++ b/src/Serein.Plus/MainWindow.xaml.cs @@ -96,7 +96,9 @@ TitleUpdater titleUpdater private void MenuItem_Click(object sender, RoutedEventArgs e) { if (sender is not MenuItem item) + { return; + } var tag = item.Tag as string; @@ -147,22 +149,26 @@ private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_ContentRendered(object sender, EventArgs e) { if (SereinAppBuilder.StartForTheFirstTime) + { new WelcomeDialog().ShowAsync(); + } if (FileLoggerProvider.IsEnabled) + { new ContentDialog { Title = "嘿!你开启了日志模式", Content = new TextBlock { Text = - $"在此模式下,应用程序会将完整的调试日志保存在\"{PathConstants.LogDirectory}/app\"目录下(可能很大很大很大,并对硬盘的读写速度产生一定影响)\r\n" - + "除非你知道你在干什么 / 是开发者要求的,请不要在此模式下运行Serein!!\r\n\r\n" - + "当然你也不需要太担心,若要退出此模式只需要重新启动就行啦 :D", + $"在此模式下,应用程序会将完整的调试日志保存在\"{PathConstants.LogDirectory}/app\"目录下(可能很大很大很大,并对硬盘的读写速度产生一定影响)\r\n" + + "除非你知道你在干什么 / 是开发者要求的,请不要在此模式下运行Serein!!\r\n\r\n" + + "当然你也不需要太担心,若要退出此模式只需要重新启动就行啦 :D", }, CloseButtonText = "我知道了", DefaultButton = ContentDialogButton.Close, }.ShowAsync(); + } _host.StartAsync(); } @@ -179,7 +185,9 @@ private void Window_IsVisibleChanged(object sender, DependencyPropertyChangedEve TopMostMenuItem.IsEnabled = IsVisible; if (Topmost && !IsVisible) + { Topmost = false; + } } private void Window_Closing(object sender, CancelEventArgs e) @@ -226,7 +234,9 @@ public void ShowInfoBar(InfoBarTask infoBarTask) ); if (GlobalInfoBar.RenderTransform is not TranslateTransform translateTransform) + { return; + } GlobalInfoBar.IsOpen = true; translateTransform.BeginAnimation(TranslateTransform.YProperty, _infoBarPopIn); @@ -258,7 +268,9 @@ void Cancel(object sender, EventArgs e) { infoBarTask.ResetEvent.Set(); if (!cancellationTokenSource.IsCancellationRequested) + { cancellationTokenSource.Cancel(); + } } } } diff --git a/src/Serein.Plus/Models/ConsoleTextEditor/AnsiColorizer.cs b/src/Serein.Plus/Models/ConsoleTextEditor/AnsiColorizer.cs index 29acb3b8..e337bbfe 100644 --- a/src/Serein.Plus/Models/ConsoleTextEditor/AnsiColorizer.cs +++ b/src/Serein.Plus/Models/ConsoleTextEditor/AnsiColorizer.cs @@ -16,7 +16,9 @@ protected override void ColorizeLine(DocumentLine line) var text = CurrentContext.Document.GetText(line); if (!text.Contains('\x1b')) + { return; + } int mIndex = -1; @@ -31,18 +33,24 @@ protected override void ColorizeLine(DocumentLine line) for (int i = 0; i < text.Length; i++) { if (text[i] != '\x1b' || i + 1 >= text.Length || text[i + 1] != '[') + { continue; + } mIndex = text.IndexOf('m', i); if (mIndex < 0) // invalid index of 'm' + { continue; + } var args = text.Substring(i + 2, mIndex - i - 2) .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (args.Length == 0) + { continue; + } for (int j = 0; j < args.Length; j++) { @@ -237,7 +245,7 @@ protected override void ColorizeLine(DocumentLine line) case "107": background = null; break; - #endregion + #endregion } } @@ -273,24 +281,28 @@ bool reversed ) { if (foreground is not null) + { if (reversed) - element.TextRunProperties.SetBackgroundBrush( - new SolidColorBrush(foreground ?? throw new NullReferenceException()) - ); + { + element.TextRunProperties.SetBackgroundBrush(new SolidColorBrush(foreground.Value)); + } else - element.TextRunProperties.SetForegroundBrush( - new SolidColorBrush(foreground ?? throw new NullReferenceException()) - ); + { + element.TextRunProperties.SetForegroundBrush(new SolidColorBrush(foreground.Value)); + } + } if (background is not null) + { if (reversed) - element.TextRunProperties.SetForegroundBrush( - new SolidColorBrush(background ?? throw new NullReferenceException()) - ); + { + element.TextRunProperties.SetForegroundBrush(new SolidColorBrush(background.Value)); + } else - element.TextRunProperties.SetBackgroundBrush( - new SolidColorBrush(background ?? throw new NullReferenceException()) - ); + { + element.TextRunProperties.SetBackgroundBrush(new SolidColorBrush(background.Value)); + } + } if (bold || italic) { @@ -310,10 +322,14 @@ bool reversed var decorations = new TextDecorationCollection(); if (strikethrough) + { decorations.Add(TextDecorations.Strikethrough); + } if (underline) + { decorations.Add(TextDecorations.Underline); + } element.TextRunProperties.SetTextDecorations(decorations); } diff --git a/src/Serein.Plus/Models/ConsoleTextEditor/LineHeaderColorizer.cs b/src/Serein.Plus/Models/ConsoleTextEditor/LineHeaderColorizer.cs index 73debc0f..5b2fdd0d 100644 --- a/src/Serein.Plus/Models/ConsoleTextEditor/LineHeaderColorizer.cs +++ b/src/Serein.Plus/Models/ConsoleTextEditor/LineHeaderColorizer.cs @@ -18,11 +18,15 @@ protected override void ColorizeLine(DocumentLine line) var text = CurrentContext.Document.GetText(line); if (!text.Contains('[') || !text.Contains(']')) + { return; + } var match = HeaderRegex.Match(text); if (!match.Success) + { return; + } var key = match.Groups["key"].Value; @@ -39,67 +43,83 @@ protected override void ColorizeLine(DocumentLine line) case "Info": if (!_onlySereinHeader) + { action = (element) => element.TextRunProperties.SetForegroundBrush( new SolidColorBrush(Color.FromRgb(0x00, 0xaf, 0xff)) ); + } break; case "Warn": if (!_onlySereinHeader) + { action = (element) => { var tf = element.TextRunProperties.Typeface; element.TextRunProperties.SetTypeface( - new Typeface(tf.FontFamily, FontStyles.Normal, FontWeights.Bold, tf.Stretch) + new Typeface( + tf.FontFamily, + FontStyles.Normal, + FontWeights.Bold, + tf.Stretch + ) ); element.TextRunProperties.SetForegroundBrush( new SolidColorBrush(Color.FromRgb(0xaf, 0x5f, 0x00)) ); }; + } break; case "Error": if (!_onlySereinHeader) + { action = (element) => { var tf = element.TextRunProperties.Typeface; element.TextRunProperties.SetTypeface( - new Typeface(tf.FontFamily, FontStyles.Normal, FontWeights.Bold, tf.Stretch) + new Typeface( + tf.FontFamily, + FontStyles.Normal, + FontWeights.Bold, + tf.Stretch + ) ); element.TextRunProperties.SetForegroundBrush( new SolidColorBrush(Color.FromRgb(0xd7, 0x00, 0x00)) ); }; + } break; case "Recv": case "↓": if (!_onlySereinHeader) + { action = (element) => element.TextRunProperties.SetForegroundBrush( new SolidColorBrush(Color.FromRgb(0x00, 0x5f, 0xd7)) ); + } break; case "Sent": case "↑": if (!_onlySereinHeader) + { action = (element) => element.TextRunProperties.SetForegroundBrush( new SolidColorBrush(Color.FromRgb(0x5f, 0xd7, 0x00)) ); + } break; default: return; } - ChangeLinePart( - line.Offset, - line.Offset + match.Value.Length, - action - ); + ChangeLinePart(line.Offset, line.Offset + match.Value.Length, action); } private static readonly Regex HeaderRegex = GetHeaderRegex(); diff --git a/src/Serein.Plus/Pages/ConnectionPage.xaml.cs b/src/Serein.Plus/Pages/ConnectionPage.xaml.cs index b9711b9a..4647cebc 100644 --- a/src/Serein.Plus/Pages/ConnectionPage.xaml.cs +++ b/src/Serein.Plus/Pages/ConnectionPage.xaml.cs @@ -34,7 +34,9 @@ public ConnectionPage(InfoBarProvider infoBarProvider, WsConnectionManager wsCon _wsConnectionManager.PropertyChanged += (_, e) => { if (e.PropertyName == nameof(_wsConnectionManager.ConnectedAt)) + { Dispatcher.Invoke(UpdateTimeText); + } }; } @@ -52,7 +54,9 @@ private void ControlButton_Click(object sender, RoutedEventArgs e) try { if (tag == "Close") + { _wsConnectionManager.Stop(); + } else if (tag == "Open") { _wsConnectionManager.Start(); diff --git a/src/Serein.Plus/Pages/MatchPage.xaml.cs b/src/Serein.Plus/Pages/MatchPage.xaml.cs index 09b4ab06..a1c78007 100644 --- a/src/Serein.Plus/Pages/MatchPage.xaml.cs +++ b/src/Serein.Plus/Pages/MatchPage.xaml.cs @@ -67,13 +67,13 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) case "Remove": DialogHelper.ShowDeleteConfirmation("确定要删除所选项吗?").ContinueWith((task) => { - if (!task.Result) - return; + if (!task.Result){ + return;} Dispatcher.Invoke(() => { - foreach (var item in MatchesDataGrid.SelectedItems.OfType().ToArray()) - _matchesProvider.Value.Remove(item); + foreach (var item in MatchesDataGrid.SelectedItems.OfType().ToArray()){ + _matchesProvider.Value.Remove(item);} _matchesProvider.SaveAsyncWithDebounce(); }); @@ -81,8 +81,8 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) break; case "Edit": - if (MatchesDataGrid.SelectedItem is not Match m3) - return; + if (MatchesDataGrid.SelectedItem is not Match m3){ + return;} var m4 = m3.ShallowClone(); diff --git a/src/Serein.Plus/Pages/PermissionGroupPage.xaml.cs b/src/Serein.Plus/Pages/PermissionGroupPage.xaml.cs index 223dda44..44be9667 100644 --- a/src/Serein.Plus/Pages/PermissionGroupPage.xaml.cs +++ b/src/Serein.Plus/Pages/PermissionGroupPage.xaml.cs @@ -57,7 +57,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) }; if (dialog1.ShowDialog() == true) + { _groupManager.Add(dialog1.Id, dialog1.Group); + } break; @@ -70,6 +72,7 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) GroupListView.SelectedItem is KeyValuePair kv1 && kv1.Key != "everyone" ) + { DialogHelper .ShowDeleteConfirmation($"确定要删除权限组(\"{kv1.Key}\")吗?") .ContinueWith( @@ -82,6 +85,7 @@ GroupListView.SelectedItem is KeyValuePair kv1 } } ); + } break; case "Edit": @@ -110,7 +114,7 @@ GroupListView.SelectedItem is KeyValuePair kv1 private void GroupListView_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - EditMenuItem.IsEnabled = GroupListView.SelectedItems.Count == 1; - RemoveMenuItem.IsEnabled = GroupListView.SelectedItems.Count > 0 && !GroupListView.SelectedItems.OfType>().Any((kv)=>kv.Key=="everyone"); + EditMenuItem.IsEnabled = GroupListView.SelectedItems.Count == 1; + RemoveMenuItem.IsEnabled = GroupListView.SelectedItems.Count > 0 && !GroupListView.SelectedItems.OfType>().Any((kv) => kv.Key == "everyone"); } } diff --git a/src/Serein.Plus/Pages/PluginConsolePage.xaml.cs b/src/Serein.Plus/Pages/PluginConsolePage.xaml.cs index 31d94daf..4cb2e5f1 100644 --- a/src/Serein.Plus/Pages/PluginConsolePage.xaml.cs +++ b/src/Serein.Plus/Pages/PluginConsolePage.xaml.cs @@ -27,7 +27,7 @@ public PluginConsolePage( PluginManager pluginManager, NetPluginLoader netPluginLoader, JsPluginLoader jsPluginLoader - ) + ) { ViewModel = pluginConsoleViewModel; _infoBarProvider = infoBarProvider; @@ -61,11 +61,20 @@ private void Button_Click(object sender, RoutedEventArgs e) break; case "Reload": - Task.Run(_pluginManager.Reload).ContinueWith((task) => - { - if (task.IsFaulted && task.Exception is not null) - _infoBarProvider.Enqueue("重新加载插件失败", task.Exception.InnerException!.Message, InfoBarSeverity.Error); - }); + Task.Run(_pluginManager.Reload) + .ContinueWith( + (task) => + { + if (task.IsFaulted && task.Exception is not null) + { + _infoBarProvider.Enqueue( + "重新加载插件失败", + task.Exception.InnerException!.Message, + InfoBarSeverity.Error + ); + } + } + ); break; } } diff --git a/src/Serein.Plus/Pages/PluginListPage.xaml.cs b/src/Serein.Plus/Pages/PluginListPage.xaml.cs index 8f714ca9..2dc3bcc0 100644 --- a/src/Serein.Plus/Pages/PluginListPage.xaml.cs +++ b/src/Serein.Plus/Pages/PluginListPage.xaml.cs @@ -54,10 +54,14 @@ private void UpdatePlugins() _pluginInfos.Clear(); foreach (var kv in _jsPluginLoader.Plugins) - _pluginInfos.Add(new(kv.Key,kv.Value)); + { + _pluginInfos.Add(new(kv.Key, kv.Value)); + } foreach (var kv in _netPluginLoader.Plugins) + { _pluginInfos.Add(new(kv.Key, kv.Value)); + } } private void MenuItem_Click(object sender, RoutedEventArgs e) @@ -71,11 +75,20 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) break; case "Reload": - Task.Run(_pluginManager.Reload).ContinueWith((task) => - { - if (task.IsFaulted && task.Exception is not null) - _infoBarProvider.Enqueue("重新加载插件失败", task.Exception.InnerException!.Message, InfoBarSeverity.Error); - }); + Task.Run(_pluginManager.Reload) + .ContinueWith( + (task) => + { + if (task.IsFaulted && task.Exception is not null) + { + _infoBarProvider.Enqueue( + "重新加载插件失败", + task.Exception.InnerException!.Message, + InfoBarSeverity.Error + ); + } + } + ); break; case "ClearConsole": @@ -83,10 +96,14 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) break; case "Disable": - if (PluginListView.SelectedItem is KeyValuePair kv) + if (PluginListView.SelectedItem is KeyValuePair kv) { - kv.Value.Disable(); - _infoBarProvider.Enqueue($"插件(Id={kv.Key})禁用成功", string.Empty, InfoBarSeverity.Success); + kv.Value.Disable(); + _infoBarProvider.Enqueue( + $"插件(Id={kv.Key})禁用成功", + string.Empty, + InfoBarSeverity.Success + ); } break; } @@ -94,7 +111,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) catch (Exception ex) { if (tag == "Disable") + { _infoBarProvider.Enqueue("禁用失败", ex.Message, InfoBarSeverity.Error); + } } } @@ -105,8 +124,8 @@ private void PluginListView_ContextMenuOpening(object sender, ContextMenuEventAr private void PluginListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { - StatusBar.Text = PluginListView.SelectedItem is KeyValuePair kv - ? $"Id={kv.Key}\r\nPath={kv.Value.FileName}" + StatusBar.Text = PluginListView.SelectedItem is KeyValuePair kv + ? $"Id={kv.Key}\r\nPath={kv.Value.FileName}" : string.Empty; } } diff --git a/src/Serein.Plus/Pages/PluginPage.xaml.cs b/src/Serein.Plus/Pages/PluginPage.xaml.cs index ddfc1786..de8bfd8e 100644 --- a/src/Serein.Plus/Pages/PluginPage.xaml.cs +++ b/src/Serein.Plus/Pages/PluginPage.xaml.cs @@ -34,7 +34,9 @@ private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventA private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) { if (args.InvokedItemContainer.Tag is not Type type) + { type = typeof(NotImplPage); + } ContentFrame.Navigate(_services.GetRequiredService(type)); } diff --git a/src/Serein.Plus/Pages/SchedulePage.xaml.cs b/src/Serein.Plus/Pages/SchedulePage.xaml.cs index 28303ffb..aedd2cba 100644 --- a/src/Serein.Plus/Pages/SchedulePage.xaml.cs +++ b/src/Serein.Plus/Pages/SchedulePage.xaml.cs @@ -32,7 +32,8 @@ public SchedulePage(MainWindow mainWindow, ScheduleProvider scheduleProvider) InitializeComponent(); ScheduleDataGrid.ItemsSource = _scheduleProvider.Value; - _scheduleProvider.Value.CollectionChanged += (o, e) => Dispatcher.Invoke(UpdateDetails, o, e); + _scheduleProvider.Value.CollectionChanged += (o, e) => + Dispatcher.Invoke(UpdateDetails, o, e); } private void UpdateDetails(object? sender, EventArgs e) @@ -41,9 +42,9 @@ private void UpdateDetails(object? sender, EventArgs e) Details.Text = ScheduleDataGrid.SelectedItems.Count > 1 ? $"共{_scheduleProvider.Value.Count}项,已选择{ScheduleDataGrid.SelectedItems.Count}项" - : ScheduleDataGrid.SelectedIndex >= 0 - ? $"共{_scheduleProvider.Value.Count}项,已选择第{ScheduleDataGrid.SelectedIndex + 1}项" - : $"共{_scheduleProvider.Value.Count}项"; + : ScheduleDataGrid.SelectedIndex >= 0 + ? $"共{_scheduleProvider.Value.Count}项,已选择第{ScheduleDataGrid.SelectedIndex + 1}项" + : $"共{_scheduleProvider.Value.Count}项"; } private void MenuItem_Click(object sender, RoutedEventArgs e) @@ -64,24 +65,38 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) break; case "Remove": - DialogHelper.ShowDeleteConfirmation("确定要删除所选项吗?").ContinueWith((task) => - { - if (!task.Result) - return; - - Dispatcher.Invoke(() => - { - foreach (var item in ScheduleDataGrid.SelectedItems.OfType().ToArray()) - _scheduleProvider.Value.Remove(item); - - _scheduleProvider.SaveAsyncWithDebounce(); - }); - }); + DialogHelper + .ShowDeleteConfirmation("确定要删除所选项吗?") + .ContinueWith( + (task) => + { + if (!task.Result) + { + return; + } + + Dispatcher.Invoke(() => + { + foreach ( + var item in ScheduleDataGrid + .SelectedItems.OfType() + .ToArray() + ) + { + _scheduleProvider.Value.Remove(item); + } + + _scheduleProvider.SaveAsyncWithDebounce(); + }); + } + ); break; case "Edit": if (ScheduleDataGrid.SelectedItem is not Schedule s3) + { return; + } var s4 = s3.ShallowClone(); @@ -99,7 +114,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) case "Toggle": foreach (var item in ScheduleDataGrid.SelectedItems.OfType().ToArray()) + { item.IsEnabled = !item.IsEnabled; + } break; case "OpenDoc": @@ -121,6 +138,7 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) private void ScheduleDataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - EditMenuItem.IsEnabled = ScheduleDataGrid.SelectedItems.Count == 1 && ScheduleDataGrid.SelectedItem is Schedule; + EditMenuItem.IsEnabled = + ScheduleDataGrid.SelectedItems.Count == 1 && ScheduleDataGrid.SelectedItem is Schedule; } } diff --git a/src/Serein.Plus/Pages/ServerPage.xaml.cs b/src/Serein.Plus/Pages/ServerPage.xaml.cs index b2048386..7e301399 100644 --- a/src/Serein.Plus/Pages/ServerPage.xaml.cs +++ b/src/Serein.Plus/Pages/ServerPage.xaml.cs @@ -43,7 +43,9 @@ ServerManager serverManager DataContext = this; foreach (var (id, server) in _serverManager.Servers) + { Add(id, server); + } _serverManager.ServersUpdated += ServerManager_ServersUpdated; } @@ -71,7 +73,9 @@ private void Add(string id, Server server) TabControl.Items.Add(tabItem); if (TabControl.Items.Count == 1) + { TabControl.SelectedIndex = 0; + } } private void ServerManager_ServersUpdated(object? sender, ServersUpdatedEventArgs e) @@ -82,11 +86,15 @@ private void ServerManager_ServersUpdated(object? sender, ServersUpdatedEventArg e.Type == ServersUpdatedType.Added && _serverManager.Servers.TryGetValue(e.Id, out var server) ) + { Add(e.Id, server); + } else if (_panels.TryGetValue(e.Id, out var page)) { if (TabControl.Items.Contains(page)) + { TabControl.Items.Remove(page); + } _panels.Remove(e.Id); _infoBarProvider.Enqueue( @@ -100,8 +108,10 @@ private void ServerManager_ServersUpdated(object? sender, ServersUpdatedEventArg private void Page_Loaded(object sender, RoutedEventArgs e) { + _cancellationTokenSource?.Dispose(); _cancellationTokenSource = new(); if (_serverManager.Servers.Count == 0) + { _infoBarProvider.Enqueue( "当前没有服务器配置", "你可以点击上方的加号进行添加", @@ -109,12 +119,15 @@ private void Page_Loaded(object sender, RoutedEventArgs e) TimeSpan.FromSeconds(5), _cancellationTokenSource.Token ); + } } private void Page_Unloaded(object sender, RoutedEventArgs e) { if (_cancellationTokenSource?.IsCancellationRequested == false) + { _cancellationTokenSource.Cancel(); + } } private void MenuItem_Click(object sender, RoutedEventArgs e) @@ -126,7 +139,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) { var dialog = new OpenFileDialog() { Filter = "服务器配置文件|*.json" }; if (dialog.ShowDialog() != true) + { return; + } var config = ServerManager.LoadFrom(dialog.FileName); var editor1 = new ServerConfigurationEditor(_serverManager, config) @@ -135,7 +150,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) }; if (editor1.ShowDialog() != true || string.IsNullOrEmpty(editor1.Id)) + { return; + } _serverManager.Add(editor1.Id, editor1.Configuration); } @@ -154,7 +171,10 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) }; if (editor2.ShowDialog() != true || string.IsNullOrEmpty(editor2.Id)) + { return; + } + _serverManager.Add(editor2.Id, editor2.Configuration); } catch (Exception ex) diff --git a/src/Serein.Plus/Pages/Settings/AppSettingPage.xaml.cs b/src/Serein.Plus/Pages/Settings/AppSettingPage.xaml.cs index 99cd16b7..2b9a4e31 100644 --- a/src/Serein.Plus/Pages/Settings/AppSettingPage.xaml.cs +++ b/src/Serein.Plus/Pages/Settings/AppSettingPage.xaml.cs @@ -26,7 +26,7 @@ public AppSettingPage(SettingProvider settingProvider, UpdateChecker updateCheck InitializeComponent(); UpdateVersionInfoBar(); - + DataContext = _settingProvider; ThemePanel.Children @@ -40,18 +40,20 @@ public AppSettingPage(SettingProvider settingProvider, UpdateChecker updateCheck private void UpdateVersionInfoBar() { VersionInfoBar.IsOpen = _updateChecker.Newest is not null; - VersionInfoBar.Message = string.Format("ǰ汾{0}°汾{1}", SereinApp.Version, _updateChecker.Newest?.TagName); + VersionInfoBar.Message = string.Format("��ǰ�汾��{0}�����°汾��{1}", SereinApp.Version, _updateChecker.Newest?.TagName); } private void OnPropertyChanged(object? sender, EventArgs e) { if (IsLoaded) + { _settingProvider.SaveAsyncWithDebounce(); + } } private void OnThemeRadioButtonChecked(object sender, RoutedEventArgs e) { - var tag = (sender as RadioButton)?.Tag as string; + var tag = (sender as RadioButton)?.Tag as string; _settingProvider.Value.Application.Theme = tag switch { diff --git a/src/Serein.Plus/Pages/Settings/CategoriesPage.xaml.cs b/src/Serein.Plus/Pages/Settings/CategoriesPage.xaml.cs index 3f463545..e3adea40 100644 --- a/src/Serein.Plus/Pages/Settings/CategoriesPage.xaml.cs +++ b/src/Serein.Plus/Pages/Settings/CategoriesPage.xaml.cs @@ -42,7 +42,9 @@ NavigationViewSelectionChangedEventArgs args ) { if (args.SelectedItem is not NavigationViewItem { Tag: Type type }) + { type = typeof(NotImplPage); + } SubFrame.Navigate(_services.GetRequiredService(type)); } diff --git a/src/Serein.Plus/Pages/Settings/ConnectionSettingPage.xaml.cs b/src/Serein.Plus/Pages/Settings/ConnectionSettingPage.xaml.cs index 6d018baf..59d11452 100644 --- a/src/Serein.Plus/Pages/Settings/ConnectionSettingPage.xaml.cs +++ b/src/Serein.Plus/Pages/Settings/ConnectionSettingPage.xaml.cs @@ -36,7 +36,9 @@ private void OnPasswordChanged(object? sender, EventArgs e) private void OnPropertyChanged(object? sender, EventArgs e) { if (IsLoaded) + { _settingProvider.SaveAsyncWithDebounce(); + } } private void UseReverseWebSocket_Click(object sender, RoutedEventArgs e) @@ -60,8 +62,12 @@ var id in GroupTextBox.Text.Split( StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries ) ) + { if (long.TryParse(id, out long i)) + { list.Add(i); + } + } _settingProvider.Value.Connection.Groups = [.. list]; OnPropertyChanged(sender, e); @@ -76,8 +82,12 @@ var id in AdministratorsTextBox.Text.Split( StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries ) ) + { if (long.TryParse(id, out long i)) + { list.Add(i); + } + } _settingProvider.Value.Connection.Administrators = [.. list]; OnPropertyChanged(sender, e); diff --git a/src/Serein.Plus/Pages/Settings/ReactionSettingPage.xaml.cs b/src/Serein.Plus/Pages/Settings/ReactionSettingPage.xaml.cs index 86e61d26..e1f1de2e 100644 --- a/src/Serein.Plus/Pages/Settings/ReactionSettingPage.xaml.cs +++ b/src/Serein.Plus/Pages/Settings/ReactionSettingPage.xaml.cs @@ -36,12 +36,18 @@ public ReactionSettingPage(SettingProvider settingProvider) private void ReactionTypeListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (ReactionTypeListView.SelectedItem is not DisplayedItem item) + { return; + } CommandListView.Items.Clear(); if (_settingProvider.Value.Reactions.TryGetValue(item.Type, out var commands)) + { foreach (var command in commands) + { CommandListView.Items.Add(command); + } + } } private void MenuItem_Click(object sender, RoutedEventArgs e) @@ -54,7 +60,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) dialog.ShowAsync().ContinueWith((task) => Dispatcher.Invoke(() => { if (task.Result != ContentDialogResult.Primary) + { return; + } CommandListView.Items.Add(dialog.Command); Save(); @@ -68,8 +76,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) Dispatcher.Invoke(() => { foreach (var command in CommandListView.SelectedItems.OfType().ToArray()) + { CommandListView.Items.Remove(command); - + } Save(); }); }); @@ -79,7 +88,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) private void Save() { if (ReactionTypeListView.SelectedItem is not DisplayedItem item) + { return; + } _settingProvider.Value.Reactions[item.Type] = CommandListView.Items.OfType().ToArray(); _settingProvider.SaveAsyncWithDebounce(); diff --git a/src/Serein.Plus/Pages/Settings/WebApiSettingPage.xaml.cs b/src/Serein.Plus/Pages/Settings/WebApiSettingPage.xaml.cs index e3d6802f..a973af04 100644 --- a/src/Serein.Plus/Pages/Settings/WebApiSettingPage.xaml.cs +++ b/src/Serein.Plus/Pages/Settings/WebApiSettingPage.xaml.cs @@ -40,7 +40,9 @@ SettingProvider settingProvider private void OnPropertyChanged(object sender, EventArgs e) { if (IsLoaded) + { _settingProvider.SaveAsyncWithDebounce(); + } } private void CheckBox_Click(object sender, RoutedEventArgs e) @@ -89,7 +91,9 @@ private void OpenFileButton_Click(object sender, RoutedEventArgs e) { var dialog = new OpenFileDialog(); if (dialog.ShowDialog() == true) + { _settingProvider.Value.WebApi.Certificate.Path = dialog.FileName; + } OnPropertyChanged(sender, e); } } diff --git a/src/Serein.Plus/Pages/ShellPage.xaml.cs b/src/Serein.Plus/Pages/ShellPage.xaml.cs index 57ab684e..2542d465 100644 --- a/src/Serein.Plus/Pages/ShellPage.xaml.cs +++ b/src/Serein.Plus/Pages/ShellPage.xaml.cs @@ -41,7 +41,9 @@ private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventA private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) { if (args.InvokedItemContainer.Tag is not Type type) + { type = args.IsSettingsInvoked ? typeof(CategoriesPage) : typeof(NotImplPage); + } var page = _services.GetRequiredService(type); diff --git a/src/Serein.Plus/Services/InfoBarProvider.cs b/src/Serein.Plus/Services/InfoBarProvider.cs index 2d588054..e1220143 100644 --- a/src/Serein.Plus/Services/InfoBarProvider.cs +++ b/src/Serein.Plus/Services/InfoBarProvider.cs @@ -35,7 +35,9 @@ public void Enqueue( }, cancellationToken); if (!_isRunning) + { Task.Run(RunInfoBarLoop, CancellationToken.None); + } } private void RunInfoBarLoop() @@ -46,7 +48,9 @@ private void RunInfoBarLoop() var task = _tasks.Take(); if (task.CancellationToken.IsCancellationRequested) + { continue; + } _mainWindow.Value.Dispatcher.Invoke(_mainWindow.Value.ShowInfoBar, task); diff --git a/src/Serein.Plus/Services/TitleUpdater.cs b/src/Serein.Plus/Services/TitleUpdater.cs index 5f7c6f88..68c6694f 100644 --- a/src/Serein.Plus/Services/TitleUpdater.cs +++ b/src/Serein.Plus/Services/TitleUpdater.cs @@ -5,7 +5,7 @@ using Serein.Core.Services.Commands; using Serein.Core.Services.Data; -namespace Serein.Plus.Services; +namespace Serein.Plus.Services; public sealed class TitleUpdater : NotifyPropertyChangedModelBase { @@ -18,15 +18,17 @@ public TitleUpdater(SettingProvider settingProvider, CommandParser commandParser { _settingProvider = settingProvider; _commandParser = commandParser; - _timer= new(2000) { Enabled = true }; + _timer = new(2000) { Enabled = true }; _timer.Elapsed += (_, _) => Update(); _settingProvider.Value.Application.PropertyChanged += (_, e) => { if (e.PropertyName == nameof(ApplicationSetting.CustomTitle)) + { Update(); - }; + } + }; } - + public string CustomTitle { get; private set; } = Name; public void Update() diff --git a/src/Serein.Plus/Validations/NotEmptyValidationRule.cs b/src/Serein.Plus/Validations/NotEmptyValidationRule.cs index a9d0a730..4cd8900f 100644 --- a/src/Serein.Plus/Validations/NotEmptyValidationRule.cs +++ b/src/Serein.Plus/Validations/NotEmptyValidationRule.cs @@ -10,7 +10,9 @@ public override ValidationResult Validate(object value, CultureInfo cultureInfo) if (value is string s) { if (!string.IsNullOrEmpty(s)) + { return ValidationResult.ValidResult; + } return new(false, "Value cannot be empty."); } return new(false, "Value must be a string."); diff --git a/src/Serein.Plus/Windows/MatchEditor.xaml.cs b/src/Serein.Plus/Windows/MatchEditor.xaml.cs index 0f39cd9d..9807cc7a 100644 --- a/src/Serein.Plus/Windows/MatchEditor.xaml.cs +++ b/src/Serein.Plus/Windows/MatchEditor.xaml.cs @@ -25,9 +25,13 @@ private void Button_Click(object sender, RoutedEventArgs e) private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { var index = (sender as ComboBox)?.SelectedIndex; - if ((MatchFieldType?)index is MatchFieldType.GroupMsg or MatchFieldType.PrivateMsg) + if ((MatchFieldType?)index is MatchFieldType.GroupMsg or MatchFieldType.PrivateMsg) + { RequireAdminCheckBox.IsEnabled = true; + } else + { RequireAdminCheckBox.IsChecked = RequireAdminCheckBox.IsEnabled = false; + } } } diff --git a/src/Serein.Plus/Windows/PermissionGroupEditor.xaml.cs b/src/Serein.Plus/Windows/PermissionGroupEditor.xaml.cs index 8c613ef3..2abd544d 100644 --- a/src/Serein.Plus/Windows/PermissionGroupEditor.xaml.cs +++ b/src/Serein.Plus/Windows/PermissionGroupEditor.xaml.cs @@ -41,9 +41,12 @@ public PermissionGroupEditor( IdTextBox.IsEnabled = string.IsNullOrEmpty(id); foreach (var userId in Group.Members) + { MemberListView.Items.Add(userId); + } foreach ((var key, var value) in group.Nodes) + { PermissionListView.Items.Add( new PermissionItemViewModel { @@ -57,6 +60,7 @@ out var description : string.Empty, } ); + } } private void MemberMenuItem_Click(object sender, RoutedEventArgs e) @@ -74,12 +78,16 @@ private void MemberMenuItem_Click(object sender, RoutedEventArgs e) Dispatcher.Invoke(() => { if (!MemberListView.Items.Contains(dialog.Id)) + { MemberListView.Items.Add(dialog.Id); + } else + { DialogHelper.ShowSimpleDialog( "添加失败", "已经添加过此用户Id" ); + } }); } ); @@ -87,6 +95,7 @@ private void MemberMenuItem_Click(object sender, RoutedEventArgs e) case "Remove": if (MemberListView.SelectedIndex >= 0) + { DialogHelper .ShowDeleteConfirmation( $"你确定要删除\"{MemberListView.SelectedItem}\"吗?" @@ -103,6 +112,7 @@ private void MemberMenuItem_Click(object sender, RoutedEventArgs e) ); } ); + } break; } } @@ -119,6 +129,7 @@ private void PermissionMenuItem_Click(object sender, RoutedEventArgs e) (task) => { if (task.Result == ContentDialogResult.Primary) + { Dispatcher.Invoke( () => PermissionListView.Items.Add( @@ -136,13 +147,16 @@ out var description } ) ); + } } ); break; case "Edit": if (PermissionListView.SelectedItem is not PermissionItemViewModel viewModel) + { break; + } var dialog2 = new PermissionEditorDialog( _permissionManager, @@ -171,22 +185,26 @@ out var description case "Remove": if (PermissionListView.SelectedIndex >= 0) + { DialogHelper - .ShowDeleteConfirmation( - $"你确定要删除\"{(PermissionListView.SelectedItem as PermissionItemViewModel)?.Node}\"吗?" - ) - .ContinueWith( - (task) => - { - if (task.Result) - Dispatcher.Invoke( - () => - PermissionListView.Items.RemoveAt( - PermissionListView.SelectedIndex - ) - ); - } - ); + .ShowDeleteConfirmation( + $"你确定要删除\"{(PermissionListView.SelectedItem as PermissionItemViewModel)?.Node}\"吗?" + ) + .ContinueWith( + (task) => + { + if (task.Result) + { + Dispatcher.Invoke( + () => + PermissionListView.Items.RemoveAt( + PermissionListView.SelectedIndex + ) + ); + } + } + ); + } break; } } @@ -198,14 +216,20 @@ private void Button_Click(object sender, RoutedEventArgs e) GroupManager.ValidateGroupId(Id); if (IdTextBox.IsEnabled && _groupManager.Ids.Contains(Id)) + { throw new InvalidOperationException("此Id已被占用"); + } Group.Members = [.. MemberListView.Items.OfType().Distinct()]; var dict = new Dictionary(); foreach (var item in PermissionListView.Items.OfType()) + { if (!string.IsNullOrEmpty(item.Node)) + { dict.TryAdd(item.Node, item.Value); + } + } Group.Nodes = dict; DialogResult = true; Close(); diff --git a/src/Serein.Plus/Windows/ServerConfigurationEditor.xaml.cs b/src/Serein.Plus/Windows/ServerConfigurationEditor.xaml.cs index 5bf72f24..035eadb0 100644 --- a/src/Serein.Plus/Windows/ServerConfigurationEditor.xaml.cs +++ b/src/Serein.Plus/Windows/ServerConfigurationEditor.xaml.cs @@ -38,7 +38,9 @@ private void Button_Click(object sender, RoutedEventArgs e) try { if (IdTextBox.IsEnabled) + { Validate(); + } DialogResult = true; Close(); @@ -58,13 +60,17 @@ private void Validate() { ServerManager.ValidateId(Id); if (_serverManager.Servers.ContainsKey(Id!)) + { throw new InvalidOperationException("此Id已被占用"); + } } private void OpenFileButton_Click(object sender, RoutedEventArgs e) { var dialog = new OpenFileDialog(); if (dialog.ShowDialog() == true) + { Configuration.FileName = dialog.FileName; + } } } diff --git a/src/Serein.Plus/Windows/ServerPluginManagerWindow.xaml.cs b/src/Serein.Plus/Windows/ServerPluginManagerWindow.xaml.cs index 1bb6ffc0..a0718f90 100644 --- a/src/Serein.Plus/Windows/ServerPluginManagerWindow.xaml.cs +++ b/src/Serein.Plus/Windows/ServerPluginManagerWindow.xaml.cs @@ -35,7 +35,9 @@ private void Update() { Plugins.Clear(); foreach (var plugin in _server.PluginManager.Plugins) + { Plugins.Add(plugin); + } } private void MenuItem_Click(object sender, RoutedEventArgs e) @@ -56,6 +58,7 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) Filter = "可接受的插件文件|*.dll;*.jar;*.js;*.py;*.lua", }; if (openFileDialog.ShowDialog() == true) + { try { _server.PluginManager.Add(openFileDialog.FileNames); @@ -64,12 +67,15 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) { DialogHelper.ShowSimpleDialog("导入失败", ex.Message); } + } + _server.PluginManager.Update(); Update(); break; case "Enable": foreach (var plugin in selectedPlugins) + { try { _server.PluginManager.Enable(plugin); @@ -82,10 +88,12 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) ); break; } + } break; case "Disable": foreach (var plugin in selectedPlugins) + { try { _server.PluginManager.Disable(plugin); @@ -98,6 +106,7 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) ); break; } + } break; case "Remove": @@ -114,7 +123,9 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) (task) => { if (task.Result) + { foreach (var plugin in selectedPlugins) + { try { _server.PluginManager.Remove(plugin); @@ -130,6 +141,8 @@ private void MenuItem_Click(object sender, RoutedEventArgs e) ); break; } + } + } } ); break; diff --git a/src/Serein.Tests/HostFactory.cs b/src/Serein.Tests/HostFactory.cs index ac21de4c..fcfd4768 100644 --- a/src/Serein.Tests/HostFactory.cs +++ b/src/Serein.Tests/HostFactory.cs @@ -19,12 +19,16 @@ public static IHost BuildNew() lock (Lock) { if (Directory.Exists(PathConstants.Root)) + { foreach (var file in Directory.GetFiles(PathConstants.Root, "*", SearchOption.AllDirectories)) + { try { File.Delete(file); } catch { } + } + } var builder = SereinAppBuilder.CreateBuilder(); diff --git a/src/Serein.Tests/Services/CommandParser/CommandTests.cs b/src/Serein.Tests/Services/CommandParser/CommandTests.cs index e96e9690..98735ba8 100644 --- a/src/Serein.Tests/Services/CommandParser/CommandTests.cs +++ b/src/Serein.Tests/Services/CommandParser/CommandTests.cs @@ -62,9 +62,13 @@ public static void ShouldBeAbleToParseCommand( Assert.Equal(exceptedBody, cmd.Body); if (!string.IsNullOrEmpty(exceptedArgument)) + { Assert.Equal(exceptedArgument, cmd.Argument); + } else + { Assert.Equal(string.Empty, cmd.Argument); + } } [Theory] diff --git a/src/Serein.Tests/Services/Server/ConfigurationTests.cs b/src/Serein.Tests/Services/Server/ConfigurationTests.cs index 36da5872..688f7b54 100644 --- a/src/Serein.Tests/Services/Server/ConfigurationTests.cs +++ b/src/Serein.Tests/Services/Server/ConfigurationTests.cs @@ -27,12 +27,16 @@ public ConfigurationTests() public void Dispose() { foreach (var (_, server) in _serverManager.Servers) + { if (server.Status) + { server.Terminate(); + } + } _app.StopAsync(); } - + [Fact] public async Task ShouldStartServerWithStartWhenSettingUpTrue() { diff --git a/src/Serein.Tests/Services/Server/FunctionTests.cs b/src/Serein.Tests/Services/Server/FunctionTests.cs index a169713c..db1eff56 100644 --- a/src/Serein.Tests/Services/Server/FunctionTests.cs +++ b/src/Serein.Tests/Services/Server/FunctionTests.cs @@ -26,8 +26,12 @@ public FunctionTests() public void Dispose() { foreach (var (_, server) in _serverManager.Servers) + { if (server.Status) + { server.Terminate(); + } + } _app.StopAsync(); } diff --git a/src/Serein.Tests/Services/Server/ManagementTests.cs b/src/Serein.Tests/Services/Server/ManagementTests.cs index 009eeb08..3535a7f7 100644 --- a/src/Serein.Tests/Services/Server/ManagementTests.cs +++ b/src/Serein.Tests/Services/Server/ManagementTests.cs @@ -26,8 +26,12 @@ public ManagementTests() public void Dispose() { foreach (var (_, server) in _serverManager.Servers) + { if (server.Status) + { server.Terminate(); + } + } _app.StopAsync(); } diff --git a/src/Serein.Tests/Services/WebApi/ApiTests.cs b/src/Serein.Tests/Services/WebApi/ApiTests.cs index 5ebd96ce..e8bd4b40 100644 --- a/src/Serein.Tests/Services/WebApi/ApiTests.cs +++ b/src/Serein.Tests/Services/WebApi/ApiTests.cs @@ -36,8 +36,8 @@ public ApiTests() public void Dispose() { - if (_httpServer.State != WebServerState.Stopped) - _httpServer.Stop(); + if (_httpServer.State != WebServerState.Stopped){ + _httpServer.Stop();} _app.StopAsync(); } @@ -71,8 +71,8 @@ public async Task ShouldBeAbleToGetHardwareInfo() attribute is null || attribute.Verb != HttpVerbs.Get || !attribute.Route.StartsWith("/hardware") - ) - continue; + ){ + continue;} var response = await _client.GetAsync("/api" + attribute.Route); Assert.True(response.IsSuccessStatusCode);