mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
Improve launcher startup flow, logging, and host resolution. Key changes: add detailed startup logging and standardized preview messages; unify CLI vs GUI handling and error/result reporting (write result file when requested); refactor DeploymentLocator to a more robust host resolution (new HostResolutionResult, explicit/portable/published/debug resolution paths, legacy fallback); overhaul LauncherFlowCoordinator to better handle IPC stages, activation retries, window lifecycle, plugin/update flows and error reporting; add CommandContext helpers (IsGui/IsPreview/ExplicitAppRoot) and JSON context options; tighten async usage and ConfigureAwait calls; add better UI error handling and consistent exit codes. Several UX/debug conveniences and robustness fixes included.
393 lines
12 KiB
C#
393 lines
12 KiB
C#
using Avalonia.Controls;
|
||
using Avalonia.Interactivity;
|
||
using Avalonia.Markup.Xaml;
|
||
using Avalonia.Media;
|
||
using Avalonia.Threading;
|
||
using LanMountainDesktop.Launcher.Services;
|
||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
|
||
namespace LanMountainDesktop.Launcher.Views;
|
||
|
||
/// <summary>
|
||
/// 鍔犺浇璇︽儏绐楀彛 - 鏄剧ず璇︾粏鐨勫姞杞界姸鎬佸拰杩涘害
|
||
/// </summary>
|
||
public partial class LoadingDetailsWindow : Window
|
||
{
|
||
private readonly ObservableCollection<LoadingItemViewModel> _items = new();
|
||
private readonly DispatcherTimer _updateTimer;
|
||
private DateTimeOffset _startTime;
|
||
|
||
public LoadingDetailsWindow()
|
||
{
|
||
AvaloniaXamlLoader.Load(this);
|
||
|
||
var itemsList = this.FindControl<ItemsControl>("LoadingItemsList");
|
||
if (itemsList != null)
|
||
{
|
||
itemsList.ItemsSource = _items;
|
||
}
|
||
|
||
_updateTimer = new DispatcherTimer
|
||
{
|
||
Interval = TimeSpan.FromMilliseconds(100)
|
||
};
|
||
_updateTimer.Tick += OnUpdateTimerTick;
|
||
|
||
_startTime = DateTimeOffset.UtcNow;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绐楀彛鍔犺浇瀹屾垚
|
||
/// </summary>
|
||
protected override void OnLoaded(RoutedEventArgs e)
|
||
{
|
||
base.OnLoaded(e);
|
||
_updateTimer.Start();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绐楀彛鍏抽棴
|
||
/// </summary>
|
||
protected override void OnClosing(WindowClosingEventArgs e)
|
||
{
|
||
_updateTimer.Stop();
|
||
base.OnClosing(e);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鏇存柊鍔犺浇鐘舵€? /// </summary>
|
||
public void UpdateLoadingState(LoadingStateMessage state)
|
||
{
|
||
Dispatcher.UIThread.Post(() =>
|
||
{
|
||
try
|
||
{
|
||
// 鏇存柊鏍囬<E98F8D>鍜屽壇鏍囬<E98F8D>
|
||
UpdateHeader(state);
|
||
|
||
// 鏇存柊鏁翠綋杩涘害
|
||
UpdateOverallProgress(state);
|
||
|
||
UpdateCurrentItem(state);
|
||
|
||
// 鏇存柊鍒楄〃
|
||
UpdateItemsList(state);
|
||
|
||
// 鏇存柊閿欒<E996BF>淇℃伅
|
||
UpdateErrorPanel(state);
|
||
|
||
// 鏇存柊瀹屾垚璁℃暟
|
||
UpdateCompletedCount(state);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.Error.WriteLine($"[LoadingDetailsWindow] Error updating state: {ex.Message}");
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鏇存柊鏍囬<E98F8D>
|
||
/// </summary>
|
||
private void UpdateHeader(LoadingStateMessage state)
|
||
{
|
||
var subtitleText = this.FindControl<TextBlock>("SubtitleText");
|
||
if (subtitleText != null)
|
||
{
|
||
subtitleText.Text = GetStageDescription(state.Stage);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鏇存柊鏁翠綋杩涘害
|
||
/// </summary>
|
||
private void UpdateOverallProgress(LoadingStateMessage state)
|
||
{
|
||
var progressBar = this.FindControl<ProgressBar>("OverallProgressBar");
|
||
var percentText = this.FindControl<TextBlock>("PercentText");
|
||
|
||
if (progressBar != null)
|
||
{
|
||
progressBar.Value = state.OverallProgressPercent;
|
||
}
|
||
|
||
if (percentText != null)
|
||
{
|
||
percentText.Text = $"{state.OverallProgressPercent}%";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鏇存柊褰撳墠娲诲姩椤? /// </summary>
|
||
private void UpdateCurrentItem(LoadingStateMessage state)
|
||
{
|
||
var currentItem = state.ActiveItems.FirstOrDefault();
|
||
if (currentItem == null) return;
|
||
|
||
var nameText = this.FindControl<TextBlock>("CurrentItemName");
|
||
var descText = this.FindControl<TextBlock>("CurrentItemDescription");
|
||
var progressBar = this.FindControl<ProgressBar>("CurrentItemProgress");
|
||
var iconText = this.FindControl<TextBlock>("CurrentItemIcon");
|
||
|
||
if (nameText != null)
|
||
{
|
||
nameText.Text = currentItem.Name;
|
||
}
|
||
|
||
if (descText != null)
|
||
{
|
||
descText.Text = currentItem.Message ?? GetItemDescription(currentItem);
|
||
}
|
||
|
||
if (progressBar != null)
|
||
{
|
||
progressBar.Value = currentItem.ProgressPercent;
|
||
}
|
||
|
||
if (iconText != null)
|
||
{
|
||
iconText.Text = GetItemIcon(currentItem.Type);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鏇存柊鍒楄〃
|
||
/// </summary>
|
||
private void UpdateItemsList(LoadingStateMessage state)
|
||
{
|
||
foreach (var item in state.ActiveItems)
|
||
{
|
||
var existing = _items.FirstOrDefault(i => i.Id == item.Id);
|
||
if (existing != null)
|
||
{
|
||
existing.UpdateFrom(item);
|
||
}
|
||
else
|
||
{
|
||
_items.Add(new LoadingItemViewModel(item));
|
||
}
|
||
}
|
||
|
||
// 绉婚櫎宸插畬鎴愮殑椤癸紙淇濈暀鏈€杩戝畬鎴愮殑5涓<35>級
|
||
var completedItems = _items.Where(i => i.State == LoadingState.Completed).ToList();
|
||
if (completedItems.Count > 5)
|
||
{
|
||
var itemsToRemove = completedItems.OrderBy(i => i.CompletedTime).Take(completedItems.Count - 5);
|
||
foreach (var item in itemsToRemove)
|
||
{
|
||
_items.Remove(item);
|
||
}
|
||
}
|
||
|
||
// 鎸夌姸鎬佹帓搴忥細杩涜<E69DA9>涓?-> 绛夊緟涓?-> 宸插畬鎴?-> 澶辫触
|
||
var sortedItems = _items.OrderBy(i => GetStatePriority(i.State)).ToList();
|
||
_items.Clear();
|
||
foreach (var item in sortedItems)
|
||
{
|
||
_items.Add(item);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鏇存柊閿欒<E996BF>闈㈡澘
|
||
/// </summary>
|
||
private void UpdateErrorPanel(LoadingStateMessage state)
|
||
{
|
||
var errorPanel = this.FindControl<Border>("ErrorPanel");
|
||
var errorText = this.FindControl<TextBlock>("ErrorText");
|
||
|
||
if (errorPanel != null)
|
||
{
|
||
errorPanel.IsVisible = state.HasErrors;
|
||
}
|
||
|
||
if (errorText != null && state.ErrorMessages?.Any() == true)
|
||
{
|
||
errorText.Text = string.Join("\n", state.ErrorMessages.Take(3));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鏇存柊瀹屾垚璁℃暟
|
||
/// </summary>
|
||
private void UpdateCompletedCount(LoadingStateMessage state)
|
||
{
|
||
var countText = this.FindControl<TextBlock>("CompletedCountText");
|
||
if (countText != null)
|
||
{
|
||
countText.Text = state.CompletedCount.ToString();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 瀹氭椂鏇存柊
|
||
/// </summary>
|
||
private void OnUpdateTimerTick(object? sender, EventArgs e)
|
||
{
|
||
// 鍙<>互鍦ㄨ繖閲屾坊鍔犳椂闂存樉绀虹瓑瀹炴椂鏇存柊
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鑾峰彇闃舵<E99783>鎻忚堪
|
||
/// </summary>
|
||
private static string GetStageDescription(StartupStage stage) => stage switch
|
||
{
|
||
StartupStage.Initializing => "正在初始化系统...",
|
||
StartupStage.LoadingSettings => "正在加载设置...",
|
||
StartupStage.LoadingPlugins => "正在加载插件...",
|
||
StartupStage.InitializingUI => "正在初始化界面...",
|
||
StartupStage.ShellInitialized => "桌面外壳已初始化",
|
||
StartupStage.DesktopVisible => "桌面已经可见",
|
||
StartupStage.ActivationRedirected => "已激活现有实例",
|
||
StartupStage.ActivationFailed => "现有实例激活失败",
|
||
StartupStage.Ready => "加载完成",
|
||
_ => "正在加载..."
|
||
};
|
||
|
||
/// <summary>
|
||
/// 鑾峰彇椤规弿杩? /// </summary>
|
||
private static string GetItemDescription(LoadingItem item)
|
||
{
|
||
if (!string.IsNullOrEmpty(item.Description))
|
||
return item.Description;
|
||
|
||
return item.Type switch
|
||
{
|
||
LoadingItemType.Plugin => "姝e湪鍔犺浇鎻掍欢...",
|
||
LoadingItemType.Component => "姝e湪鍔犺浇缁勪欢...",
|
||
LoadingItemType.Resource => "姝e湪鍔犺浇璧勬簮...",
|
||
LoadingItemType.Data => "姝e湪鍔犺浇鏁版嵁...",
|
||
LoadingItemType.Network => "姝e湪涓嬭浇...",
|
||
_ => "姝e湪澶勭悊..."
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鑾峰彇椤瑰浘鏍? /// </summary>
|
||
private static string GetItemIcon(LoadingItemType type) => type switch
|
||
{
|
||
LoadingItemType.Plugin => "\uE768",
|
||
LoadingItemType.Component => "\uE7C4",
|
||
LoadingItemType.Resource => "\uE7C5",
|
||
LoadingItemType.Data => "\uE7C6",
|
||
LoadingItemType.Network => "\uE774",
|
||
LoadingItemType.Settings => "\uE713",
|
||
LoadingItemType.System => "\uE7C7",
|
||
_ => "\uE768"
|
||
};
|
||
|
||
/// <summary>
|
||
/// 鑾峰彇鐘舵€佷紭鍏堢骇
|
||
/// </summary>
|
||
private static int GetStatePriority(LoadingState state) => state switch
|
||
{
|
||
LoadingState.InProgress => 0,
|
||
LoadingState.Pending => 1,
|
||
LoadingState.Completed => 2,
|
||
LoadingState.Failed => 3,
|
||
LoadingState.Timeout => 4,
|
||
LoadingState.Cancelled => 5,
|
||
_ => 6
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鍔犺浇椤硅<E6A4A4>鍥炬ā鍨?/// </summary>
|
||
public class LoadingItemViewModel : INotifyPropertyChanged
|
||
{
|
||
public string Id { get; }
|
||
public string Name { get; private set; }
|
||
public LoadingItemType Type { get; private set; }
|
||
public LoadingState State { get; private set; }
|
||
public int ProgressPercent { get; private set; }
|
||
public DateTimeOffset? CompletedTime { get; private set; }
|
||
|
||
public string StatusIcon => GetStatusIcon(State);
|
||
public IBrush StatusColor => GetStatusColor(State);
|
||
public string ProgressText => State == LoadingState.Completed ? "瀹屾垚" : $"{ProgressPercent}%";
|
||
public string TypeLabel => GetTypeLabel(Type);
|
||
public IBrush TypeBackground => GetTypeBackground(Type);
|
||
public IBrush TypeForeground => GetTypeForeground(Type);
|
||
public double Opacity => State == LoadingState.Completed ? 0.6 : 1.0;
|
||
|
||
public event PropertyChangedEventHandler? PropertyChanged;
|
||
|
||
public LoadingItemViewModel(LoadingItem item)
|
||
{
|
||
Id = item.Id;
|
||
UpdateFrom(item);
|
||
}
|
||
|
||
public void UpdateFrom(LoadingItem item)
|
||
{
|
||
Name = item.Name;
|
||
Type = item.Type;
|
||
State = item.State;
|
||
ProgressPercent = item.ProgressPercent;
|
||
|
||
if (State == LoadingState.Completed && !CompletedTime.HasValue)
|
||
{
|
||
CompletedTime = DateTimeOffset.UtcNow;
|
||
}
|
||
|
||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
|
||
}
|
||
|
||
private static string GetStatusIcon(LoadingState state) => state switch
|
||
{
|
||
LoadingState.Pending => "\uE7C3",
|
||
LoadingState.InProgress => "\uE768",
|
||
LoadingState.Completed => "\uE73E",
|
||
LoadingState.Failed => "\uE783",
|
||
LoadingState.Timeout => "\uE71A",
|
||
LoadingState.Cancelled => "\uE711",
|
||
_ => "\uE7C3"
|
||
};
|
||
|
||
private static IBrush GetStatusColor(LoadingState state) => state switch
|
||
{
|
||
LoadingState.Pending => new SolidColorBrush(Colors.Gray),
|
||
LoadingState.InProgress => new SolidColorBrush(Colors.DodgerBlue),
|
||
LoadingState.Completed => new SolidColorBrush(Colors.Green),
|
||
LoadingState.Failed => new SolidColorBrush(Colors.Red),
|
||
LoadingState.Timeout => new SolidColorBrush(Colors.Orange),
|
||
LoadingState.Cancelled => new SolidColorBrush(Colors.Gray),
|
||
_ => new SolidColorBrush(Colors.Gray)
|
||
};
|
||
|
||
private static string GetTypeLabel(LoadingItemType type) => type switch
|
||
{
|
||
LoadingItemType.Plugin => "鎻掍欢",
|
||
LoadingItemType.Component => "缁勪欢",
|
||
LoadingItemType.Resource => "璧勬簮",
|
||
LoadingItemType.Data => "鏁版嵁",
|
||
LoadingItemType.Network => "缃戠粶",
|
||
LoadingItemType.Settings => "璁剧疆",
|
||
LoadingItemType.System => "绯荤粺",
|
||
_ => "鍏朵粬"
|
||
};
|
||
|
||
private static IBrush GetTypeBackground(LoadingItemType type) => type switch
|
||
{
|
||
LoadingItemType.Plugin => new SolidColorBrush(Color.Parse("#E3F2FD")),
|
||
LoadingItemType.Component => new SolidColorBrush(Color.Parse("#F3E5F5")),
|
||
LoadingItemType.Resource => new SolidColorBrush(Color.Parse("#E8F5E9")),
|
||
LoadingItemType.Data => new SolidColorBrush(Color.Parse("#FFF3E0")),
|
||
LoadingItemType.Network => new SolidColorBrush(Color.Parse("#E0F7FA")),
|
||
_ => new SolidColorBrush(Color.Parse("#F5F5F5"))
|
||
};
|
||
|
||
private static IBrush GetTypeForeground(LoadingItemType type) => type switch
|
||
{
|
||
LoadingItemType.Plugin => new SolidColorBrush(Color.Parse("#1976D2")),
|
||
LoadingItemType.Component => new SolidColorBrush(Color.Parse("#7B1FA2")),
|
||
LoadingItemType.Resource => new SolidColorBrush(Color.Parse("#388E3C")),
|
||
LoadingItemType.Data => new SolidColorBrush(Color.Parse("#F57C00")),
|
||
LoadingItemType.Network => new SolidColorBrush(Color.Parse("#0097A7")),
|
||
_ => new SolidColorBrush(Color.Parse("#616161"))
|
||
};
|
||
}
|
||
|