Files
launcher-next/Launcher.Next.Application/LegacyConfigurationProbe.cs
T

46 lines
4.1 KiB
C#

using System.Security.Cryptography;
using System.Text.Json;
using Launcher.Next.Core;
namespace Launcher.Next.Application;
public sealed record LegacyConfigurationProbeRequest(ResolvedLegacySettingsPath Settings, ResolvedLegacyDownloadListPath DownloadList, ResolvedLegacyProbeEvidenceRoot EvidenceRoot, string PatternName, LegacyCompatibilityOverlay Overlay, string OperationId);
public sealed record LegacyConfigurationProbeResult(string TerminalState, IReadOnlyList<PackageRequirement> Requirements, string EvidencePath, IReadOnlyList<string> Diagnostics);
/// <summary>Read-only configuration inspection. It intentionally has no package,
/// activation, process, or Autodesk dependency.</summary>
public sealed class LegacyConfigurationProbe(LegacyConfigurationAdapter adapter)
{
public async Task<LegacyConfigurationProbeResult> RunAsync(LegacyConfigurationProbeRequest request, CancellationToken cancellationToken = default)
{
var settingsPath = request.Settings.Value;
var selectionPath = request.DownloadList.Value;
var evidenceRoot = request.EvidenceRoot.Value;
if (!File.Exists(settingsPath) || !File.Exists(selectionPath)) throw new InvalidOperationException("LEGACY_PROBE_INPUT_PATH_INVALID");
if (Path.GetDirectoryName(settingsPath)!.Equals(evidenceRoot, StringComparison.OrdinalIgnoreCase) || Path.GetDirectoryName(selectionPath)!.Equals(evidenceRoot, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("PATH_ROLE_COLLISION");
Directory.CreateDirectory(evidenceRoot);
var before = await Snapshot(settingsPath, selectionPath, cancellationToken);
try
{
var settings = adapter.ParseSettings(await File.ReadAllTextAsync(settingsPath, cancellationToken));
var pattern = settings.Patterns.Where(x => string.Equals(x.Name, request.PatternName, StringComparison.Ordinal)).ToArray();
if (pattern.Length != 1) throw new InvalidOperationException(pattern.Length == 0 ? "LEGACY_PATTERN_NOT_FOUND" : "LEGACY_PATTERN_AMBIGUOUS");
var requirements = adapter.Join(pattern[0], adapter.ParseDownloadList(await File.ReadAllTextAsync(selectionPath, cancellationToken)), request.Overlay);
var after = await Snapshot(settingsPath, selectionPath, cancellationToken);
if (!before.SequenceEqual(after)) throw new InvalidOperationException("LEGACY_PROBE_SOURCE_CHANGED_DURING_READ");
var evidence = new { mode="LegacyConfigurationProbe", operationId=request.OperationId, terminalState="LEGACY_CONFIGURATION_VERIFIED", selectedPattern=pattern[0].Name, selectedIdCount=requirements.Count, sourceWrites=false, payloadCopied=false, stagingCreated=false, activationAttempted=false, autoCadLaunchAttempted=false, dfsWriteAttempted=false, inputSnapshots=before.Select(x=>Convert.ToHexString(SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(x)))) };
var output=Path.Combine(evidenceRoot, request.OperationId + ".json");
await File.WriteAllTextAsync(output, JsonSerializer.Serialize(evidence), cancellationToken);
return new("LEGACY_CONFIGURATION_VERIFIED",requirements,output,[]);
}
catch(Exception ex)
{
var output=Path.Combine(evidenceRoot, request.OperationId + ".json");
await File.WriteAllTextAsync(output, JsonSerializer.Serialize(new { mode="LegacyConfigurationProbe", operationId=request.OperationId, terminalState="PILOT_CONFIGURATION_BLOCKED", diagnostic=ex.Message, sourceWrites=false, payloadCopied=false, stagingCreated=false, activationAttempted=false, autoCadLaunchAttempted=false, dfsWriteAttempted=false }), cancellationToken);
return new("PILOT_CONFIGURATION_BLOCKED",[],output,[ex.Message]);
}
}
private static async Task<string[]> Snapshot(string a,string b,CancellationToken ct) => [await One(a,ct),await One(b,ct)];
private static async Task<string> One(string path,CancellationToken ct) { var info=new FileInfo(path); return path+"|"+info.Length+"|"+info.LastWriteTimeUtc.Ticks+"|"+Convert.ToHexString(await SHA256.HashDataAsync(File.OpenRead(path),ct)); }
}