Files
MRCC/launcher/PCL-CE/PCL.Core/App/Essentials/StartupService.cs
T

160 lines
6.3 KiB
C#
Raw Normal View History

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;
/// <summary>
/// 命令处理委托
/// </summary>
/// <param name="model">命令模型</param>
/// <param name="isCallback">
/// 指示该委托是否由已注册的回调触发,若是,则代表可能由 RPC
/// 或用户后续操作而非当前进程的命令行参数触发,应注意鉴权问题
/// </param>
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");
/// <summary>
/// 解析后的命令行模型实例
/// </summary>
/// <exception cref="Exception">尚未初始化完成</exception>
public static CommandLine CommandLine
{
get => field ?? throw _GetUninitializedException();
private set;
} = null!;
private static readonly Dictionary<string, CommandLine> _UnhandledCommandMap = [];
private static readonly ConcurrentDictionary<string, CommandHandler> _HandleCallbackMap = [];
/// <summary>
/// 未处理的子命令
/// </summary>
public static IReadOnlyDictionary<string, CommandLine> UnhandledCommands => _UnhandledCommandMap.AsReadOnly();
/// <summary>
/// 处理一个子命令
/// </summary>
/// <param name="command">子命令</param>
/// <param name="handler">用于处理命令的委托,传入 <see langword="null"/> 则触发已注册的处理回调</param>
/// <param name="registerCallback">指定是否注册该委托为处理回调</param>
/// <returns>是否执行成功,若子命令不存在或未注册任何处理回调则不成功</returns>
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<SubcommandDefinition> 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<Dictionary<string, CommandLine>>(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;
}
}