Add configurable data location (portable/system)

Introduce support for choosing and resolving the application's data root (system user dir vs. portable app folder). Adds DataLocationConfig model, DataLocationResolver (load/save/resolve/migrate), a UI prompt (DataLocationPromptWindow) and an OOBE step (DataLocationOobeStep) to let users pick and optionally migrate existing data. Wire the chosen data root into the launcher flow and host launch plan (forwarded via --data-root and LMD_DATA_ROOT), and add AppDataPathProvider to let runtime services read the effective data root (initialized in Program.Main). Update various services (logging, settings, DB, plugin/market, startup registry, etc.) to use the new provider/resolver and register the config type in the JSON context. This enables portable installs, safe migration, and runtime overrides via CLI or environment variable.
This commit is contained in:
lincube
2026-04-24 12:30:05 +08:00
parent 28f41cd27c
commit ad3648a0b8
21 changed files with 970 additions and 46 deletions

View File

@@ -12,10 +12,12 @@ internal sealed record HostLaunchPlan(
internal static class HostLaunchPlanBuilder
{
public const string DataRootOptionName = "data-root";
private static readonly string[] LauncherOnlyOptions =
[
"debug", "show-loading-details", "plugins-dir", "source", "result",
"app-root",
"app-root", DataRootOptionName,
LauncherIpcConstants.LauncherPidEnvVar,
LauncherIpcConstants.PackageRootEnvVar,
LauncherIpcConstants.VersionEnvVar,
@@ -25,7 +27,8 @@ internal static class HostLaunchPlanBuilder
public static HostLaunchPlan Build(
CommandContext context,
DeploymentLocator deploymentLocator,
HostResolutionResult resolution)
HostResolutionResult resolution,
string? dataRoot = null)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(deploymentLocator);
@@ -39,7 +42,7 @@ internal static class HostLaunchPlanBuilder
var hostPath = Path.GetFullPath(resolution.ResolvedHostPath);
var packageRoot = ResolvePackageRoot(hostPath, resolution.AppRoot, resolution.ResolutionSource);
var versionInfo = deploymentLocator.GetVersionInfo();
var arguments = BuildForwardedArguments(context, packageRoot, versionInfo);
var arguments = BuildForwardedArguments(context, packageRoot, versionInfo, dataRoot);
var environment = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
[LauncherIpcConstants.LauncherPidEnvVar] = Environment.ProcessId.ToString(),
@@ -48,6 +51,11 @@ internal static class HostLaunchPlanBuilder
[LauncherIpcConstants.CodenameEnvVar] = versionInfo.Codename
};
if (!string.IsNullOrWhiteSpace(dataRoot))
{
environment["LMD_DATA_ROOT"] = dataRoot;
}
return new HostLaunchPlan(
hostPath,
packageRoot,
@@ -92,7 +100,8 @@ internal static class HostLaunchPlanBuilder
private static IReadOnlyList<string> BuildForwardedArguments(
CommandContext context,
string packageRoot,
AppVersionInfo versionInfo)
AppVersionInfo versionInfo,
string? dataRoot = null)
{
var arguments = new List<string>();
@@ -144,6 +153,11 @@ internal static class HostLaunchPlanBuilder
arguments.Add($"--{LauncherIpcConstants.VersionEnvVar}={versionInfo.Version}");
arguments.Add($"--{LauncherIpcConstants.CodenameEnvVar}={versionInfo.Codename}");
if (!string.IsNullOrWhiteSpace(dataRoot))
{
arguments.Add($"--{DataRootOptionName}={dataRoot}");
}
return arguments;
}