2026-06-05 11:08:11 +08:00
|
|
|
using System.Diagnostics;
|
2026-05-31 19:41:10 +08:00
|
|
|
using LanMountainDesktop.Shared.IPC;
|
|
|
|
|
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
|
|
|
|
|
|
|
|
|
namespace LanMountainDesktop.Launcher.Shell;
|
|
|
|
|
|
|
|
|
|
internal sealed class AirAppRuntimeBridge
|
|
|
|
|
{
|
|
|
|
|
private const int ConnectAttempts = 8;
|
|
|
|
|
|
|
|
|
|
private readonly string _appRoot;
|
|
|
|
|
private readonly string? _dataRoot;
|
|
|
|
|
|
|
|
|
|
public AirAppRuntimeBridge(string appRoot, string? dataRoot)
|
|
|
|
|
{
|
|
|
|
|
_appRoot = appRoot;
|
|
|
|
|
_dataRoot = dataRoot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task EnsureStartedAsync()
|
|
|
|
|
{
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Info($"AIRAPP: Checking if AirApp Runtime is available. AppRoot='{_appRoot}'");
|
|
|
|
|
|
2026-05-31 19:41:10 +08:00
|
|
|
if (await TryGetStatusAsync().ConfigureAwait(false) is not null)
|
|
|
|
|
{
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Info("AIRAPP: AirApp Runtime is already available.");
|
2026-05-31 19:41:10 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Info("AIRAPP: Starting AirApp Runtime...");
|
2026-06-05 11:08:11 +08:00
|
|
|
Process? process;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
process = AirAppRuntimeProcessStarter.Start(new AirAppRuntimeStartRequest(
|
|
|
|
|
_appRoot,
|
|
|
|
|
Environment.ProcessId,
|
|
|
|
|
0,
|
|
|
|
|
_dataRoot));
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Warn($"AIRAPP: AirApp Runtime start request failed. AppRoot='{_appRoot}'; Error='{ex.Message}'");
|
2026-06-05 11:08:11 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Info($"AIRAPP: AirApp Runtime start requested. Pid={(process is null ? -1 : process.Id)}; AppRoot='{_appRoot}'.");
|
2026-05-31 19:41:10 +08:00
|
|
|
|
|
|
|
|
for (var attempt = 1; attempt <= ConnectAttempts; attempt++)
|
|
|
|
|
{
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Info($"AIRAPP: Attempt {attempt}/{ConnectAttempts} - Checking IPC connection...");
|
|
|
|
|
|
2026-05-31 19:41:10 +08:00
|
|
|
if (await TryGetStatusAsync().ConfigureAwait(false) is not null)
|
|
|
|
|
{
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Info("AIRAPP: AirApp Runtime IPC is ready.");
|
2026-05-31 19:41:10 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 12:59:36 +08:00
|
|
|
var delayMs = 250 * attempt;
|
|
|
|
|
Logger.Info($"AIRAPP: IPC not ready, waiting {delayMs}ms before retry...");
|
|
|
|
|
await Task.Delay(TimeSpan.FromMilliseconds(delayMs)).ConfigureAwait(false);
|
2026-05-31 19:41:10 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Warn("AIRAPP: AirApp Runtime did not become ready after pre-start; Host fallback remains available.");
|
2026-05-31 19:41:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task AttachHostAsync(int hostProcessId)
|
|
|
|
|
{
|
|
|
|
|
if (hostProcessId <= 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-06-14 12:59:36 +08:00
|
|
|
using var cts = new CancellationTokenSource();
|
2026-05-31 19:41:10 +08:00
|
|
|
using var client = new LanMountainDesktopIpcClient();
|
2026-06-14 12:59:36 +08:00
|
|
|
|
|
|
|
|
var connectTask = client.ConnectAsync(IpcConstants.AirAppRuntimePipeName);
|
|
|
|
|
await connectTask.WaitAsync(TimeSpan.FromSeconds(3), cts.Token).ConfigureAwait(false);
|
|
|
|
|
|
2026-05-31 19:41:10 +08:00
|
|
|
var proxy = client.CreateProxy<IAirAppRuntimeControlService>();
|
2026-06-14 12:59:36 +08:00
|
|
|
var attachTask = proxy.AttachHostAsync(hostProcessId);
|
|
|
|
|
var result = await attachTask.WaitAsync(TimeSpan.FromSeconds(3), cts.Token).ConfigureAwait(false);
|
2026-05-31 19:41:10 +08:00
|
|
|
Logger.Info($"AirApp Runtime host attach completed. Accepted={result.Accepted}; Code='{result.Code}'; HostPid={hostProcessId}.");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.Warn($"Failed to attach Host to AirApp Runtime: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<AirAppRuntimeStatus?> TryGetStatusAsync()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-06-14 12:59:36 +08:00
|
|
|
using var cts = new CancellationTokenSource();
|
2026-05-31 19:41:10 +08:00
|
|
|
using var client = new LanMountainDesktopIpcClient();
|
2026-06-14 12:59:36 +08:00
|
|
|
|
|
|
|
|
var connectTask = client.ConnectAsync(IpcConstants.AirAppRuntimePipeName);
|
|
|
|
|
await connectTask.WaitAsync(TimeSpan.FromSeconds(2), cts.Token).ConfigureAwait(false);
|
|
|
|
|
|
2026-05-31 19:41:10 +08:00
|
|
|
var proxy = client.CreateProxy<IAirAppRuntimeControlService>();
|
2026-06-14 12:59:36 +08:00
|
|
|
var statusTask = proxy.GetStatusAsync();
|
|
|
|
|
return await statusTask.WaitAsync(TimeSpan.FromSeconds(2), cts.Token).ConfigureAwait(false);
|
2026-05-31 19:41:10 +08:00
|
|
|
}
|
2026-06-14 12:59:36 +08:00
|
|
|
catch (TimeoutException)
|
|
|
|
|
{
|
|
|
|
|
Logger.Info("AIRAPP: TryGetStatusAsync timed out (2s).");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
{
|
|
|
|
|
Logger.Info("AIRAPP: TryGetStatusAsync cancelled.");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
2026-05-31 19:41:10 +08:00
|
|
|
{
|
2026-06-14 12:59:36 +08:00
|
|
|
Logger.Info($"AIRAPP: TryGetStatusAsync failed: {ex.GetType().Name} - {ex.Message}");
|
2026-05-31 19:41:10 +08:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|