Add external public IPC host/client and plugin SDK

Introduce a new LanMountainDesktop.Shared.IPC project implementing a public IPC host and client (LanMountainDesktopIpcClient, PublicIpcHostService), IPC constants and routed notify IDs, DTOs and DI helpers for registering public services. Update Plugin SDK to allow plugins to contribute public IPC services and registrations, add related descriptors/records and extension helpers. Migrate Launcher/App to use the new public IPC for startup/loading notifications and wiring (including TryConnect helper), switch LoadingStateReporter to use the external notification publisher, and add host-side public services (app info, shell control, plugin catalog). Include integration tests and spec/checklist/docs for the external IPC public API.
This commit is contained in:
lincube
2026-04-22 14:55:30 +08:00
parent f51ec309a6
commit aa7c118d13
43 changed files with 1347 additions and 49 deletions

View File

@@ -0,0 +1,29 @@
using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Services.ExternalIpc;
internal sealed class PublicAppInfoService : IPublicAppInfoService
{
private readonly string _version;
private readonly string _codename;
private readonly DateTimeOffset _startedAt;
public PublicAppInfoService(string version, string codename, DateTimeOffset startedAt)
{
_version = version;
_codename = codename;
_startedAt = startedAt;
}
public PublicAppInfoSnapshot GetAppInfo()
{
return new PublicAppInfoSnapshot(
"LanMountainDesktop",
_version,
_codename,
IpcConstants.DefaultPipeName,
Environment.ProcessId,
_startedAt);
}
}

View File

@@ -0,0 +1,19 @@
using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Services.ExternalIpc;
internal sealed class PublicPluginCatalogService : IPublicPluginCatalogService
{
private readonly PublicIpcHostService _publicIpcHostService;
public PublicPluginCatalogService(PublicIpcHostService publicIpcHostService)
{
_publicIpcHostService = publicIpcHostService;
}
public PublicIpcCatalogSnapshot GetCatalog()
{
return _publicIpcHostService.GetCatalogSnapshot();
}
}

View File

@@ -0,0 +1,47 @@
using Avalonia;
using Avalonia.Threading;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Services.ExternalIpc;
internal sealed class PublicShellControlService : IPublicShellControlService
{
public Task<bool> ActivateMainWindowAsync()
{
return Dispatcher.UIThread.InvokeAsync(() =>
{
return (Application.Current as App)?.TryActivateMainWindowFromExternalIpc("PublicIpc") == true;
}).GetTask();
}
public Task<bool> OpenSettingsAsync(string? pageTag = null)
{
return Dispatcher.UIThread.InvokeAsync(() =>
{
if (Application.Current is not App app)
{
return false;
}
app.OpenIndependentSettingsModule("PublicIpc", pageTag);
return true;
}).GetTask();
}
public Task<bool> RestartAsync()
{
var lifecycle = App.CurrentHostApplicationLifecycle;
return Task.FromResult(lifecycle?.TryRestart(new HostApplicationLifecycleRequest(
Source: "PublicIpc",
Reason: "External IPC requested restart.")) == true);
}
public Task<bool> ExitAsync()
{
var lifecycle = App.CurrentHostApplicationLifecycle;
return Task.FromResult(lifecycle?.TryExit(new HostApplicationLifecycleRequest(
Source: "PublicIpc",
Reason: "External IPC requested exit.")) == true);
}
}

View File

@@ -1,6 +1,6 @@
using System.Timers;
using LanMountainDesktop.Services.Launcher;
using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC;
namespace LanMountainDesktop.Services.Loading;
@@ -10,7 +10,7 @@ namespace LanMountainDesktop.Services.Loading;
public class LoadingStateReporter : IDisposable
{
private readonly LoadingStateManager _manager;
private readonly LauncherIpcClient? _ipcClient;
private readonly IExternalIpcNotificationPublisher? _notificationPublisher;
private readonly System.Timers.Timer _reportTimer;
private readonly object _lock = new();
private bool _isDisposed;
@@ -36,10 +36,10 @@ public class LoadingStateReporter : IDisposable
public LoadingStateReporter(
LoadingStateManager manager,
LauncherIpcClient? ipcClient = null)
IExternalIpcNotificationPublisher? notificationPublisher = null)
{
_manager = manager ?? throw new ArgumentNullException(nameof(manager));
_ipcClient = ipcClient;
_notificationPublisher = notificationPublisher;
// 创建定时上报定时器
_reportTimer = new System.Timers.Timer(ReportIntervalMs);
@@ -80,7 +80,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
public async Task ReportImmediatelyAsync()
{
if (_isDisposed || _ipcClient == null) return;
if (_isDisposed || _notificationPublisher == null) return;
var message = CreateDetailedProgressMessage();
await SendMessageAsync(message);
@@ -91,7 +91,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
public async Task ReportItemProgressAsync(string itemId, int percent, string? message = null)
{
if (_isDisposed || _ipcClient == null) return;
if (_isDisposed || _notificationPublisher == null) return;
var item = _manager.GetAllItems().FirstOrDefault(i => i.Id == itemId);
if (item == null) return;
@@ -121,7 +121,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
public async Task ReportStageChangeAsync(StartupStage stage, string? message = null)
{
if (_isDisposed || _ipcClient == null) return;
if (_isDisposed || _notificationPublisher == null) return;
var progressMessage = new DetailedProgressMessage
{
@@ -140,7 +140,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
public async Task ReportErrorAsync(string errorMessage, string? details = null)
{
if (_isDisposed || _ipcClient == null) return;
if (_isDisposed || _notificationPublisher == null) return;
var fullMessage = string.IsNullOrEmpty(details)
? errorMessage
@@ -280,7 +280,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
private async Task SendMessageAsync(DetailedProgressMessage message)
{
if (_ipcClient == null) return;
if (_notificationPublisher == null) return;
// 检查最小上报间隔
var now = DateTimeOffset.UtcNow;
@@ -293,15 +293,15 @@ public class LoadingStateReporter : IDisposable
try
{
// 转换为 StartupProgressMessage 以保持兼容性
var baseMessage = new StartupProgressMessage
var loadingStateMessage = _manager.GetLoadingStateMessage() with
{
Stage = message.Stage,
ProgressPercent = message.ProgressPercent,
OverallProgressPercent = message.ProgressPercent,
Message = FormatMessage(message),
Timestamp = DateTimeOffset.UtcNow
};
await _ipcClient.ReportProgressAsync(baseMessage);
await _notificationPublisher.NotifyAsync(IpcRoutedNotifyIds.LauncherLoadingState, loadingStateMessage);
_lastReportTime = DateTimeOffset.UtcNow;
}
catch (Exception ex)