mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
feat.airapp剥离启动器
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
using dotnetCampus.Ipc.CompilerServices.Attributes;
|
||||
|
||||
namespace LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
|
||||
[IpcPublic(IgnoresIpcException = true)]
|
||||
public interface IAirAppRuntimeControlService
|
||||
{
|
||||
Task<AirAppRuntimeControlResult> AttachHostAsync(int hostProcessId);
|
||||
|
||||
Task<AirAppRuntimeStatus> GetStatusAsync();
|
||||
}
|
||||
|
||||
public sealed record AirAppRuntimeControlResult(
|
||||
bool Accepted,
|
||||
string Code,
|
||||
string Message,
|
||||
AirAppRuntimeStatus Status);
|
||||
|
||||
public sealed record AirAppRuntimeStatus(
|
||||
int ProcessId,
|
||||
int LauncherProcessId,
|
||||
int HostProcessId,
|
||||
bool LauncherProcessAlive,
|
||||
bool HostProcessAlive,
|
||||
bool HasLiveAirApps,
|
||||
DateTimeOffset StartedAtUtc,
|
||||
DateTimeOffset UpdatedAtUtc);
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LanMountainDesktop.Shared.IPC;
|
||||
|
||||
public static class AirAppRuntimeDataRootResolver
|
||||
{
|
||||
private const string LauncherDataFolderName = ".Launcher";
|
||||
private const string ConfigFileName = "data-location.config.json";
|
||||
private const string DesktopFolderName = "Desktop";
|
||||
|
||||
public static string ResolveDataRoot(string? appRoot)
|
||||
{
|
||||
var defaultSystemDataPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"LanMountainDesktop");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(appRoot))
|
||||
{
|
||||
return defaultSystemDataPath;
|
||||
}
|
||||
|
||||
var normalizedAppRoot = Path.GetFullPath(appRoot);
|
||||
var configPath = Path.Combine(normalizedAppRoot, LauncherDataFolderName, ConfigFileName);
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
return defaultSystemDataPath;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(File.ReadAllText(configPath));
|
||||
var root = document.RootElement;
|
||||
var mode = GetString(root, "dataLocationMode");
|
||||
|
||||
if (string.Equals(mode, "Portable", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Path.GetFullPath(
|
||||
GetString(root, "portableDataPath")
|
||||
?? Path.Combine(normalizedAppRoot, DesktopFolderName));
|
||||
}
|
||||
|
||||
return Path.GetFullPath(GetString(root, "systemDataPath") ?? defaultSystemDataPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultSystemDataPath;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetString(JsonElement element, string propertyName)
|
||||
{
|
||||
foreach (var property in element.EnumerateObject())
|
||||
{
|
||||
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase) &&
|
||||
property.Value.ValueKind is JsonValueKind.String)
|
||||
{
|
||||
var value = property.Value.GetString();
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
77
LanMountainDesktop.Shared.IPC/AirAppRuntimePathResolver.cs
Normal file
77
LanMountainDesktop.Shared.IPC/AirAppRuntimePathResolver.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
namespace LanMountainDesktop.Shared.IPC;
|
||||
|
||||
public static class AirAppRuntimePathResolver
|
||||
{
|
||||
private const string WindowsExecutableName = "LanMountainDesktop.AirAppRuntime.exe";
|
||||
private const string UnixExecutableName = "LanMountainDesktop.AirAppRuntime";
|
||||
private const string DllName = "LanMountainDesktop.AirAppRuntime.dll";
|
||||
|
||||
private static string ExecutableName => OperatingSystem.IsWindows()
|
||||
? WindowsExecutableName
|
||||
: UnixExecutableName;
|
||||
|
||||
public static string? ResolveExecutablePath(string? appRoot = null, string? hostBaseDirectory = null)
|
||||
{
|
||||
return EnumerateCandidates(appRoot, hostBaseDirectory)
|
||||
.Select(Path.GetFullPath)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.FirstOrDefault(File.Exists);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> EnumerateCandidates(string? appRoot = null, string? hostBaseDirectory = null)
|
||||
{
|
||||
foreach (var root in EnumerateRoots(appRoot, hostBaseDirectory))
|
||||
{
|
||||
yield return Path.Combine(root, ExecutableName);
|
||||
yield return Path.Combine(root, DllName);
|
||||
yield return Path.Combine(root, "AirAppRuntime", ExecutableName);
|
||||
yield return Path.Combine(root, "AirAppRuntime", DllName);
|
||||
}
|
||||
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
for (var depth = 0; depth < 8 && current is not null; depth++, current = current.Parent)
|
||||
{
|
||||
yield return Path.Combine(
|
||||
current.FullName,
|
||||
"LanMountainDesktop.AirAppRuntime",
|
||||
"bin",
|
||||
#if DEBUG
|
||||
"Debug",
|
||||
#else
|
||||
"Release",
|
||||
#endif
|
||||
"net10.0",
|
||||
ExecutableName);
|
||||
|
||||
yield return Path.Combine(
|
||||
current.FullName,
|
||||
"LanMountainDesktop.AirAppRuntime",
|
||||
"bin",
|
||||
#if DEBUG
|
||||
"Debug",
|
||||
#else
|
||||
"Release",
|
||||
#endif
|
||||
"net10.0",
|
||||
DllName);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateRoots(string? appRoot, string? hostBaseDirectory)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(appRoot))
|
||||
{
|
||||
yield return Path.GetFullPath(appRoot);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(hostBaseDirectory))
|
||||
{
|
||||
var hostDirectory = Path.GetFullPath(hostBaseDirectory);
|
||||
yield return hostDirectory;
|
||||
yield return Path.GetFullPath(Path.Combine(hostDirectory, ".."));
|
||||
}
|
||||
|
||||
yield return AppContext.BaseDirectory;
|
||||
yield return Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, ".."));
|
||||
}
|
||||
}
|
||||
101
LanMountainDesktop.Shared.IPC/AirAppRuntimeProcessStarter.cs
Normal file
101
LanMountainDesktop.Shared.IPC/AirAppRuntimeProcessStarter.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace LanMountainDesktop.Shared.IPC;
|
||||
|
||||
public sealed record AirAppRuntimeStartRequest(
|
||||
string? AppRoot,
|
||||
int LauncherProcessId,
|
||||
int RequesterProcessId,
|
||||
string? DataRoot);
|
||||
|
||||
public static class AirAppRuntimeProcessStarter
|
||||
{
|
||||
public static Process? Start(AirAppRuntimeStartRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var runtimePath = AirAppRuntimePathResolver.ResolveExecutablePath(
|
||||
request.AppRoot,
|
||||
AppContext.BaseDirectory);
|
||||
if (string.IsNullOrWhiteSpace(runtimePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var startInfo = CreateStartInfo(runtimePath);
|
||||
AddOptionalArgument(startInfo, "--app-root", request.AppRoot);
|
||||
AddOptionalArgument(startInfo, "--data-root", request.DataRoot);
|
||||
AddIntArgument(startInfo, "--launcher-pid", request.LauncherProcessId);
|
||||
AddIntArgument(startInfo, "--requester-pid", request.RequesterProcessId);
|
||||
return Process.Start(startInfo);
|
||||
}
|
||||
|
||||
public static ProcessStartInfo CreateStartInfo(string runtimePath)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(runtimePath);
|
||||
var fullPath = Path.GetFullPath(runtimePath);
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = Path.GetDirectoryName(fullPath) ?? AppContext.BaseDirectory
|
||||
};
|
||||
|
||||
var extension = Path.GetExtension(fullPath);
|
||||
if (OperatingSystem.IsWindows() &&
|
||||
string.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
startInfo.FileName = ResolveDotNetHostPath();
|
||||
startInfo.ArgumentList.Add(fullPath);
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
if (!OperatingSystem.IsWindows() &&
|
||||
string.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
startInfo.FileName = "dotnet";
|
||||
startInfo.ArgumentList.Add(fullPath);
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
startInfo.FileName = fullPath;
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
private static string ResolveDotNetHostPath()
|
||||
{
|
||||
var programFiles = Environment.GetEnvironmentVariable("ProgramW6432") ??
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
var programFilesCandidate = Path.Combine(programFiles, "dotnet", "dotnet.exe");
|
||||
if (File.Exists(programFilesCandidate))
|
||||
{
|
||||
return programFilesCandidate;
|
||||
}
|
||||
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var perUserCandidate = Path.Combine(localAppData, "dotnet", "dotnet.exe");
|
||||
return File.Exists(perUserCandidate) ? perUserCandidate : "dotnet";
|
||||
}
|
||||
|
||||
private static void AddOptionalArgument(ProcessStartInfo startInfo, string name, string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
startInfo.ArgumentList.Add(name);
|
||||
startInfo.ArgumentList.Add(Path.GetFullPath(value));
|
||||
}
|
||||
|
||||
private static void AddIntArgument(ProcessStartInfo startInfo, string name, int value)
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
startInfo.ArgumentList.Add(name);
|
||||
startInfo.ArgumentList.Add(value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,10 @@ public static class IpcConstants
|
||||
|
||||
public const string ProtocolVersion = "external-ipc-public-api.v1";
|
||||
|
||||
public const string AirAppLifecyclePipeName = "LanMountainDesktop.Launcher.AirApp.v1";
|
||||
public const string AirAppRuntimePipeName = "LanMountainDesktop.AirAppRuntime.v1";
|
||||
|
||||
[Obsolete("Use AirAppRuntimePipeName. The lifecycle service is now hosted by LanMountainDesktop.AirAppRuntime.")]
|
||||
public const string AirAppLifecyclePipeName = AirAppRuntimePipeName;
|
||||
|
||||
public const string AirAppLifecycleProtocolVersion = "air-app-lifecycle.v1";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user