using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Threading.Tasks; using PCL.Core.App.Cli; using PCL.Core.App.IoC; using PCL.Core.Utils.OS; using PCL.Core.Utils.Secret; using PCL.Core.Utils; namespace PCL.Core.App.Essentials; /// /// 命令处理委托 /// /// 命令模型 /// /// 指示该委托是否由已注册的回调触发,若是,则代表可能由 RPC /// 或用户后续操作而非当前进程的命令行参数触发,应注意鉴权问题 /// public delegate void CommandHandler(CommandLine model, bool isCallback); [LifecycleService(LifecycleState.BeforeLoading, Priority = int.MaxValue)] [LifecycleScope("startup", "基本信息", false)] public sealed partial class StartupService { private static Exception _GetUninitializedException() => new InvalidOperationException("Not initialized"); /// /// 解析后的命令行模型实例 /// /// 尚未初始化完成 public static CommandLine CommandLine { get => field ?? throw _GetUninitializedException(); private set; } = null!; private static readonly Dictionary _UnhandledCommandMap = []; private static readonly ConcurrentDictionary _HandleCallbackMap = []; /// /// 未处理的子命令 /// public static IReadOnlyDictionary UnhandledCommands => _UnhandledCommandMap.AsReadOnly(); /// /// 处理一个子命令 /// /// 子命令 /// 用于处理命令的委托,传入 则触发已注册的处理回调 /// 指定是否注册该委托为处理回调 /// 是否执行成功,若子命令不存在或未注册任何处理回调则不成功 public static bool TryHandleCommand( string command, CommandHandler? handler = null, bool registerCallback = false) { var isCallback = false; if (handler is null) { _HandleCallbackMap.TryGetValue(command, out handler); if (handler is null) return false; isCallback = true; } else if (registerCallback) _HandleCallbackMap.TryAdd(command, handler); lock (_UnhandledCommandMap) { _UnhandledCommandMap.TryGetValue(command, out var model); if (model is null) return false; // remove all related commands foreach (var x in _UnhandledCommandMap.Keys.Where(x => x.StartsWith(command)).ToList()) _UnhandledCommandMap.Remove(x); // run handler try { handler(model, isCallback); } catch (Exception ex) { Context.Warn($"Exception thrown while handle command: {command}", ex); } return true; } } [LifecycleStart] private static void _LogBasicInfo() { var info = new StringBuilder(); info.Append("\n版本: ").Append(Basics.Metadata.Version).Append(" (").Append(GetArchitectureName(RuntimeInformation.ProcessArchitecture)).Append(')'); info.Append("\n路径: ").Append(Basics.ExecutablePath); info.Append("\n命令行参数:"); if (Basics.CommandLineArguments.Length == 0) info.Append(" []"); else foreach (var x in Basics.CommandLineArguments) info.Append("\n - ").Append(x); info.Append("\n系统版本: ").Append(Environment.OSVersion.Version).Append(" (").Append(GetArchitectureName(RuntimeInformation.OSArchitecture)).Append(')'); var memory = KernelInterop.GetPhysicalMemoryBytes(); const int memoryDiv = 1024 * 1024; info.Append("\n可用内存: ").Append(memory.Available / memoryDiv).Append('/').Append(memory.Total / memoryDiv).Append(" MiB"); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var cp = Encoding.GetEncoding(0); info.Append("\n默认代码页: ").Append(cp.EncodingName).Append(" (").Append(cp.CodePage).Append(')'); info.Append("\n管理员身份: ").Append(ProcessInterop.IsAdmin()); info.Append("\n识别码: ").Append(Identify.LauncherId); Context.Info(info.ToString()); return; string GetArchitectureName(Architecture arch) => arch switch { Architecture.X64 => "x64", Architecture.Arm64 => "ARM64", _ => arch.ToString() }; } [LifecycleStart] private static void _ParseCommandLineArgs() { IEnumerable subcommands = [ ("update", [("execute"), ("success"), ("failed")]), ("activate", []), ("promote", []), ]; Context.Debug("正在解析命令行参数..."); var c = CommandLine.Parse(Basics.FullCommandLineArguments, subcommands); var prefix = new StringBuilder(); while (true) { _UnhandledCommandMap[prefix.ToString()] = c; if (c.Subcommand is null) break; if (prefix.Length != 0) prefix.Append('.'); prefix.Append(c.Subcommand.CommandText); c = c.Subcommand; } _UnhandledCommandMap.Remove("", out c!); CommandLine = c; } [RegisterRpc("cli")] public static RpcResponse OnRpcCommand(string? argument, string? content, bool indent) { if (content is null) return RpcResponse.Err("Must provide valid JSON model"); try { var models = JsonSerializer.Deserialize>(content, JsonCompat.SerializerOptions); if (models is null) return RpcResponse.Err("Invalid JSON: empty/null content"); Task.Run(() => { foreach (var (command, model) in models) { _UnhandledCommandMap[command] = model; TryHandleCommand(command); } }); } catch (JsonException ex) { return RpcResponse.Err($"Invalid JSON: {ex.Message}"); } return RpcResponse.EmptySuccess; } }