mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
fix.在线安装器,启动器
This commit is contained in:
@@ -132,6 +132,27 @@ internal sealed class DataLocationResolver
|
||||
return ResolveDataRoot(config);
|
||||
}
|
||||
|
||||
public string ResolveDataRoot(DataLocationMode mode, string? customPath = null)
|
||||
{
|
||||
return ResolveDataRoot(BuildConfig(mode, customPath));
|
||||
}
|
||||
|
||||
public DataLocationConfig BuildConfig(DataLocationMode mode, string? customPath = null)
|
||||
{
|
||||
var targetDataRoot = mode == DataLocationMode.Portable
|
||||
? Path.GetFullPath(!string.IsNullOrWhiteSpace(customPath)
|
||||
? customPath
|
||||
: DefaultPortableDataPath)
|
||||
: _defaultSystemDataPath;
|
||||
|
||||
return new DataLocationConfig
|
||||
{
|
||||
DataLocationMode = mode.ToString(),
|
||||
SystemDataPath = _defaultSystemDataPath,
|
||||
PortableDataPath = mode == DataLocationMode.Portable ? targetDataRoot : null
|
||||
};
|
||||
}
|
||||
|
||||
private string ResolveDataRoot(DataLocationConfig? config)
|
||||
{
|
||||
if (config is null)
|
||||
@@ -193,18 +214,8 @@ internal sealed class DataLocationResolver
|
||||
|
||||
public bool ApplyLocationChoice(DataLocationMode mode, string? customPath = null, bool migrateExistingData = false)
|
||||
{
|
||||
var targetDataRoot = mode == DataLocationMode.Portable
|
||||
? Path.GetFullPath(!string.IsNullOrWhiteSpace(customPath)
|
||||
? customPath
|
||||
: DefaultPortableDataPath)
|
||||
: _defaultSystemDataPath;
|
||||
|
||||
var config = new DataLocationConfig
|
||||
{
|
||||
DataLocationMode = mode.ToString(),
|
||||
SystemDataPath = _defaultSystemDataPath,
|
||||
PortableDataPath = mode == DataLocationMode.Portable ? targetDataRoot : null
|
||||
};
|
||||
var config = BuildConfig(mode, customPath);
|
||||
var targetDataRoot = ResolveDataRoot(config);
|
||||
|
||||
// 先创建目录结构
|
||||
try
|
||||
|
||||
@@ -57,6 +57,23 @@ internal sealed class OobeCompletionResult
|
||||
public string ErrorMessage { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
internal sealed class OobeSessionDraft
|
||||
{
|
||||
public DataLocationMode DataLocationMode { get; init; } = DataLocationMode.System;
|
||||
|
||||
public bool MigrateExistingData { get; init; }
|
||||
|
||||
public HostAppSettingsStartupChoices StartupChoices { get; init; }
|
||||
|
||||
public PrivacyConfig PrivacyConfig { get; init; } = new();
|
||||
|
||||
public bool PrivacyAgreementAccepted { get; init; }
|
||||
|
||||
public string PrivacyUserId { get; init; } = string.Empty;
|
||||
|
||||
public string PrivacyDeviceId { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
internal sealed record LauncherExecutionSnapshot(
|
||||
bool IsElevated,
|
||||
string UserName,
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
using LanMountainDesktop.Launcher.Views;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
internal sealed class DataLocationOobeStep : IOobeStep
|
||||
{
|
||||
private readonly DataLocationResolver _resolver;
|
||||
|
||||
public DataLocationOobeStep(DataLocationResolver resolver)
|
||||
{
|
||||
_resolver = resolver;
|
||||
}
|
||||
|
||||
public async Task RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var existingConfig = _resolver.LoadConfig();
|
||||
if (existingConfig is not null)
|
||||
{
|
||||
Logger.Info("DataLocation OOBE step skipped: config already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
DataLocationPromptWindow? window = null;
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
window = new DataLocationPromptWindow(_resolver);
|
||||
window.Show();
|
||||
});
|
||||
|
||||
if (window is null)
|
||||
{
|
||||
Logger.Warn("DataLocation OOBE step failed: window could not be created.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await window.WaitForChoiceAsync().ConfigureAwait(false);
|
||||
if (result is null)
|
||||
{
|
||||
Logger.Info("DataLocation OOBE step: user cancelled or closed window. Using default system location.");
|
||||
_resolver.ApplyLocationChoice(DataLocationMode.System, null, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var success = _resolver.ApplyLocationChoice(result.SelectedMode, null, result.MigrateExistingData);
|
||||
Logger.Info(
|
||||
$"DataLocation OOBE step: user selected '{result.SelectedMode}'. " +
|
||||
$"Migrate={result.MigrateExistingData}; Success={success}.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
if (window.IsVisible)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,15 @@
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
internal sealed record OobeStepResult(bool ContinueLaunch, LauncherResult? Result = null)
|
||||
{
|
||||
public static OobeStepResult Continue { get; } = new(true);
|
||||
|
||||
public static OobeStepResult Complete(LauncherResult result) => new(false, result);
|
||||
}
|
||||
|
||||
internal interface IOobeStep
|
||||
{
|
||||
Task RunAsync(CancellationToken cancellationToken);
|
||||
Task<OobeStepResult> RunAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
92
LanMountainDesktop.Launcher/Oobe/OobeSessionCommitService.cs
Normal file
92
LanMountainDesktop.Launcher/Oobe/OobeSessionCommitService.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Launcher.Models;
|
||||
|
||||
namespace LanMountainDesktop.Launcher.Oobe;
|
||||
|
||||
internal sealed class OobeSessionCommitService
|
||||
{
|
||||
private readonly DataLocationResolver _dataLocationResolver;
|
||||
private readonly OobeStateService _oobeStateService;
|
||||
private readonly CommandContext _context;
|
||||
private readonly Func<bool, bool>? _setWindowsStartup;
|
||||
|
||||
public OobeSessionCommitService(
|
||||
DataLocationResolver dataLocationResolver,
|
||||
OobeStateService oobeStateService,
|
||||
CommandContext context,
|
||||
Func<bool, bool>? setWindowsStartup = null)
|
||||
{
|
||||
_dataLocationResolver = dataLocationResolver;
|
||||
_oobeStateService = oobeStateService;
|
||||
_context = context;
|
||||
_setWindowsStartup = setWindowsStartup;
|
||||
}
|
||||
|
||||
public OobeCompletionResult Commit(OobeSessionDraft draft)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(draft);
|
||||
|
||||
if (!_dataLocationResolver.ApplyLocationChoice(
|
||||
draft.DataLocationMode,
|
||||
customPath: null,
|
||||
draft.MigrateExistingData))
|
||||
{
|
||||
return Failure("data_location_save_failed", "Failed to save the selected data location.");
|
||||
}
|
||||
|
||||
var dataRoot = _dataLocationResolver.ResolveDataRoot();
|
||||
|
||||
try
|
||||
{
|
||||
var settingsPath = HostAppSettingsOobeMerger.GetSettingsFilePath(dataRoot);
|
||||
HostAppSettingsOobeMerger.MergeStartupPresentation(settingsPath, draft.StartupChoices);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Failure("startup_settings_save_failed", ex.Message);
|
||||
}
|
||||
|
||||
var setWindowsStartup = _setWindowsStartup ?? new LauncherWindowsStartupService().SetEnabled;
|
||||
if (OperatingSystem.IsWindows() &&
|
||||
!setWindowsStartup(draft.StartupChoices.AutoStartWithWindows))
|
||||
{
|
||||
return Failure("windows_startup_save_failed", "Failed to save Windows startup preference.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var launcherDataPath = _dataLocationResolver.ResolveLauncherDataPath();
|
||||
Directory.CreateDirectory(launcherDataPath);
|
||||
|
||||
var privacyConfigPath = Path.Combine(launcherDataPath, "privacy-config.json");
|
||||
var privacyJson = JsonSerializer.Serialize(draft.PrivacyConfig, AppJsonContext.Default.PrivacyConfig);
|
||||
File.WriteAllText(privacyConfigPath, privacyJson);
|
||||
|
||||
var agreementService = new PrivacyAgreementService(launcherDataPath);
|
||||
if (!agreementService.SaveAgreement(
|
||||
draft.PrivacyAgreementAccepted,
|
||||
draft.PrivacyUserId,
|
||||
draft.PrivacyDeviceId))
|
||||
{
|
||||
return Failure("privacy_agreement_save_failed", "Failed to save privacy agreement state.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Failure("privacy_settings_save_failed", ex.Message);
|
||||
}
|
||||
|
||||
var completion = _oobeStateService.MarkCompleted(_context, dataRoot);
|
||||
return completion.Success
|
||||
? completion
|
||||
: Failure(completion.ResultCode, completion.ErrorMessage);
|
||||
}
|
||||
|
||||
private static OobeCompletionResult Failure(string code, string message) =>
|
||||
new()
|
||||
{
|
||||
Success = false,
|
||||
ResultCode = code,
|
||||
ErrorMessage = message
|
||||
};
|
||||
}
|
||||
@@ -7,10 +7,12 @@ internal sealed class OobeStateService
|
||||
{
|
||||
private const int CurrentSchemaVersion = 1;
|
||||
|
||||
private readonly string _appRoot;
|
||||
private readonly string? _stateRootOverride;
|
||||
private readonly string _stateDirectory;
|
||||
private readonly string _statePath;
|
||||
private readonly string _legacyStatePath;
|
||||
private readonly string _legacyMarkerPath;
|
||||
private readonly IReadOnlyList<string> _legacyStatePaths;
|
||||
private readonly IReadOnlyList<string> _legacyMarkerPaths;
|
||||
private readonly LauncherExecutionSnapshot _executionSnapshot;
|
||||
|
||||
public OobeStateService(
|
||||
@@ -18,21 +20,17 @@ internal sealed class OobeStateService
|
||||
string? stateRootOverride = null,
|
||||
LauncherExecutionSnapshot? executionSnapshot = null)
|
||||
{
|
||||
_ = Path.GetFullPath(appRoot);
|
||||
_appRoot = Path.GetFullPath(appRoot);
|
||||
_stateRootOverride = string.IsNullOrWhiteSpace(stateRootOverride)
|
||||
? null
|
||||
: Path.GetFullPath(stateRootOverride);
|
||||
_executionSnapshot = executionSnapshot ?? LauncherExecutionContext.Capture();
|
||||
|
||||
var stateRoot = string.IsNullOrWhiteSpace(stateRootOverride)
|
||||
? ResolveStateRoot(appRoot)
|
||||
: Path.GetFullPath(stateRootOverride);
|
||||
_stateDirectory = Path.Combine(stateRoot, "Launcher", "state");
|
||||
_statePath = Path.Combine(_stateDirectory, "oobe-state.json");
|
||||
var stateRoot = ResolveCurrentStateRoot();
|
||||
(_stateDirectory, _statePath) = BuildStatePaths(stateRoot);
|
||||
|
||||
var legacyRoot = string.IsNullOrWhiteSpace(stateRootOverride)
|
||||
? Path.GetFullPath(appRoot)
|
||||
: Path.GetFullPath(stateRootOverride);
|
||||
var legacyStateDirectory = Path.Combine(legacyRoot, ".launcher", "state");
|
||||
_legacyStatePath = Path.Combine(legacyStateDirectory, "oobe-state.json");
|
||||
_legacyMarkerPath = Path.Combine(legacyStateDirectory, "first_run_completed");
|
||||
_legacyStatePaths = BuildLegacyPaths("oobe-state.json");
|
||||
_legacyMarkerPaths = BuildLegacyPaths("first_run_completed");
|
||||
}
|
||||
|
||||
public OobeLaunchDecision Evaluate(CommandContext context)
|
||||
@@ -47,10 +45,17 @@ internal sealed class OobeStateService
|
||||
}
|
||||
|
||||
public OobeCompletionResult MarkCompleted(CommandContext context)
|
||||
{
|
||||
return MarkCompleted(context, null);
|
||||
}
|
||||
|
||||
public OobeCompletionResult MarkCompleted(CommandContext context, string? stateRoot)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(_stateDirectory);
|
||||
var (stateDirectory, statePath) = BuildStatePaths(
|
||||
string.IsNullOrWhiteSpace(stateRoot) ? ResolveCurrentStateRoot() : Path.GetFullPath(stateRoot));
|
||||
Directory.CreateDirectory(stateDirectory);
|
||||
var payload = new OobeStateFile
|
||||
{
|
||||
SchemaVersion = CurrentSchemaVersion,
|
||||
@@ -60,14 +65,14 @@ internal sealed class OobeStateService
|
||||
LaunchSource = context.LaunchSource
|
||||
};
|
||||
|
||||
var tempPath = Path.Combine(_stateDirectory, $"oobe-state.{Guid.NewGuid():N}.tmp");
|
||||
var tempPath = Path.Combine(stateDirectory, $"oobe-state.{Guid.NewGuid():N}.tmp");
|
||||
var json = JsonSerializer.Serialize(payload, AppJsonContext.Default.OobeStateFile);
|
||||
File.WriteAllText(tempPath, json);
|
||||
File.Move(tempPath, _statePath, overwrite: true);
|
||||
File.Move(tempPath, statePath, overwrite: true);
|
||||
TryDeleteLegacyMarker();
|
||||
|
||||
Logger.Info(
|
||||
$"OOBE completion persisted. LaunchSource='{context.LaunchSource}'; StatePath='{_statePath}'; " +
|
||||
$"OOBE completion persisted. LaunchSource='{context.LaunchSource}'; StatePath='{statePath}'; " +
|
||||
$"UserSid='{_executionSnapshot.UserSid ?? string.Empty}'.");
|
||||
|
||||
return new OobeCompletionResult
|
||||
@@ -110,20 +115,27 @@ internal sealed class OobeStateService
|
||||
return EvaluateStateFile(context, _statePath, migratedLegacyState: false);
|
||||
}
|
||||
|
||||
if (File.Exists(_legacyStatePath))
|
||||
foreach (var legacyStatePath in _legacyStatePaths)
|
||||
{
|
||||
return EvaluateStateFile(context, _legacyStatePath, migratedLegacyState: false);
|
||||
if (File.Exists(legacyStatePath))
|
||||
{
|
||||
var decision = EvaluateStateFile(context, legacyStatePath, migratedLegacyState: true);
|
||||
if (decision.Status == OobeStateStatus.Completed)
|
||||
{
|
||||
_ = MarkCompleted(context);
|
||||
}
|
||||
|
||||
return decision;
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(_legacyMarkerPath))
|
||||
foreach (var legacyMarkerPath in _legacyMarkerPaths)
|
||||
{
|
||||
migratedLegacyMarker = TryMigrateLegacyMarker(context);
|
||||
return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, usedLegacyMarker: true, migratedLegacyMarker: migratedLegacyMarker);
|
||||
}
|
||||
|
||||
if (_executionSnapshot.IsElevated)
|
||||
{
|
||||
return BuildSuppressedDecision(context, "elevated", "oobe_suppressed_elevated");
|
||||
if (File.Exists(legacyMarkerPath))
|
||||
{
|
||||
migratedLegacyMarker = TryMigrateLegacyMarker(context);
|
||||
return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, usedLegacyMarker: true, migratedLegacyMarker: migratedLegacyMarker);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(context.LaunchSource, "postinstall", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -159,15 +171,18 @@ internal sealed class OobeStateService
|
||||
|
||||
private void TryDeleteLegacyMarker()
|
||||
{
|
||||
try
|
||||
foreach (var legacyMarkerPath in _legacyMarkerPaths)
|
||||
{
|
||||
if (File.Exists(_legacyMarkerPath))
|
||||
try
|
||||
{
|
||||
if (File.Exists(legacyMarkerPath))
|
||||
{
|
||||
File.Delete(legacyMarkerPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
File.Delete(_legacyMarkerPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +240,44 @@ internal sealed class OobeStateService
|
||||
};
|
||||
}
|
||||
|
||||
private string ResolveCurrentStateRoot()
|
||||
{
|
||||
return _stateRootOverride ?? ResolveStateRoot(_appRoot);
|
||||
}
|
||||
|
||||
private static (string StateDirectory, string StatePath) BuildStatePaths(string stateRoot)
|
||||
{
|
||||
var stateDirectory = Path.Combine(Path.GetFullPath(stateRoot), "Launcher", "state");
|
||||
return (stateDirectory, Path.Combine(stateDirectory, "oobe-state.json"));
|
||||
}
|
||||
|
||||
private IReadOnlyList<string> BuildLegacyPaths(string fileName)
|
||||
{
|
||||
var roots = new List<string>();
|
||||
if (_stateRootOverride is not null)
|
||||
{
|
||||
roots.Add(_stateRootOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
roots.Add(ResolveDefaultSystemStateRoot());
|
||||
roots.Add(_appRoot);
|
||||
try
|
||||
{
|
||||
roots.Add(ResolveCurrentStateRoot());
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return roots
|
||||
.Where(root => !string.IsNullOrWhiteSpace(root))
|
||||
.Select(root => Path.Combine(Path.GetFullPath(root), ".launcher", "state", fileName))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static string ResolveStateRoot(string appRoot)
|
||||
{
|
||||
try
|
||||
@@ -243,4 +296,15 @@ internal sealed class OobeStateService
|
||||
return Path.Combine(appData, "LanMountainDesktop");
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveDefaultSystemStateRoot()
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
if (string.IsNullOrWhiteSpace(appData))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return Path.Combine(appData, "LanMountainDesktop");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,19 @@ internal sealed class WelcomeOobeStep : IOobeStep
|
||||
{
|
||||
private readonly CommandContext _context;
|
||||
private readonly OobeStateService _oobeStateService;
|
||||
private readonly DataLocationResolver _dataLocationResolver;
|
||||
|
||||
public WelcomeOobeStep(OobeStateService oobeStateService, CommandContext context)
|
||||
public WelcomeOobeStep(
|
||||
OobeStateService oobeStateService,
|
||||
CommandContext context,
|
||||
DataLocationResolver dataLocationResolver)
|
||||
{
|
||||
_oobeStateService = oobeStateService;
|
||||
_context = context;
|
||||
_dataLocationResolver = dataLocationResolver;
|
||||
}
|
||||
|
||||
public async Task RunAsync(CancellationToken cancellationToken)
|
||||
public async Task<OobeStepResult> RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -27,16 +32,32 @@ internal sealed class WelcomeOobeStep : IOobeStep
|
||||
|
||||
if (window is null)
|
||||
{
|
||||
return;
|
||||
return BuildCancelledResult("OOBE window could not be created.");
|
||||
}
|
||||
|
||||
await window.WaitForEnterAsync().ConfigureAwait(false);
|
||||
var completion = _oobeStateService.MarkCompleted(_context);
|
||||
var draft = await window.WaitForCompletionAsync().ConfigureAwait(false);
|
||||
if (draft is null)
|
||||
{
|
||||
Logger.Info("OOBE was cancelled before completion; Host launch will be skipped.");
|
||||
return BuildCancelledResult("OOBE was cancelled before completion.");
|
||||
}
|
||||
|
||||
var completion = new OobeSessionCommitService(
|
||||
_dataLocationResolver,
|
||||
_oobeStateService,
|
||||
_context)
|
||||
.Commit(draft);
|
||||
if (!completion.Success)
|
||||
{
|
||||
Logger.Warn(
|
||||
$"OOBE completion state was not persisted. ResultCode='{completion.ResultCode}'; " +
|
||||
$"OOBE session was not persisted. ResultCode='{completion.ResultCode}'; " +
|
||||
$"Error='{completion.ErrorMessage}'.");
|
||||
return OobeStepResult.Complete(LaunchResultBuilder.Build(
|
||||
false,
|
||||
"oobe",
|
||||
completion.ResultCode,
|
||||
"OOBE settings could not be saved.",
|
||||
errorMessage: completion.ErrorMessage));
|
||||
}
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
@@ -46,5 +67,16 @@ internal sealed class WelcomeOobeStep : IOobeStep
|
||||
window.Close();
|
||||
}
|
||||
});
|
||||
|
||||
return OobeStepResult.Continue;
|
||||
}
|
||||
|
||||
private static OobeStepResult BuildCancelledResult(string message)
|
||||
{
|
||||
return OobeStepResult.Complete(LaunchResultBuilder.Build(
|
||||
false,
|
||||
"oobe",
|
||||
"oobe_cancelled",
|
||||
message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using LanMountainDesktop.Shared.IPC;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
|
||||
@@ -24,11 +25,21 @@ internal sealed class AirAppRuntimeBridge
|
||||
return;
|
||||
}
|
||||
|
||||
var process = AirAppRuntimeProcessStarter.Start(new AirAppRuntimeStartRequest(
|
||||
_appRoot,
|
||||
Environment.ProcessId,
|
||||
0,
|
||||
_dataRoot));
|
||||
Process? process;
|
||||
try
|
||||
{
|
||||
process = AirAppRuntimeProcessStarter.Start(new AirAppRuntimeStartRequest(
|
||||
_appRoot,
|
||||
Environment.ProcessId,
|
||||
0,
|
||||
_dataRoot));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn($"AirApp Runtime start request failed. AppRoot='{_appRoot}'; Error='{ex.Message}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Info($"AirApp Runtime start requested. Pid={(process is null ? -1 : process.Id)}; AppRoot='{_appRoot}'.");
|
||||
|
||||
for (var attempt = 1; attempt <= ConnectAttempts; attempt++)
|
||||
|
||||
@@ -116,7 +116,7 @@ internal static class PreviewEntryHandler
|
||||
{
|
||||
try
|
||||
{
|
||||
await window.WaitForEnterAsync().ConfigureAwait(false);
|
||||
await window.WaitForCompletionAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -24,8 +24,6 @@ internal static class LauncherGuiCoordinator
|
||||
var startupAttemptRegistry = new StartupAttemptRegistry();
|
||||
var coordinatorPipeName = LauncherCoordinatorIpcServer.CreatePipeName();
|
||||
var successPolicy = LauncherOrchestrator.ResolveSuccessPolicyKey(context);
|
||||
var airAppRuntimeBridge = new AirAppRuntimeBridge(appRoot, dataLocationResolver.ResolveDataRoot());
|
||||
await airAppRuntimeBridge.EnsureStartedAsync().ConfigureAwait(false);
|
||||
|
||||
if (!startupAttemptRegistry.TryReserveCoordinator(
|
||||
context.LaunchSource,
|
||||
@@ -123,6 +121,7 @@ internal static class LauncherGuiCoordinator
|
||||
if (result.Success)
|
||||
{
|
||||
var hostPid = ResolveManagedHostPid(result, startupAttemptRegistry.GetOwnedAttempt()?.HostPid ?? 0);
|
||||
var airAppRuntimeBridge = new AirAppRuntimeBridge(appRoot, dataLocationResolver.ResolveDataRoot());
|
||||
await airAppRuntimeBridge.AttachHostAsync(hostPid).ConfigureAwait(false);
|
||||
await WaitForHostProcessToExitAsync(hostPid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ internal sealed class LauncherOrchestrator
|
||||
_dataLocationResolver = new DataLocationResolver(deploymentLocator.GetAppRoot());
|
||||
_oobeSteps =
|
||||
[
|
||||
new WelcomeOobeStep(_oobeStateService, _context),
|
||||
new DataLocationOobeStep(_dataLocationResolver)
|
||||
new WelcomeOobeStep(_oobeStateService, _context, _dataLocationResolver)
|
||||
];
|
||||
_pipeline = pipeline ?? new LaunchPipeline(
|
||||
[
|
||||
|
||||
@@ -108,6 +108,8 @@ internal sealed class HostLaunchService
|
||||
return HostLaunchOutcome.FromResult(prerequisiteFailure);
|
||||
}
|
||||
|
||||
await EnsureAirAppRuntimeStartedAsync(context.DeploymentLocator.GetAppRoot(), dataRoot).ConfigureAwait(false);
|
||||
|
||||
var hostPath = plan.HostPath;
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
@@ -204,6 +206,18 @@ internal sealed class HostLaunchService
|
||||
details));
|
||||
}
|
||||
|
||||
private static async Task EnsureAirAppRuntimeStartedAsync(string appRoot, string? dataRoot)
|
||||
{
|
||||
try
|
||||
{
|
||||
await new AirAppRuntimeBridge(appRoot, dataRoot).EnsureStartedAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn($"AirApp Runtime pre-start failed; Host fallback remains available. Error='{ex.Message}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<HostStartAttempt> StartHostProcessAsync(
|
||||
HostLaunchPlan plan,
|
||||
HostStartMode startMode,
|
||||
|
||||
@@ -13,7 +13,18 @@ internal sealed class OobeGatePhase : ILaunchPhase
|
||||
await LaunchUiPresenter.HideSplashAsync(context.SplashWindow).ConfigureAwait(false);
|
||||
foreach (var step in context.OobeSteps)
|
||||
{
|
||||
await step.RunAsync(cancellationToken).ConfigureAwait(false);
|
||||
var stepResult = await step.RunAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (!stepResult.ContinueLaunch)
|
||||
{
|
||||
context.WindowsClosingByOrchestrator = true;
|
||||
await LaunchUiPresenter.CloseWindowsAsync(context.SplashWindow, context.LoadingDetailsWindow).ConfigureAwait(false);
|
||||
return new LaunchPhaseResult(
|
||||
LaunchPhaseStatus.Completed,
|
||||
stepResult.Result ?? LaunchResultBuilder.BuildFailure(
|
||||
"oobe",
|
||||
"oobe_cancelled",
|
||||
"OOBE did not complete."));
|
||||
}
|
||||
}
|
||||
|
||||
await LaunchUiPresenter.ShowSplashAsync(context.SplashWindow).ConfigureAwait(false);
|
||||
|
||||
@@ -126,7 +126,7 @@ public partial class DevDebugWindow : Window
|
||||
try
|
||||
{
|
||||
// 等待用户点击开始按钮
|
||||
await oobeWindow.WaitForEnterAsync();
|
||||
await oobeWindow.WaitForCompletionAsync();
|
||||
|
||||
// 用户点击后,窗口会自动关闭(通过OobeWindow内部的动画和关闭逻辑)
|
||||
Console.WriteLine("[DevDebugWindow] OOBE completed by user");
|
||||
|
||||
@@ -17,10 +17,11 @@ public partial class OobeWindow : Window
|
||||
private const int AnimationDurationMs = 300;
|
||||
private const int TypingDelayMs = 100;
|
||||
|
||||
private readonly TaskCompletionSource<bool> _completionSource = new();
|
||||
private readonly TaskCompletionSource<OobeSessionDraft?> _completionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly DataLocationResolver _resolver;
|
||||
private bool _isTransitioning;
|
||||
private bool _isDebugMode;
|
||||
private bool _isCompleting;
|
||||
private int _currentStep = 1;
|
||||
|
||||
// 数据位置选择
|
||||
@@ -40,6 +41,7 @@ public partial class OobeWindow : Window
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
Loaded += OnWindowLoaded;
|
||||
Opened += OnWindowOpened;
|
||||
Closed += OnWindowClosed;
|
||||
|
||||
var appRoot = AppDomain.CurrentDomain.BaseDirectory;
|
||||
_resolver = new DataLocationResolver(appRoot);
|
||||
@@ -51,7 +53,7 @@ public partial class OobeWindow : Window
|
||||
_isDebugMode = isDebugMode;
|
||||
}
|
||||
|
||||
public Task WaitForEnterAsync() => _completionSource.Task;
|
||||
internal Task<OobeSessionDraft?> WaitForCompletionAsync() => _completionSource.Task;
|
||||
|
||||
private void OnWindowLoaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -261,6 +263,14 @@ public partial class OobeWindow : Window
|
||||
await PlayTypingAnimationAsync();
|
||||
}
|
||||
|
||||
private void OnWindowClosed(object? sender, EventArgs e)
|
||||
{
|
||||
if (!_isCompleting)
|
||||
{
|
||||
_completionSource.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PlayTypingAnimationAsync()
|
||||
{
|
||||
var typingTextBlock = this.FindControl<TextBlock>("TypingTextBlock");
|
||||
@@ -477,11 +487,6 @@ public partial class OobeWindow : Window
|
||||
if (_isTransitioning) return;
|
||||
|
||||
// 应用数据位置选择
|
||||
if (!_isDebugMode)
|
||||
{
|
||||
_resolver.ApplyLocationChoice(_selectedDataLocationMode, null, _migrateExistingData);
|
||||
}
|
||||
|
||||
await NavigateToStep(4);
|
||||
}
|
||||
|
||||
@@ -495,7 +500,6 @@ public partial class OobeWindow : Window
|
||||
private async void OnStartupPresentationNextClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isTransitioning) return;
|
||||
SaveOobeStartupPresentation();
|
||||
await NavigateToStep(5);
|
||||
}
|
||||
|
||||
@@ -521,7 +525,7 @@ public partial class OobeWindow : Window
|
||||
|
||||
private void RefreshOobeStartupPresentationFromDisk()
|
||||
{
|
||||
var path = HostAppSettingsOobeMerger.GetSettingsFilePath(_resolver.ResolveDataRoot());
|
||||
var path = HostAppSettingsOobeMerger.GetSettingsFilePath(ResolveSelectedDataRoot());
|
||||
var defaults = HostAppSettingsOobeMerger.LoadStartupDefaults(path);
|
||||
|
||||
if (this.FindControl<Border>("OobeSlideTransitionSection") is { } slideSection)
|
||||
@@ -675,8 +679,6 @@ public partial class OobeWindow : Window
|
||||
if (_isTransitioning) return;
|
||||
|
||||
// 保存隐私设置
|
||||
SavePrivacySettings();
|
||||
|
||||
await NavigateToStep(6);
|
||||
}
|
||||
|
||||
@@ -725,13 +727,15 @@ public partial class OobeWindow : Window
|
||||
try
|
||||
{
|
||||
await PlayExitAnimationAsync();
|
||||
_completionSource.TrySetResult(true);
|
||||
_isCompleting = true;
|
||||
_completionSource.TrySetResult(BuildSessionDraft());
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[OobeWindow] Error: {ex.Message}");
|
||||
_completionSource.TrySetResult(true);
|
||||
_isCompleting = true;
|
||||
_completionSource.TrySetResult(BuildSessionDraft());
|
||||
Close();
|
||||
}
|
||||
}
|
||||
@@ -978,6 +982,43 @@ public partial class OobeWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
private OobeSessionDraft BuildSessionDraft()
|
||||
{
|
||||
var privacy = BuildPrivacyConfig();
|
||||
return new OobeSessionDraft
|
||||
{
|
||||
DataLocationMode = _selectedDataLocationMode,
|
||||
MigrateExistingData = _migrateExistingData,
|
||||
StartupChoices = CollectOobeStartupChoices(),
|
||||
PrivacyConfig = privacy,
|
||||
PrivacyAgreementAccepted = this.FindControl<CheckBox>("PrivacyAgreementCheckBox")?.IsChecked ?? false,
|
||||
PrivacyUserId = privacy.TelemetryId,
|
||||
PrivacyDeviceId = GetDeviceIdentifier()
|
||||
};
|
||||
}
|
||||
|
||||
private PrivacyConfig BuildPrivacyConfig()
|
||||
{
|
||||
return new PrivacyConfig
|
||||
{
|
||||
CrashTelemetryEnabled = this.FindControl<ToggleSwitch>("CrashTelemetryToggle")?.IsChecked ?? true,
|
||||
UsageTelemetryEnabled = this.FindControl<ToggleSwitch>("UsageTelemetryToggle")?.IsChecked ?? true,
|
||||
TelemetryId = this.FindControl<TextBox>("TelemetryIdTextBox")?.Text ?? Guid.NewGuid().ToString("N")
|
||||
};
|
||||
}
|
||||
|
||||
private string ResolveSelectedDataRoot()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _resolver.ResolveDataRoot(_selectedDataLocationMode);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return _resolver.DefaultSystemDataPath;
|
||||
}
|
||||
}
|
||||
|
||||
private static double EaseOutCubic(double t) => 1 - Math.Pow(1 - t, 3);
|
||||
private static double EaseOutQuad(double t) => 1 - Math.Pow(1 - t, 2);
|
||||
private static double EaseOutBack(double t)
|
||||
|
||||
Reference in New Issue
Block a user