mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 15:44:25 +08:00
refactor(launcher): reorganize into responsibility folders
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Launcher.Services.AirApp;
|
||||
namespace LanMountainDesktop.Launcher.AirApp;
|
||||
|
||||
internal sealed class AirAppHostLocator
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Launcher.Services.AirApp;
|
||||
namespace LanMountainDesktop.Launcher.AirApp;
|
||||
|
||||
internal static class AirAppInstanceKey
|
||||
{
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services.AirApp;
|
||||
namespace LanMountainDesktop.Launcher.AirApp;
|
||||
|
||||
internal interface IAirAppProcessStarter
|
||||
{
|
||||
@@ -60,7 +58,7 @@ internal sealed class AirAppProcessStarter : IAirAppProcessStarter
|
||||
AddArgument(startInfo, "--source-placement-id", sourcePlacementId.Trim());
|
||||
}
|
||||
|
||||
LanMountainDesktop.Launcher.Services.Logger.Info(
|
||||
Logger.Info(
|
||||
$"Starting AirAppHost. AppId='{appId}'; InstanceKey='{instanceKey}'; HostPath='{hostPath}'; DataRoot='{dataRoot ?? string.Empty}'.");
|
||||
var process = Process.Start(startInfo);
|
||||
if (process is not null)
|
||||
@@ -70,12 +68,12 @@ internal sealed class AirAppProcessStarter : IAirAppProcessStarter
|
||||
{
|
||||
try
|
||||
{
|
||||
LanMountainDesktop.Launcher.Services.Logger.Info(
|
||||
Logger.Info(
|
||||
$"AirAppHost exited. AppId='{appId}'; InstanceKey='{instanceKey}'; ProcessId={process.Id}; ExitCode={process.ExitCode}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LanMountainDesktop.Launcher.Services.Logger.Warn($"Failed to log AirAppHost exit: {ex.Message}");
|
||||
Logger.Warn($"Failed to log AirAppHost exit: {ex.Message}");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using LanMountainDesktop.Shared.IPC;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services.AirApp;
|
||||
namespace LanMountainDesktop.Launcher.AirApp;
|
||||
|
||||
internal sealed class LauncherAirAppLifecycleIpcHost : IDisposable
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services.AirApp;
|
||||
namespace LanMountainDesktop.Launcher.AirApp;
|
||||
|
||||
internal sealed class LauncherAirAppLifecycleService : IAirAppLifecycleService
|
||||
{
|
||||
@@ -7,8 +7,6 @@ using Avalonia.Threading;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Resources;
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Launcher.Services.AirApp;
|
||||
using LanMountainDesktop.Launcher.Services.Ipc;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC;
|
||||
@@ -70,12 +68,12 @@ public partial class App : Application
|
||||
return;
|
||||
}
|
||||
|
||||
// 调试模式:只显示 DevDebugWindow,不走正常启动流程
|
||||
// 避免启动主程序后 Launcher 自动退出,导致开发者无法预览 UI
|
||||
// è°?è¯?模å¼<C3A5>ï¼?å<>ªæ?¾ç¤º DevDebugWindowï¼?ä¸<C3A4>èµ°æ£å¸¸å<C2B8>¯å?¨æµ<C3A6>ç¨?
|
||||
// é<EFBFBD>¿å?<3F>å<EFBFBD>¯å?¨ä¸»ç¨?åº<C3A5>å<EFBFBD>? Launcher è?ªå?¨é??å?ºï¼?导è?´å¼?å<>?è??æ? æ³?é¢?è§? UI
|
||||
if (context.IsDebugMode && !context.IsPreviewCommand &&
|
||||
!string.Equals(context.Command, "apply-update", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Info("Debug mode active — showing DevDebugWindow instead of normal launch flow.");
|
||||
Logger.Info("Debug mode active â?? showing DevDebugWindow instead of normal launch flow.");
|
||||
var devDebugWindow = new DevDebugWindow();
|
||||
devDebugWindow.Show();
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Launcher.Plugins;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
|
||||
@@ -30,15 +30,10 @@ namespace LanMountainDesktop.Launcher;
|
||||
[JsonSerializable(typeof(PublicTaskbarStatus))]
|
||||
[JsonSerializable(typeof(PublicShellActivationResult))]
|
||||
[JsonSerializable(typeof(LauncherResult))]
|
||||
[JsonSerializable(typeof(HostDiscoveryConfig))]
|
||||
[JsonSerializable(typeof(PluginManifest))]
|
||||
[JsonSerializable(typeof(PendingUpgrade))]
|
||||
[JsonSerializable(typeof(List<PendingUpgrade>))]
|
||||
[JsonSerializable(typeof(OobeStateFile))]
|
||||
[JsonSerializable(typeof(DataLocationConfig))]
|
||||
[JsonSerializable(typeof(GitHubRelease))]
|
||||
[JsonSerializable(typeof(GitHubAsset))]
|
||||
[JsonSerializable(typeof(List<GitHubRelease>))]
|
||||
[JsonSerializable(typeof(StartupAttemptRecord))]
|
||||
[JsonSerializable(typeof(PrivacyConfig))]
|
||||
[JsonSerializable(typeof(PrivacyAgreementState))]
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Deployment;
|
||||
|
||||
internal sealed class DeploymentLocator
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Deployment;
|
||||
|
||||
/// <summary>
|
||||
/// 主程序发现选项
|
||||
@@ -1,6 +1,6 @@
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Deployment;
|
||||
|
||||
internal sealed record HostLaunchPlan(
|
||||
string HostPath,
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Deployment;
|
||||
|
||||
internal sealed class HostResolutionResult
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Deployment;
|
||||
|
||||
/// <summary>
|
||||
/// 老版本检测器 - 检测 0.8.x 及更早的单应用模式安装
|
||||
9
LanMountainDesktop.Launcher/GlobalUsings.cs
Normal file
9
LanMountainDesktop.Launcher/GlobalUsings.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
global using LanMountainDesktop.Launcher.AirApp;
|
||||
global using LanMountainDesktop.Launcher.Deployment;
|
||||
global using LanMountainDesktop.Launcher.Infrastructure;
|
||||
global using LanMountainDesktop.Launcher.Ipc;
|
||||
global using LanMountainDesktop.Launcher.Oobe;
|
||||
global using LanMountainDesktop.Launcher.Plugins;
|
||||
global using LanMountainDesktop.Launcher.Startup;
|
||||
global using LanMountainDesktop.Launcher.Update;
|
||||
global using LanMountainDesktop.Launcher.Services;
|
||||
@@ -2,7 +2,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
internal static class Commands
|
||||
{
|
||||
@@ -171,16 +171,12 @@ internal static class Commands
|
||||
? launcherDir
|
||||
: AppContext.BaseDirectory);
|
||||
|
||||
// 发布版结构:Launcher 和 app-* 目录在同一目录
|
||||
// 检查当前目录是否有 app-* 子目录(发布版)
|
||||
var appDirs = Directory.GetDirectories(baseDir, "app-*", SearchOption.TopDirectoryOnly);
|
||||
if (appDirs.Length > 0)
|
||||
{
|
||||
// 找到 app-* 目录,说明是发布版结构
|
||||
return baseDir;
|
||||
}
|
||||
|
||||
// 开发环境:检查父目录是否有主程序
|
||||
var parent = Path.GetFullPath(Path.Combine(baseDir, ".."));
|
||||
var parentHost = OperatingSystem.IsWindows()
|
||||
? Path.Combine(parent, "LanMountainDesktop.exe")
|
||||
@@ -190,7 +186,6 @@ internal static class Commands
|
||||
return parent;
|
||||
}
|
||||
|
||||
// 默认返回 baseDir
|
||||
return baseDir;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// 解析应用数据目录位置。
|
||||
@@ -1,7 +1,7 @@
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
internal sealed class DeferredSplashStageReporter : ISplashStageReporter
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
internal enum DotNetRuntimeArchitecture
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
internal interface ISplashStageReporter
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
internal static class LanguagePreferenceService
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// 启动器背景图片服务
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
internal sealed record LauncherDebugSettings(bool DevModeEnabled, string? CustomHostPath);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Security.Principal;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
internal static class LauncherExecutionContext
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// 简单的日志记录器 - 同时输出到控制台和文件
|
||||
@@ -2,7 +2,7 @@ using Avalonia;
|
||||
using Avalonia.Styling;
|
||||
using FluentAvalonia.Styling;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// 主题服务,管理启动器的主题设置
|
||||
@@ -3,7 +3,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services.Ipc;
|
||||
namespace LanMountainDesktop.Launcher.Ipc;
|
||||
|
||||
internal sealed class LauncherCoordinatorIpcClient
|
||||
{
|
||||
@@ -4,7 +4,7 @@ using System.Text.Json;
|
||||
using System.IO.Pipes;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services.Ipc;
|
||||
namespace LanMountainDesktop.Launcher.Ipc;
|
||||
|
||||
internal sealed class LauncherCoordinatorIpcServer : IDisposable
|
||||
{
|
||||
@@ -2,10 +2,9 @@ using System.Buffers;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Shared.Contracts.Update;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services.Ipc;
|
||||
namespace LanMountainDesktop.Launcher.Ipc;
|
||||
|
||||
internal sealed class LauncherUpdateProgressIpcServer : IUpdateProgressReporter, IDisposable
|
||||
{
|
||||
@@ -18,6 +18,7 @@
|
||||
<ItemGroup>
|
||||
<!-- 只引用 Shared.Contracts(IPC 协议) -->
|
||||
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
|
||||
<ProjectReference Include="..\LanMountainDesktop.PluginPackaging\LanMountainDesktop.PluginPackaging.csproj" />
|
||||
<ProjectReference Include="..\LanMountainDesktop.Shared.IPC\LanMountainDesktop.Shared.IPC.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ using Avalonia.Threading;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
internal sealed class DataLocationOobeStep : IOobeStep
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
/// <summary>
|
||||
/// 在 OOBE 中向 Host 的 settings.json 写入启动与展示相关字段,属性名与 Host
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
internal interface IOobeStep
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
/// <summary>
|
||||
/// 将当前 Windows 用户登录时自启动项指向<strong>本 Launcher 进程</strong>(与正式入口一致)。
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
internal sealed class OobeStateService
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
/// <summary>
|
||||
/// 隐私协议同意状态管理服务(带防篡改保护)
|
||||
@@ -1,7 +1,7 @@
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
internal sealed class WelcomeOobeStep : IOobeStep
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System.IO.Compression;
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Plugins;
|
||||
|
||||
/// <summary>
|
||||
/// 插件安装服务 - 简化版,不依赖 PluginSdk
|
||||
@@ -290,7 +290,7 @@ internal sealed class PluginInstallerService
|
||||
/// <summary>
|
||||
/// 简化的插件清单模型
|
||||
/// </summary>
|
||||
public class PluginManifest
|
||||
internal class PluginManifest
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Plugins;
|
||||
|
||||
internal sealed class PluginUpgradeQueueService
|
||||
{
|
||||
@@ -29,7 +29,7 @@ internal sealed class PluginUpgradeQueueService
|
||||
}
|
||||
|
||||
var text = File.ReadAllText(pendingPath);
|
||||
var pending = JsonSerializer.Deserialize(text, AppJsonContext.Default.ListPendingUpgrade) ?? [];
|
||||
var pending = JsonSerializer.Deserialize<List<PendingUpgrade>>(text, AppJsonContext.Default.Options) ?? [];
|
||||
var failures = new List<string>();
|
||||
var succeeded = new List<PendingUpgrade>();
|
||||
|
||||
@@ -63,7 +63,7 @@ internal sealed class PluginUpgradeQueueService
|
||||
}
|
||||
else
|
||||
{
|
||||
File.WriteAllText(pendingPath, JsonSerializer.Serialize(remaining, AppJsonContext.Default.ListPendingUpgrade));
|
||||
File.WriteAllText(pendingPath, JsonSerializer.Serialize(remaining, AppJsonContext.Default.Options));
|
||||
}
|
||||
|
||||
return new LauncherResult
|
||||
@@ -1,7 +1,5 @@
|
||||
using Avalonia;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
|
||||
namespace LanMountainDesktop.Launcher;
|
||||
|
||||
public static class Program
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Diagnostics;
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Resources;
|
||||
using LanMountainDesktop.Launcher.Services.Ipc;
|
||||
using LanMountainDesktop.Launcher.Startup;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
@@ -185,115 +184,18 @@ internal sealed partial class LauncherFlowCoordinator
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<PublicShellStatus?> TryGetPublicShellStatusAsync(
|
||||
LanMountainDesktopIpcClient ipcClient) =>
|
||||
await HostStartupMonitor.TryGetPublicShellStatusAsync(ipcClient).ConfigureAwait(false);
|
||||
|
||||
private static async Task<StartupSuccessState?> TryRecoverActivationThroughExistingHostAsync(
|
||||
LanMountainDesktopIpcClient ipcClient,
|
||||
StartupSuccessTracker startupSuccessTracker,
|
||||
TimeSpan timeout)
|
||||
{
|
||||
var activation = await TryActivateExistingHostWithStatusAsync(ipcClient, timeout).ConfigureAwait(false);
|
||||
if (activation is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (startupSuccessTracker.TryResolve(activation.Status, out var shellSuccess))
|
||||
{
|
||||
return shellSuccess;
|
||||
}
|
||||
|
||||
if (activation.Accepted)
|
||||
{
|
||||
return startupSuccessTracker.BuildRecoverySuccessState();
|
||||
}
|
||||
|
||||
return HostActivationPolicy.IsRecoverableActivationFailure(activation)
|
||||
? new StartupSuccessState(
|
||||
StartupStage.Ready,
|
||||
"startup_pending",
|
||||
activation.Message)
|
||||
: null;
|
||||
}
|
||||
|
||||
private static async Task<PublicShellStatus?> TryGetPublicShellStatusAsync(
|
||||
LanMountainDesktopIpcClient ipcClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
|
||||
return await shellProxy.GetShellStatusAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn($"Failed to query public shell status: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<StartupSuccessState?> TryRecoverWithPublicActivationAsync(
|
||||
LanMountainDesktopIpcClient ipcClient,
|
||||
Process hostProcess,
|
||||
Task<StartupSuccessState> successTask,
|
||||
StartupSuccessTracker startupSuccessTracker)
|
||||
{
|
||||
try
|
||||
{
|
||||
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
|
||||
var activation = await shellProxy.ActivateMainWindowWithStatusAsync().ConfigureAwait(false);
|
||||
StartupDiagnostics.TraceShellStatus("recovery_activation", activation.Status);
|
||||
if (startupSuccessTracker.TryResolve(activation.Status, out var shellSuccess))
|
||||
{
|
||||
return shellSuccess;
|
||||
}
|
||||
|
||||
var completedTask = await Task.WhenAny(successTask, Task.Delay(TimeSpan.FromSeconds(5))).ConfigureAwait(false);
|
||||
if (completedTask == successTask)
|
||||
{
|
||||
return await successTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!hostProcess.HasExited && (activation.Accepted || HostActivationPolicy.IsRecoverableActivationFailure(activation)))
|
||||
{
|
||||
return startupSuccessTracker.BuildRecoverySuccessState();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn($"Public activation recovery failed: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static LoadingStateMessage BuildDelayedLoadingState(
|
||||
LoadingStateMessage loadingState,
|
||||
string summaryMessage,
|
||||
string detailMessage,
|
||||
DateTimeOffset startedAtUtc)
|
||||
{
|
||||
var delayedItems = loadingState.ActiveItems
|
||||
.Where(item => !string.Equals(item.Id, "launcher-soft-timeout", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
delayedItems.Insert(0, new LoadingItem
|
||||
{
|
||||
Id = "launcher-soft-timeout",
|
||||
Type = LoadingItemType.System,
|
||||
Name = "Startup still in progress",
|
||||
Description = detailMessage,
|
||||
State = LoadingState.Delayed,
|
||||
ProgressPercent = Math.Max(loadingState.OverallProgressPercent, 1),
|
||||
Message = detailMessage,
|
||||
StartTime = startedAtUtc
|
||||
});
|
||||
|
||||
return loadingState with
|
||||
{
|
||||
ActiveItems = delayedItems,
|
||||
Message = summaryMessage,
|
||||
Timestamp = DateTimeOffset.UtcNow,
|
||||
TotalCount = Math.Max(loadingState.TotalCount, delayedItems.Count)
|
||||
};
|
||||
}
|
||||
TimeSpan timeout) =>
|
||||
await HostStartupMonitor.TryRecoverActivationThroughExistingHostAsync(
|
||||
ipcClient,
|
||||
startupSuccessTracker,
|
||||
timeout).ConfigureAwait(false);
|
||||
|
||||
private static Dictionary<string, string> BuildAttemptDetails(
|
||||
StartupAttemptRecord? trackedAttempt,
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Diagnostics;
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Resources;
|
||||
using LanMountainDesktop.Launcher.Services.Ipc;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC;
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Diagnostics;
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Resources;
|
||||
using LanMountainDesktop.Launcher.Services.Ipc;
|
||||
using LanMountainDesktop.Launcher.Startup;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
@@ -340,7 +339,7 @@ internal sealed partial class LauncherFlowCoordinator
|
||||
{
|
||||
softTimeoutShown = true;
|
||||
reporter.Report("delayed", SoftTimeoutStatusMessage);
|
||||
loadingState = BuildDelayedLoadingState(
|
||||
loadingState = HostStartupMonitor.BuildDelayedLoadingState(
|
||||
loadingState,
|
||||
SoftTimeoutStatusMessage,
|
||||
SoftTimeoutDetailsMessage,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Resources;
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Shared.IPC;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Startup;
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Startup;
|
||||
|
||||
internal sealed class StartupAttemptRegistry
|
||||
{
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using LanMountainDesktop.Shared.Contracts.Update;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Update;
|
||||
|
||||
public interface IUpdateProgressReporter
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using LanMountainDesktop.Shared.Contracts.Update;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Update;
|
||||
|
||||
internal sealed class NullUpdateProgressReporter : IUpdateProgressReporter
|
||||
{
|
||||
@@ -4,7 +4,7 @@ using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using ContractsUpdate = LanMountainDesktop.Shared.Contracts.Update;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Services;
|
||||
namespace LanMountainDesktop.Launcher.Update;
|
||||
|
||||
internal sealed class UpdateEngineService
|
||||
{
|
||||
@@ -28,7 +28,7 @@ public partial class OobeWindow : Window
|
||||
private bool _migrateExistingData;
|
||||
|
||||
// 主题选择
|
||||
private Services.ThemeMode _selectedThemeMode = Services.ThemeMode.Light;
|
||||
private ThemeMode _selectedThemeMode = ThemeMode.Light;
|
||||
private string _selectedAccentColor = "#0078D4";
|
||||
private MonetSource _selectedMonetSource = MonetSource.Wallpaper;
|
||||
|
||||
@@ -82,19 +82,19 @@ public partial class OobeWindow : Window
|
||||
// 浅色/深色模式选择
|
||||
if (this.FindControl<Border>("LightModeOption") is { } lightModeOption)
|
||||
{
|
||||
lightModeOption.PointerPressed += (s, e) => SelectThemeMode(Services.ThemeMode.Light);
|
||||
lightModeOption.PointerPressed += (s, e) => SelectThemeMode(ThemeMode.Light);
|
||||
}
|
||||
|
||||
if (this.FindControl<Border>("DarkModeOption") is { } darkModeOption)
|
||||
{
|
||||
darkModeOption.PointerPressed += (s, e) => SelectThemeMode(Services.ThemeMode.Dark);
|
||||
darkModeOption.PointerPressed += (s, e) => SelectThemeMode(ThemeMode.Dark);
|
||||
}
|
||||
|
||||
if (this.FindControl<RadioButton>("LightModeRadio") is { } lightModeRadio)
|
||||
{
|
||||
lightModeRadio.IsCheckedChanged += (s, e) =>
|
||||
{
|
||||
if (lightModeRadio.IsChecked == true) SelectThemeMode(Services.ThemeMode.Light);
|
||||
if (lightModeRadio.IsChecked == true) SelectThemeMode(ThemeMode.Light);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public partial class OobeWindow : Window
|
||||
{
|
||||
darkModeRadio.IsCheckedChanged += (s, e) =>
|
||||
{
|
||||
if (darkModeRadio.IsChecked == true) SelectThemeMode(Services.ThemeMode.Dark);
|
||||
if (darkModeRadio.IsChecked == true) SelectThemeMode(ThemeMode.Dark);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -812,7 +812,7 @@ public partial class OobeWindow : Window
|
||||
}
|
||||
|
||||
// 主题选择方法
|
||||
private void SelectThemeMode(Services.ThemeMode mode)
|
||||
private void SelectThemeMode(ThemeMode mode)
|
||||
{
|
||||
_selectedThemeMode = mode;
|
||||
|
||||
@@ -821,30 +821,30 @@ public partial class OobeWindow : Window
|
||||
|
||||
if (this.FindControl<RadioButton>("LightModeRadio") is { } lightModeRadio)
|
||||
{
|
||||
lightModeRadio.IsChecked = mode == Services.ThemeMode.Light;
|
||||
lightModeRadio.IsChecked = mode == ThemeMode.Light;
|
||||
}
|
||||
|
||||
if (this.FindControl<RadioButton>("DarkModeRadio") is { } darkModeRadio)
|
||||
{
|
||||
darkModeRadio.IsChecked = mode == Services.ThemeMode.Dark;
|
||||
darkModeRadio.IsChecked = mode == ThemeMode.Dark;
|
||||
}
|
||||
|
||||
if (this.FindControl<Border>("LightModeOption") is { } lightModeOption)
|
||||
{
|
||||
lightModeOption.BorderBrush = mode == Services.ThemeMode.Light
|
||||
lightModeOption.BorderBrush = mode == ThemeMode.Light
|
||||
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
|
||||
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
|
||||
lightModeOption.BorderThickness = mode == Services.ThemeMode.Light
|
||||
lightModeOption.BorderThickness = mode == ThemeMode.Light
|
||||
? new Thickness(2)
|
||||
: new Thickness(1);
|
||||
}
|
||||
|
||||
if (this.FindControl<Border>("DarkModeOption") is { } darkModeOption)
|
||||
{
|
||||
darkModeOption.BorderBrush = mode == Services.ThemeMode.Dark
|
||||
darkModeOption.BorderBrush = mode == ThemeMode.Dark
|
||||
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
|
||||
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
|
||||
darkModeOption.BorderThickness = mode == Services.ThemeMode.Dark
|
||||
darkModeOption.BorderThickness = mode == ThemeMode.Dark
|
||||
? new Thickness(2)
|
||||
: new Thickness(1);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Launcher.Services.AirApp;
|
||||
using LanMountainDesktop.Launcher.AirApp;
|
||||
using LanMountainDesktop.Launcher.Infrastructure;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
@@ -69,7 +69,7 @@ public sealed class CornerRadiusStyleTests
|
||||
ThemeVariant: "Dark"));
|
||||
|
||||
var context = new PluginDesktopComponentContext(
|
||||
new PluginManifest("plugin.id", "Plugin Name", "plugin.dll"),
|
||||
new LanMountainDesktop.PluginSdk.PluginManifest("plugin.id", "Plugin Name", "plugin.dll"),
|
||||
"C:\\Plugins\\plugin.id",
|
||||
"C:\\Data\\plugin.id",
|
||||
new NullServiceProvider(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.Launcher;
|
||||
using LanMountainDesktop.Launcher.Services.AirApp;
|
||||
using LanMountainDesktop.Launcher.AirApp;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
using Xunit;
|
||||
|
||||
|
||||
6
LanMountainDesktop.Tests/LauncherGlobalUsings.cs
Normal file
6
LanMountainDesktop.Tests/LauncherGlobalUsings.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
global using LanMountainDesktop.Launcher.AirApp;
|
||||
global using LanMountainDesktop.Launcher.Deployment;
|
||||
global using LanMountainDesktop.Launcher.Infrastructure;
|
||||
global using LanMountainDesktop.Launcher.Ipc;
|
||||
global using LanMountainDesktop.Launcher.Oobe;
|
||||
global using LanMountainDesktop.Launcher.Update;
|
||||
@@ -1,4 +1,4 @@
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Launcher.Plugins;
|
||||
using System.IO.Compression;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class PluginManifestRuntimeTests
|
||||
""";
|
||||
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
|
||||
var manifest = PluginManifest.Load(stream, "plugin.json");
|
||||
var manifest = LanMountainDesktop.PluginSdk.PluginManifest.Load(stream, "plugin.json");
|
||||
|
||||
Assert.NotNull(manifest.Runtime);
|
||||
Assert.Equal(PluginRuntimeModes.InProcess, manifest.Runtime!.Mode);
|
||||
@@ -40,7 +40,7 @@ public sealed class PluginManifestRuntimeTests
|
||||
""";
|
||||
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => PluginManifest.Load(stream, "plugin.json"));
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => LanMountainDesktop.PluginSdk.PluginManifest.Load(stream, "plugin.json"));
|
||||
|
||||
Assert.Contains("runtime.mode", ex.Message);
|
||||
Assert.Contains("shared-worker", ex.Message);
|
||||
|
||||
Reference in New Issue
Block a user