mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
fix.在线安装器,启动器
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user