mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
* 激进的更新 * 试试 * fix.可爱的我一直在修CI( * fix.启动器一定要能够启动 * feat.尝试弄了AOT的启动器。 * fix.修CI,好像是因为Linux那边有个问题,反正修就对了。 * fix.ci难修,为什么liunx跑不起来呢? * Update build.yml * Update LanMountainDesktop.csproj * changed.调整了启动逻辑,优化了更新页面。 * changed.优化了更新体验 * feat.依旧试增量更新这一块,看看velopack * fix.我们试验性地修复了启动器无法正常启动的问题,原因可能是这个画面没有启动,就GUI没显示。然后还把编译问题修了一下。 * fix.继续修ci,ci怎么天天炸 * changed.velopack,试试rust * fix.修ci,修融合桌面,修启动器 * fix.GitHub Action工作流怎么天天出问题 * feat.引入velopack,不好,是rust(至少内存很安全了。 * chore: migrate release pipeline to signed filemap and wire rainyun s3 * fix: make optional s3 upload step workflow-parse safe * fix: make delta pack generation robust for empty diffs and linux paths * chore: rotate launcher update public key for pdc signing * fix: restore stable launcher update public key * fix: sync launcher public key with update signing secret * fix: normalize PEM line endings in signing key validation * fix: rotate launcher public key to match ci signing secret * fix: compare signing keys by SPKI instead of PEM text * refactor update backend to host-managed PDC pipeline * fix release workflow env key collisions * relax publish-pdc precheck to require S3 only * set GH_TOKEN for PDCC installer step * ci: add local pdc mock fallback for release publish * ci: fix pdc mock process log redirection * ci: fallback pdcc signing key to update private key * ci: ensure pdcc signing passphrase env is always set * ci: create pdcc publish root before invoking client * ci: set pdcc version variable from release version * ci: decouple pdcc installer version from publish config version * ci: package pdcc subchannels with generated filemap and changelog * ci: make local pdc mock diff return empty for fast fallback * ci: fix pdcc variable mapping and pdc signing prechecks * Update App.axaml.cs * ci: wire aws cli credentials for rainyun s3 * ci: pin pdcc client version separately from app version * ci: harden local pdc mock transport handling * ci: publish pdcc subchannels in one pass * ci: add pdcc publish heartbeat and timeout * ci: fix pdcc publish workdir bootstrap * feat.Penguin Logistics Online Network Distribution System * ci: fix plonds s3 probe and signing fallback * ci: validate signing key and quiet missing baselines * ci: relax aws checksum mode for rainyun s3 * ci: avoid multipart uploads to rainyun s3 * ci: handle empty plonds baselines safely * ci.plonds * Rebuild release pipeline around PLONDS and DDSS * Fix Windows installer script path in release workflow
193 lines
5.9 KiB
C#
193 lines
5.9 KiB
C#
using System.Buffers;
|
||
using System.IO.Pipes;
|
||
using System.Text.Json;
|
||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||
|
||
namespace LanMountainDesktop.Launcher.Services.Ipc;
|
||
|
||
/// <summary>
|
||
/// Launcher IPC 服务端 - 接收主程序的启动进度报告
|
||
/// 采用持久连接 + 长度前缀协议,支持客户端在同一连接上多次发送消息。
|
||
/// 跨平台实现:Windows 使用命名管道,Linux/macOS 使用 Unix 域套接字
|
||
/// </summary>
|
||
public class LauncherIpcServer : IDisposable
|
||
{
|
||
private readonly CancellationTokenSource _cts = new();
|
||
private readonly Action<StartupProgressMessage> _onProgress;
|
||
private Task? _listenTask;
|
||
private NamedPipeServerStream? _currentPipe;
|
||
|
||
/// <summary>
|
||
/// 协议:每条消息以 4 字节小端 int32 长度前缀开头,后跟 UTF-8 JSON 正文。
|
||
/// 这在 Windows Message 模式和 Unix Byte 模式下均能可靠工作。
|
||
/// </summary>
|
||
private const int LengthPrefixSize = 4;
|
||
|
||
public LauncherIpcServer(Action<StartupProgressMessage> onProgress)
|
||
{
|
||
_onProgress = onProgress;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动 IPC 服务端监听
|
||
/// </summary>
|
||
public void Start()
|
||
{
|
||
_listenTask = Task.Run(ListenLoopAsync, _cts.Token);
|
||
}
|
||
|
||
private async Task ListenLoopAsync()
|
||
{
|
||
while (!_cts.Token.IsCancellationRequested)
|
||
{
|
||
NamedPipeServerStream? pipe = null;
|
||
try
|
||
{
|
||
pipe = new NamedPipeServerStream(
|
||
LauncherIpcConstants.PipeName,
|
||
PipeDirection.In,
|
||
1,
|
||
PipeTransmissionMode.Byte);
|
||
|
||
_currentPipe = pipe;
|
||
await pipe.WaitForConnectionAsync(_cts.Token);
|
||
|
||
// 持久连接:在同一连接上循环读取多条消息,直到客户端断开
|
||
await ReadMessagesFromConnectionAsync(pipe, _cts.Token);
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
break;
|
||
}
|
||
catch (IOException)
|
||
{
|
||
// 客户端断开连接,继续等待新连接
|
||
continue;
|
||
}
|
||
catch (ObjectDisposedException)
|
||
{
|
||
break;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.Error.WriteLine($"IPC listen error: {ex.Message}");
|
||
try
|
||
{
|
||
await Task.Delay(200, _cts.Token);
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
try
|
||
{
|
||
pipe?.Dispose();
|
||
}
|
||
catch { }
|
||
|
||
if (ReferenceEquals(_currentPipe, pipe))
|
||
{
|
||
_currentPipe = null;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从已连接的管道中持续读取消息,直到连接断开或取消
|
||
/// </summary>
|
||
private async Task ReadMessagesFromConnectionAsync(NamedPipeServerStream pipe, CancellationToken cancellationToken)
|
||
{
|
||
var lengthBuffer = ArrayPool<byte>.Shared.Rent(LengthPrefixSize);
|
||
try
|
||
{
|
||
while (pipe.IsConnected && !cancellationToken.IsCancellationRequested)
|
||
{
|
||
// 1. 读取 4 字节长度前缀
|
||
var totalRead = 0;
|
||
while (totalRead < LengthPrefixSize)
|
||
{
|
||
var read = await pipe.ReadAsync(lengthBuffer.AsMemory(totalRead, LengthPrefixSize - totalRead), cancellationToken);
|
||
if (read == 0)
|
||
{
|
||
// 连接已关闭
|
||
return;
|
||
}
|
||
totalRead += read;
|
||
}
|
||
|
||
var payloadLength = BitConverter.ToInt32(lengthBuffer, 0);
|
||
if (payloadLength <= 0 || payloadLength > 1024 * 1024) // 最大 1MB 单条消息
|
||
{
|
||
// 无效长度,跳过此连接
|
||
return;
|
||
}
|
||
|
||
// 2. 读取消息正文
|
||
var payloadBuffer = ArrayPool<byte>.Shared.Rent(payloadLength);
|
||
try
|
||
{
|
||
totalRead = 0;
|
||
while (totalRead < payloadLength)
|
||
{
|
||
var read = await pipe.ReadAsync(payloadBuffer.AsMemory(totalRead, payloadLength - totalRead), cancellationToken);
|
||
if (read == 0)
|
||
{
|
||
return;
|
||
}
|
||
totalRead += read;
|
||
}
|
||
|
||
// 3. 反序列化并回调
|
||
var json = System.Text.Encoding.UTF8.GetString(payloadBuffer, 0, payloadLength);
|
||
var message = JsonSerializer.Deserialize(json, AppJsonContext.Default.StartupProgressMessage);
|
||
if (message is not null)
|
||
{
|
||
_onProgress(message);
|
||
}
|
||
}
|
||
catch (JsonException)
|
||
{
|
||
// 忽略解析错误,继续读取下一条消息
|
||
}
|
||
finally
|
||
{
|
||
ArrayPool<byte>.Shared.Return(payloadBuffer);
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
ArrayPool<byte>.Shared.Return(lengthBuffer);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止 IPC 服务端
|
||
/// </summary>
|
||
public void Stop()
|
||
{
|
||
_cts.Cancel();
|
||
try
|
||
{
|
||
_currentPipe?.Dispose();
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
Stop();
|
||
_cts.Dispose();
|
||
|
||
try
|
||
{
|
||
_listenTask?.Wait(TimeSpan.FromSeconds(2));
|
||
}
|
||
catch { }
|
||
}
|
||
}
|