using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; namespace StellaOps.Concelier.Exporter.Json; public static class ExportDigestCalculator { public static string ComputeTreeDigest(JsonExportResult result) { ArgumentNullException.ThrowIfNull(result); using var sha256 = SHA256.Create(); var buffer = new byte[128 * 1024]; foreach (var file in result.FilePaths.OrderBy(static path => path, StringComparer.Ordinal)) { var normalized = file.Replace("\\", "/"); var pathBytes = Encoding.UTF8.GetBytes(normalized); _ = sha256.TransformBlock(pathBytes, 0, pathBytes.Length, null, 0); var fullPath = ResolveFullPath(result.ExportDirectory, normalized); using var stream = File.OpenRead(fullPath); int read; while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) { _ = sha256.TransformBlock(buffer, 0, read, null, 0); } } _ = sha256.TransformFinalBlock(Array.Empty(), 0, 0); var hash = sha256.Hash ?? Array.Empty(); var hex = Convert.ToHexString(hash).ToLowerInvariant(); return $"sha256:{hex}"; } private static string ResolveFullPath(string root, string normalizedRelativePath) { var segments = normalizedRelativePath.Split('/', StringSplitOptions.RemoveEmptyEntries); var parts = new string[segments.Length + 1]; parts[0] = root; for (var i = 0; i < segments.Length; i++) { parts[i + 1] = segments[i]; } return Path.Combine(parts); } }