Files
LanMountainDesktop/LanMountainDesktop.Launcher/App.axaml.cs
2026-04-17 15:16:01 +08:00

388 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Launcher.Views;
namespace LanMountainDesktop.Launcher;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var context = LauncherRuntimeContext.Current;
// 调试模式:显示开发调试窗口
if (context.IsDebugMode)
{
var devDebugWindow = new DevDebugWindow();
devDebugWindow.Show();
// 调试模式下不自动启动正常流程,由开发者通过调试窗口控制
base.OnFrameworkInitializationCompleted();
return;
}
// 处理各界面的预览命令
if (HandlePreviewCommand(context, desktop))
{
base.OnFrameworkInitializationCompleted();
return;
}
// apply-update 模式:显示 UpdateWindow执行增量更新 + 插件升级
if (string.Equals(context.Command, "apply-update", StringComparison.OrdinalIgnoreCase))
{
// 先显示窗口,再启动后台任务
var updateWindow = new UpdateWindow();
updateWindow.Show();
_ = RunApplyUpdateWithWindowAsync(desktop, context, updateWindow);
}
else
{
// 正常启动流程
var appRoot = Commands.ResolveAppRoot(context);
var deploymentLocator = new DeploymentLocator(appRoot);
// TODO: 从配置读取 GitHub 仓库信息
var updateCheckService = new UpdateCheckService("ClassIsland", "LanMountainDesktop");
var coordinator = new LauncherFlowCoordinator(
context,
deploymentLocator,
new OobeStateService(appRoot),
new UpdateEngineService(deploymentLocator),
updateCheckService,
new PluginInstallerService());
// 先显示 Splash 窗口,确保应用程序不会立即退出
var splashWindow = new SplashWindow();
splashWindow.Show();
// 启动协调器流程
_ = RunCoordinatorWithSplashAsync(desktop, coordinator, splashWindow);
}
}
base.OnFrameworkInitializationCompleted();
}
/// <summary>
/// 处理界面预览命令
/// </summary>
private bool HandlePreviewCommand(CommandContext context, IClassicDesktopStyleApplicationLifetime desktop)
{
var command = context.Command.ToLowerInvariant();
switch (command)
{
case "preview-splash":
Console.WriteLine("[Launcher] Preview mode: SplashWindow");
var splashWindow = new SplashWindow();
splashWindow.SetDebugMode(true);
splashWindow.Show();
_ = SimulateSplashPreviewAsync(desktop, splashWindow);
return true;
case "preview-error":
Console.WriteLine("[Launcher] Preview mode: ErrorWindow");
var errorWindow = new ErrorWindow();
errorWindow.SetErrorMessage("[预览模式] 这是一个错误页面预览。\n\n用于查看错误页面的样式和布局。");
errorWindow.Show();
_ = WaitForWindowCloseAsync(desktop, errorWindow);
return true;
case "preview-update":
Console.WriteLine("[Launcher] Preview mode: UpdateWindow");
var updateWindow = new UpdateWindow();
updateWindow.SetDebugMode(true);
updateWindow.Show();
_ = SimulateUpdatePreviewAsync(desktop, updateWindow);
return true;
case "preview-oobe":
Console.WriteLine("[Launcher] Preview mode: OobeWindow");
var oobeWindow = new OobeWindow();
oobeWindow.Show();
_ = SimulateOobePreviewAsync(desktop, oobeWindow);
return true;
case "preview-debug":
Console.WriteLine("[Launcher] Preview mode: DevDebugWindow");
var devDebugWindow = new DevDebugWindow();
devDebugWindow.Show();
return true;
default:
return false;
}
}
/// <summary>
/// 模拟 Splash 窗口预览
/// </summary>
private async Task SimulateSplashPreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, SplashWindow window)
{
var stages = new[] { "initializing", "update", "plugins", "launch", "ready" };
var messages = new[] { "初始化...", "检查更新...", "检查插件...", "正在启动...", "就绪" };
var reporter = (ISplashStageReporter)window;
for (int i = 0; i < stages.Length; i++)
{
reporter.Report(stages[i], messages[i]);
await Task.Delay(800);
}
// 等待5秒后自动关闭
await Task.Delay(5000);
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0));
}
/// <summary>
/// 模拟 Update 窗口预览
/// </summary>
private async Task SimulateUpdatePreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, UpdateWindow window)
{
var stages = new[] { "verify", "extract", "apply", "plugins", "cleanup" };
for (int i = 0; i < stages.Length; i++)
{
window.Report(stages[i], $"正在{GetStageName(stages[i])}...", (i + 1) * 20);
await Task.Delay(600);
}
window.ReportComplete(true, null);
// 等待3秒后自动关闭
await Task.Delay(3000);
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0));
string GetStageName(string stage) => stage switch
{
"verify" => "验证",
"extract" => "解压",
"apply" => "应用",
"plugins" => "升级插件",
"cleanup" => "清理",
_ => stage
};
}
/// <summary>
/// 模拟 OOBE 窗口预览
/// </summary>
private async Task SimulateOobePreviewAsync(IClassicDesktopStyleApplicationLifetime desktop, OobeWindow window)
{
try
{
// 等待用户点击开始按钮
await window.WaitForEnterAsync();
Console.WriteLine("[Launcher] OOBE preview completed by user");
}
catch (Exception ex)
{
Console.Error.WriteLine($"[Launcher] OOBE preview error: {ex.Message}");
}
// 用户点击后关闭应用程序
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0));
}
/// <summary>
/// 等待窗口关闭
/// </summary>
private async Task WaitForWindowCloseAsync(IClassicDesktopStyleApplicationLifetime desktop, Window window)
{
var tcs = new TaskCompletionSource();
window.Closed += (s, e) => tcs.TrySetResult();
await tcs.Task;
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(0));
}
private static async Task RunCoordinatorWithSplashAsync(
IClassicDesktopStyleApplicationLifetime desktop,
LauncherFlowCoordinator coordinator,
SplashWindow splashWindow)
{
LauncherResult result;
ErrorWindow? errorWindow = null;
try
{
result = await coordinator.RunAsync(splashWindow).ConfigureAwait(false);
}
catch (Exception ex)
{
// 捕获异常并显示错误窗口
result = new LauncherResult
{
Success = false,
Stage = "launch",
Code = "exception",
Message = $"启动器发生错误: {ex.Message}",
ErrorMessage = ex.ToString()
};
Console.Error.WriteLine($"[Launcher] Exception caught: {ex}");
// 在 UI 线程显示错误窗口 - 使用更健壮的方式
try
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
try
{
// 安全关闭 Splash 窗口
if (splashWindow.IsVisible && splashWindow.IsLoaded)
{
splashWindow.Close();
}
}
catch (Exception closeEx)
{
Console.Error.WriteLine($"[Launcher] Error closing splash window: {closeEx.Message}");
}
// 创建并显示错误窗口
try
{
errorWindow = new ErrorWindow();
errorWindow.SetErrorMessage($"启动器发生错误:\n{ex.Message}\n\n请检查应用安装是否完整或尝试重新安装。");
errorWindow.Show();
Console.WriteLine("[Launcher] ErrorWindow shown successfully");
}
catch (Exception windowEx)
{
Console.Error.WriteLine($"[Launcher] Failed to show ErrorWindow: {windowEx.Message}");
}
});
// 如果错误窗口成功显示,等待它关闭
if (errorWindow != null)
{
try
{
// 等待用户选择或窗口关闭
var errorResult = await errorWindow.WaitForChoiceAsync();
Console.WriteLine($"[Launcher] ErrorWindow result: {errorResult}");
}
catch (Exception waitEx)
{
Console.Error.WriteLine($"[Launcher] Error waiting for ErrorWindow: {waitEx.Message}");
// 如果等待失败至少给用户5秒时间看到错误信息
await Task.Delay(5000);
}
}
else
{
// 错误窗口未能显示等待5秒让用户看到控制台输出
await Task.Delay(5000);
}
}
catch (Exception uiEx)
{
// 最后的兜底:记录到控制台
Console.Error.WriteLine($"[Launcher] Critical error in UI thread: {uiEx.Message}");
await Task.Delay(3000);
}
}
await Commands.WriteResultIfNeededAsync(LauncherRuntimeContext.Current.GetOption("result"), result).ConfigureAwait(false);
Environment.ExitCode = result.Success ? 0 : 1;
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background);
}
/// <summary>
/// apply-update 模式:执行增量更新和插件升级,完成后自动退出
/// </summary>
private static async Task RunApplyUpdateWithWindowAsync(
IClassicDesktopStyleApplicationLifetime desktop,
CommandContext context,
UpdateWindow window)
{
var appRoot = Commands.ResolveAppRoot(context);
var deploymentLocator = new DeploymentLocator(appRoot);
var updateEngine = new UpdateEngineService(deploymentLocator);
var pluginInstaller = new PluginInstallerService();
var pluginUpgrades = new PluginUpgradeQueueService(pluginInstaller);
var success = true;
string? errorMessage = null;
try
{
// 1. 应用增量更新
await Dispatcher.UIThread.InvokeAsync(() => window.Report("verify", "正在验证更新...", 10));
var updateResult = await updateEngine.ApplyPendingUpdateAsync().ConfigureAwait(false);
if (!updateResult.Success)
{
success = false;
errorMessage = updateResult.Message;
}
// 2. 应用待处理的插件升级
if (success)
{
await Dispatcher.UIThread.InvokeAsync(() => window.Report("plugins", "正在升级插件...", 60));
var pluginsDir = context.GetOption("plugins-dir")
?? Path.Combine(appRoot, "plugins");
var queueResult = pluginUpgrades.ApplyPendingUpgrades(pluginsDir);
if (!queueResult.Success && queueResult.Code != "noop")
{
// 插件升级失败不阻断整体流程,仅记录到控制台
Console.Error.WriteLine($"Plugin upgrade had failures: {queueResult.Message}");
}
}
// 3. 清理旧版本
if (success)
{
await Dispatcher.UIThread.InvokeAsync(() => window.Report("cleanup", "正在清理...", 90));
deploymentLocator.CleanupDestroyedDeployments();
}
}
catch (Exception ex)
{
success = false;
errorMessage = ex.Message;
}
// 显示完成状态,短暂停留后关闭
await Dispatcher.UIThread.InvokeAsync(() => window.ReportComplete(success, errorMessage));
if (success)
{
// 成功:停留 1.5 秒让用户看到"更新完成"
await Task.Delay(1500);
}
else
{
// 失败:停留 5 秒让用户看到错误信息
await Task.Delay(5000);
}
await Commands.WriteResultIfNeededAsync(context.GetOption("result"), new LauncherResult
{
Success = success,
Stage = "apply-update",
Code = success ? "ok" : "failed",
Message = success ? "Update applied successfully." : (errorMessage ?? "Unknown error")
}).ConfigureAwait(false);
Environment.ExitCode = success ? 0 : 1;
await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background);
}
}