mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-21 08:04:26 +08:00
feat.PLONDS系统会不断地改进
This commit is contained in:
@@ -1,157 +0,0 @@
|
||||
namespace Plonds.Core.Publishing;
|
||||
|
||||
public static class PlondsCommitAnalyzer
|
||||
{
|
||||
private static readonly string[] SourceDirectories =
|
||||
[
|
||||
"LanMountainDesktop/",
|
||||
"LanMountainDesktop.Launcher/",
|
||||
"LanMountainDesktop.Shared.Contracts/",
|
||||
"LanMountainDesktop.PluginSdk/",
|
||||
"LanMountainDesktop.Appearance/",
|
||||
"LanMountainDesktop.Settings.Core/",
|
||||
"LanMountainDesktop.ComponentSystem/"
|
||||
];
|
||||
|
||||
private static readonly (string Prefix, string[] Artifacts)[] SourceToArtifactMappings =
|
||||
[
|
||||
("LanMountainDesktop/", ["LanMountainDesktop.dll", "LanMountainDesktop.exe"]),
|
||||
("LanMountainDesktop.Launcher/", ["LanMountainDesktop.Launcher.exe", "LanMountainDesktop.Launcher.dll"]),
|
||||
("LanMountainDesktop.Shared.Contracts/", ["LanMountainDesktop.Shared.Contracts.dll"]),
|
||||
("LanMountainDesktop.PluginSdk/", ["LanMountainDesktop.PluginSdk.dll"]),
|
||||
("LanMountainDesktop.Appearance/", ["LanMountainDesktop.Appearance.dll"]),
|
||||
("LanMountainDesktop.Settings.Core/", ["LanMountainDesktop.Settings.Core.dll"]),
|
||||
("LanMountainDesktop.ComponentSystem/", ["LanMountainDesktop.ComponentSystem.dll"])
|
||||
];
|
||||
|
||||
private static readonly string[] SourceCodeExtensions =
|
||||
[
|
||||
".cs", ".axaml", ".xaml", ".csproj"
|
||||
];
|
||||
|
||||
public static HashSet<string> GetChangedSourceFiles(string baselineTag, string currentTag)
|
||||
{
|
||||
var changedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var start = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = $"log --name-only --pretty=format: {baselineTag}..{currentTag}",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
|
||||
if (start is null)
|
||||
{
|
||||
return changedFiles;
|
||||
}
|
||||
|
||||
var output = start.StandardOutput.ReadToEnd();
|
||||
start.WaitForExit();
|
||||
|
||||
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var trimmed = line.Trim().Replace('\\', '/');
|
||||
if (string.IsNullOrWhiteSpace(trimmed))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsSourceDirectoryFile(trimmed))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
changedFiles.Add(trimmed);
|
||||
}
|
||||
|
||||
return changedFiles;
|
||||
}
|
||||
|
||||
public static HashSet<string> MapSourceFilesToArtifacts(IReadOnlySet<string> sourceFiles)
|
||||
{
|
||||
var artifacts = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var hasUnmappedChanges = false;
|
||||
|
||||
foreach (var sourceFile in sourceFiles)
|
||||
{
|
||||
var normalized = sourceFile.Replace('\\', '/');
|
||||
var mapped = false;
|
||||
|
||||
foreach (var (prefix, artifactList) in SourceToArtifactMappings)
|
||||
{
|
||||
if (!normalized.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsSourceCodeFile(normalized) && !IsConfigFile(normalized))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var artifact in artifactList)
|
||||
{
|
||||
artifacts.Add(artifact);
|
||||
}
|
||||
|
||||
mapped = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mapped && IsConfigFile(normalized))
|
||||
{
|
||||
var artifactPath = MapConfigToArtifact(normalized);
|
||||
if (artifactPath is not null)
|
||||
{
|
||||
artifacts.Add(artifactPath);
|
||||
mapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mapped)
|
||||
{
|
||||
hasUnmappedChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUnmappedChanges)
|
||||
{
|
||||
foreach (var (_, artifactList) in SourceToArtifactMappings)
|
||||
{
|
||||
foreach (var artifact in artifactList)
|
||||
{
|
||||
artifacts.Add(artifact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return artifacts;
|
||||
}
|
||||
|
||||
public static bool IsSourceDirectoryFile(string path)
|
||||
{
|
||||
var normalized = path.Replace('\\', '/');
|
||||
return SourceDirectories.Any(d => normalized.StartsWith(d, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static bool IsSourceCodeFile(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path);
|
||||
return SourceCodeExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsConfigFile(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path);
|
||||
return string.Equals(ext, ".json", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(ext, ".xml", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string? MapConfigToArtifact(string sourcePath)
|
||||
{
|
||||
var fileName = Path.GetFileName(sourcePath);
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public sealed record PlondsCommitDeltaBuildOptions(
|
||||
string Channel,
|
||||
string BaselineTag,
|
||||
string CurrentTag,
|
||||
string HashAlgorithm = "sha256",
|
||||
string? SourceDirs = null,
|
||||
string? FallbackBaselineZip = null,
|
||||
string? BaselineVersion = null,
|
||||
string LauncherRelativePath = "LanMountainDesktop.Launcher.exe",
|
||||
string HashAlgorithm = "sha256");
|
||||
string LauncherRelativePath = "LanMountainDesktop.Launcher.exe");
|
||||
|
||||
@@ -13,11 +13,35 @@ public sealed class PlondsCommitDeltaBuilder
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public PlondsCommitDeltaBuildResult Build(PlondsCommitDeltaBuildOptions options)
|
||||
private static readonly Dictionary<string, string[]> SourceToArtifactMap = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["LanMountainDesktop"] = ["LanMountainDesktop.dll", "LanMountainDesktop.exe"],
|
||||
["LanMountainDesktop.Launcher"] = ["LanMountainDesktop.Launcher.exe"],
|
||||
["LanMountainDesktop.Shared.Contracts"] = ["LanMountainDesktop.Shared.Contracts.dll"],
|
||||
["LanMountainDesktop.PluginSdk"] = ["LanMountainDesktop.PluginSdk.dll"],
|
||||
["LanMountainDesktop.Appearance"] = ["LanMountainDesktop.Appearance.dll"],
|
||||
["LanMountainDesktop.Settings.Core"] = ["LanMountainDesktop.Settings.Core.dll"],
|
||||
["LanMountainDesktop.ComponentSystem"] = ["LanMountainDesktop.ComponentSystem.dll"]
|
||||
};
|
||||
|
||||
private static readonly string[] FallbackAllArtifacts =
|
||||
[
|
||||
"LanMountainDesktop.dll",
|
||||
"LanMountainDesktop.exe",
|
||||
"LanMountainDesktop.Launcher.exe",
|
||||
"LanMountainDesktop.Shared.Contracts.dll",
|
||||
"LanMountainDesktop.PluginSdk.dll",
|
||||
"LanMountainDesktop.Appearance.dll",
|
||||
"LanMountainDesktop.Settings.Core.dll",
|
||||
"LanMountainDesktop.ComponentSystem.dll"
|
||||
];
|
||||
|
||||
public PlondsDeltaBuildResult Build(PlondsCommitDeltaBuildOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var hashAlgorithm = PlondsDeltaBuilder.ValidateHashAlgorithmInternal(options.HashAlgorithm);
|
||||
var hashAlgorithm = ValidateHashAlgorithm(options.HashAlgorithm);
|
||||
var sourceDirs = ParseSourceDirs(options.SourceDirs);
|
||||
|
||||
var currentPayloadZip = Path.GetFullPath(options.CurrentPayloadZip);
|
||||
if (!File.Exists(currentPayloadZip))
|
||||
@@ -32,50 +56,62 @@ public sealed class PlondsCommitDeltaBuilder
|
||||
Directory.CreateDirectory(outputRoot);
|
||||
PayloadUtilities.ExtractZip(currentPayloadZip, currentExtractRoot);
|
||||
|
||||
var changedSourceFiles = PlondsCommitAnalyzer.GetChangedSourceFiles(options.BaselineTag, options.CurrentTag);
|
||||
var changedSourceFiles = GetChangedSourceFiles(options.BaselineTag, options.CurrentTag, sourceDirs);
|
||||
|
||||
if (changedSourceFiles.Count == 0)
|
||||
{
|
||||
return FallbackToFileCompare(options, currentPayloadZip, outputRoot, workRoot, hashAlgorithm);
|
||||
Console.WriteLine("No source code changes detected between tags. Falling back to file-compare method.");
|
||||
return FallbackToFileCompare(options, currentExtractRoot, outputRoot, hashAlgorithm);
|
||||
}
|
||||
|
||||
var mappedArtifacts = PlondsCommitAnalyzer.MapSourceFilesToArtifacts(changedSourceFiles);
|
||||
Console.WriteLine($"Detected {changedSourceFiles.Count} changed source file(s) between {options.BaselineTag} and {options.CurrentTag}.");
|
||||
foreach (var file in changedSourceFiles.Take(20))
|
||||
{
|
||||
Console.WriteLine($" {file}");
|
||||
}
|
||||
|
||||
if (changedSourceFiles.Count > 20)
|
||||
{
|
||||
Console.WriteLine($" ... and {changedSourceFiles.Count - 20} more");
|
||||
}
|
||||
|
||||
var artifactFiles = MapSourceToArtifacts(changedSourceFiles, sourceDirs);
|
||||
var currentManifest = PayloadUtilities.ScanDirectory(currentExtractRoot);
|
||||
|
||||
var filesMap = new Dictionary<string, PlondsFileEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
var changedFilesMap = new Dictionary<string, PlondsChangedFileEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var artifact in mappedArtifacts.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
||||
foreach (var artifactFile in artifactFiles)
|
||||
{
|
||||
if (!currentManifest.TryGetValue(artifact, out var fingerprint))
|
||||
var normalizedPath = artifactFile.Replace('\\', '/');
|
||||
if (!currentManifest.TryGetValue(normalizedPath, out var fingerprint))
|
||||
{
|
||||
Console.WriteLine($" Artifact not found in current zip: {normalizedPath}, skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var hash = GetHash(fingerprint, hashAlgorithm);
|
||||
var fileHash = PlondsDeltaBuilder.ComputeHash(fingerprint.FullPath, hashAlgorithm);
|
||||
var action = PlondsConstants.ActionReplace;
|
||||
|
||||
filesMap[artifact] = new PlondsFileEntry(action, hash, fingerprint.Size, hashAlgorithm);
|
||||
changedFilesMap[artifact] = new PlondsChangedFileEntry(artifact, hash, fingerprint.Size, hashAlgorithm);
|
||||
filesMap[normalizedPath] = new PlondsFileEntry(action, fileHash, fingerprint.Size, hashAlgorithm);
|
||||
changedFilesMap[normalizedPath] = new PlondsChangedFileEntry(normalizedPath, fileHash, fingerprint.Size, hashAlgorithm);
|
||||
}
|
||||
|
||||
var changedZipPath = CreateChangedZipFromArtifacts(currentExtractRoot, mappedArtifacts, outputRoot, options.Platform);
|
||||
|
||||
var requiresCleanInstall = mappedArtifacts.Contains(options.LauncherRelativePath, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var changedZipPath = CreateChangedZipFromList(currentExtractRoot, artifactFiles, outputRoot, options.Platform);
|
||||
var changedZipMd5 = ComputeMd5Hex(changedZipPath);
|
||||
|
||||
var launcherInChanges = artifactFiles.Any(f =>
|
||||
string.Equals(Path.GetFileName(f), "LanMountainDesktop.Launcher.exe", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var manifest = new PlondsManifest(
|
||||
FormatVersion: PlondsConstants.FormatVersion,
|
||||
CurrentVersion: options.CurrentVersion,
|
||||
PreviousVersion: options.BaselineVersion ?? options.BaselineTag.TrimStart('v'),
|
||||
PreviousVersion: options.BaselineTag.TrimStart('v'),
|
||||
IsFullUpdate: false,
|
||||
RequiresCleanInstall: requiresCleanInstall,
|
||||
RequiresCleanInstall: launcherInChanges,
|
||||
Channel: options.Channel,
|
||||
Platform: options.Platform,
|
||||
UpdatedAt: DateTimeOffset.UtcNow,
|
||||
CompareMethod: PlondsConstants.CompareMethodCommitAnalyze,
|
||||
HashAlgorithm: hashAlgorithm,
|
||||
FilesMap: filesMap,
|
||||
ChangedFilesMap: changedFilesMap,
|
||||
Checksums: new Dictionary<string, string>
|
||||
@@ -87,24 +123,125 @@ public sealed class PlondsCommitDeltaBuilder
|
||||
var manifestJson = JsonSerializer.Serialize(manifest, JsonOptions);
|
||||
File.WriteAllText(manifestPath, manifestJson);
|
||||
|
||||
return new PlondsCommitDeltaBuildResult(
|
||||
return new PlondsDeltaBuildResult(
|
||||
Platform: options.Platform,
|
||||
ChangedZipPath: changedZipPath,
|
||||
ManifestPath: manifestPath,
|
||||
IsFullUpdate: false,
|
||||
RequiresCleanInstall: requiresCleanInstall,
|
||||
FellBackToFileCompare: false,
|
||||
RequiresCleanInstall: launcherInChanges,
|
||||
CurrentVersion: options.CurrentVersion,
|
||||
BaselineVersion: options.BaselineVersion,
|
||||
ChangedSourceFiles: changedSourceFiles.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToArray(),
|
||||
MappedArtifactFiles: mappedArtifacts.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToArray());
|
||||
BaselineVersion: options.BaselineTag.TrimStart('v'));
|
||||
}
|
||||
|
||||
private PlondsCommitDeltaBuildResult FallbackToFileCompare(
|
||||
private static List<string> GetChangedSourceFiles(string baselineTag, string currentTag, string[] sourceDirs)
|
||||
{
|
||||
var changedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var normalizedBaseline = baselineTag.StartsWith("v") ? baselineTag : $"v{baselineTag}";
|
||||
var normalizedCurrent = currentTag.StartsWith("v") ? currentTag : $"v{currentTag}";
|
||||
|
||||
var psi = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = $"diff --name-only {normalizedBaseline}..{normalizedCurrent}",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = System.Diagnostics.Process.Start(psi)
|
||||
?? throw new InvalidOperationException("Failed to start git process.");
|
||||
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"git diff failed with exit code {process.ExitCode}.");
|
||||
}
|
||||
|
||||
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (string.IsNullOrWhiteSpace(trimmed))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var isSourceFile = sourceDirs.Any(dir =>
|
||||
trimmed.StartsWith(dir + "/", StringComparison.OrdinalIgnoreCase) ||
|
||||
trimmed.StartsWith(dir + "\\", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (isSourceFile)
|
||||
{
|
||||
changedFiles.Add(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
return changedFiles.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
}
|
||||
|
||||
private static HashSet<string> MapSourceToArtifacts(IReadOnlyList<string> changedSourceFiles, string[] sourceDirs)
|
||||
{
|
||||
var artifacts = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var hasUnmappedChanges = false;
|
||||
|
||||
foreach (var sourceFile in changedSourceFiles)
|
||||
{
|
||||
var mapped = false;
|
||||
|
||||
foreach (var dir in sourceDirs)
|
||||
{
|
||||
if (!sourceFile.StartsWith(dir + "/", StringComparison.OrdinalIgnoreCase) &&
|
||||
!sourceFile.StartsWith(dir + "\\", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SourceToArtifactMap.TryGetValue(dir, out var artifactList))
|
||||
{
|
||||
foreach (var artifact in artifactList)
|
||||
{
|
||||
artifacts.Add(artifact);
|
||||
}
|
||||
|
||||
mapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mapped)
|
||||
{
|
||||
var extension = Path.GetExtension(sourceFile).ToLowerInvariant();
|
||||
if (extension is ".json" or ".xml" or ".config")
|
||||
{
|
||||
var fileName = Path.GetFileName(sourceFile);
|
||||
artifacts.Add(fileName);
|
||||
mapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mapped)
|
||||
{
|
||||
hasUnmappedChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUnmappedChanges)
|
||||
{
|
||||
Console.WriteLine("Unmapped source changes detected. Including all core artifacts as a conservative fallback.");
|
||||
foreach (var artifact in FallbackAllArtifacts)
|
||||
{
|
||||
artifacts.Add(artifact);
|
||||
}
|
||||
}
|
||||
|
||||
return artifacts;
|
||||
}
|
||||
|
||||
private static PlondsDeltaBuildResult FallbackToFileCompare(
|
||||
PlondsCommitDeltaBuildOptions options,
|
||||
string currentPayloadZip,
|
||||
string currentExtractRoot,
|
||||
string outputRoot,
|
||||
string workRoot,
|
||||
string hashAlgorithm)
|
||||
{
|
||||
var fallbackZip = string.IsNullOrWhiteSpace(options.FallbackBaselineZip)
|
||||
@@ -113,97 +250,35 @@ public sealed class PlondsCommitDeltaBuilder
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fallbackZip) || !File.Exists(fallbackZip))
|
||||
{
|
||||
var currentExtractRoot = Path.Combine(workRoot, "current");
|
||||
PayloadUtilities.ExtractZip(currentPayloadZip, currentExtractRoot);
|
||||
var currentManifest = PayloadUtilities.ScanDirectory(currentExtractRoot);
|
||||
|
||||
var filesMap = new Dictionary<string, PlondsFileEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
var changedFilesMap = new Dictionary<string, PlondsChangedFileEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var path in currentManifest.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var fp = currentManifest[path];
|
||||
var hash = GetHash(fp, hashAlgorithm);
|
||||
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionAdd, hash, fp.Size, hashAlgorithm);
|
||||
changedFilesMap[path] = new PlondsChangedFileEntry(path, hash, fp.Size, hashAlgorithm);
|
||||
}
|
||||
|
||||
var changedZipPath = CreateChangedZipFromArtifacts(currentExtractRoot, filesMap.Keys.ToHashSet(), outputRoot, options.Platform);
|
||||
var changedZipMd5 = ComputeMd5Hex(changedZipPath);
|
||||
|
||||
var manifest = new PlondsManifest(
|
||||
FormatVersion: PlondsConstants.FormatVersion,
|
||||
Console.WriteLine("No fallback baseline zip available. Generating full update.");
|
||||
var fullBuilder = new PlondsDeltaBuilder();
|
||||
return fullBuilder.Build(new PlondsDeltaBuildOptions(
|
||||
Platform: options.Platform,
|
||||
CurrentVersion: options.CurrentVersion,
|
||||
PreviousVersion: "0.0.0",
|
||||
IsFullUpdate: true,
|
||||
RequiresCleanInstall: false,
|
||||
CurrentPayloadZip: options.CurrentPayloadZip,
|
||||
OutputRoot: outputRoot,
|
||||
Channel: options.Channel,
|
||||
Platform: options.Platform,
|
||||
UpdatedAt: DateTimeOffset.UtcNow,
|
||||
CompareMethod: PlondsConstants.CompareMethodCommitAnalyze,
|
||||
HashAlgorithm: hashAlgorithm,
|
||||
FilesMap: filesMap,
|
||||
ChangedFilesMap: changedFilesMap,
|
||||
Checksums: new Dictionary<string, string>
|
||||
{
|
||||
["changed.zip"] = $"md5:{changedZipMd5}"
|
||||
});
|
||||
|
||||
var manifestPath = Path.Combine(outputRoot, "PLONDS.json");
|
||||
var manifestJson = JsonSerializer.Serialize(manifest, JsonOptions);
|
||||
File.WriteAllText(manifestPath, manifestJson);
|
||||
|
||||
return new PlondsCommitDeltaBuildResult(
|
||||
Platform: options.Platform,
|
||||
ChangedZipPath: changedZipPath,
|
||||
ManifestPath: manifestPath,
|
||||
IsFullUpdate: true,
|
||||
RequiresCleanInstall: false,
|
||||
FellBackToFileCompare: true,
|
||||
CurrentVersion: options.CurrentVersion,
|
||||
BaselineVersion: options.BaselineVersion,
|
||||
ChangedSourceFiles: [],
|
||||
MappedArtifactFiles: []);
|
||||
LauncherRelativePath: options.LauncherRelativePath));
|
||||
}
|
||||
|
||||
Console.WriteLine($"Falling back to file-compare using baseline: {fallbackZip}");
|
||||
var deltaBuilder = new PlondsDeltaBuilder();
|
||||
var deltaResult = deltaBuilder.Build(new PlondsDeltaBuildOptions(
|
||||
return deltaBuilder.Build(new PlondsDeltaBuildOptions(
|
||||
Platform: options.Platform,
|
||||
CurrentVersion: options.CurrentVersion,
|
||||
CurrentPayloadZip: currentPayloadZip,
|
||||
CurrentPayloadZip: options.CurrentPayloadZip,
|
||||
OutputRoot: outputRoot,
|
||||
Channel: options.Channel,
|
||||
BaselineVersion: options.BaselineVersion,
|
||||
BaselineVersion: options.BaselineTag.TrimStart('v'),
|
||||
BaselinePayloadZip: fallbackZip,
|
||||
LauncherRelativePath: options.LauncherRelativePath,
|
||||
HashAlgorithm: hashAlgorithm));
|
||||
|
||||
return new PlondsCommitDeltaBuildResult(
|
||||
Platform: deltaResult.Platform,
|
||||
ChangedZipPath: deltaResult.ChangedZipPath,
|
||||
ManifestPath: deltaResult.ManifestPath,
|
||||
IsFullUpdate: deltaResult.IsFullUpdate,
|
||||
RequiresCleanInstall: deltaResult.RequiresCleanInstall,
|
||||
FellBackToFileCompare: true,
|
||||
CurrentVersion: deltaResult.CurrentVersion,
|
||||
BaselineVersion: deltaResult.BaselineVersion,
|
||||
ChangedSourceFiles: [],
|
||||
MappedArtifactFiles: []);
|
||||
HashAlgorithm: hashAlgorithm,
|
||||
LauncherRelativePath: options.LauncherRelativePath));
|
||||
}
|
||||
|
||||
private static string GetHash(PayloadUtilities.FileFingerprint fingerprint, string hashAlgorithm)
|
||||
{
|
||||
if (hashAlgorithm == PlondsConstants.HashAlgorithmMd5)
|
||||
{
|
||||
return ComputeMd5Hex(fingerprint.FullPath);
|
||||
}
|
||||
|
||||
return fingerprint.Sha256;
|
||||
}
|
||||
|
||||
private static string CreateChangedZipFromArtifacts(
|
||||
private static string CreateChangedZipFromList(
|
||||
string currentExtractRoot,
|
||||
IReadOnlySet<string> artifacts,
|
||||
IEnumerable<string> artifactFiles,
|
||||
string outputRoot,
|
||||
string platform)
|
||||
{
|
||||
@@ -211,15 +286,16 @@ public sealed class PlondsCommitDeltaBuilder
|
||||
var stagingRoot = Path.Combine(outputRoot, "work", platform, "staging");
|
||||
PayloadUtilities.EnsureCleanDirectory(stagingRoot);
|
||||
|
||||
foreach (var artifact in artifacts)
|
||||
foreach (var artifactFile in artifactFiles)
|
||||
{
|
||||
var sourcePath = Path.Combine(currentExtractRoot, artifact);
|
||||
var normalizedPath = artifactFile.Replace('\\', '/');
|
||||
var sourcePath = Path.Combine(currentExtractRoot, normalizedPath);
|
||||
if (!File.Exists(sourcePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var destPath = Path.Combine(stagingRoot, artifact);
|
||||
var destPath = Path.Combine(stagingRoot, normalizedPath);
|
||||
var destDir = Path.GetDirectoryName(destPath);
|
||||
if (!string.IsNullOrWhiteSpace(destDir))
|
||||
{
|
||||
@@ -233,6 +309,27 @@ public sealed class PlondsCommitDeltaBuilder
|
||||
return changedZipPath;
|
||||
}
|
||||
|
||||
private static string ValidateHashAlgorithm(string algorithm)
|
||||
{
|
||||
var normalized = algorithm.Trim().ToLowerInvariant();
|
||||
if (normalized is not (PlondsConstants.HashAlgorithmSha256 or PlondsConstants.HashAlgorithmMd5))
|
||||
{
|
||||
throw new ArgumentException($"Unsupported hash algorithm: {algorithm}. Supported: sha256, md5");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static string[] ParseSourceDirs(string? sourceDirs)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceDirs))
|
||||
{
|
||||
return PlondsConstants.DefaultSourceDirs;
|
||||
}
|
||||
|
||||
return sourceDirs.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
}
|
||||
|
||||
private static string ComputeMd5Hex(string filePath)
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class PlondsDeltaBuilder
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var hashAlgorithm = ValidateHashAlgorithmInternal(options.HashAlgorithm);
|
||||
var hashAlgorithm = ValidateHashAlgorithm(options.HashAlgorithm);
|
||||
|
||||
var currentPayloadZip = Path.GetFullPath(options.CurrentPayloadZip);
|
||||
if (!File.Exists(currentPayloadZip))
|
||||
@@ -71,8 +71,6 @@ public sealed class PlondsDeltaBuilder
|
||||
Channel: options.Channel,
|
||||
Platform: options.Platform,
|
||||
UpdatedAt: DateTimeOffset.UtcNow,
|
||||
CompareMethod: PlondsConstants.CompareMethodFileCompare,
|
||||
HashAlgorithm: hashAlgorithm,
|
||||
FilesMap: filesMap,
|
||||
ChangedFilesMap: changedFilesMap,
|
||||
Checksums: new Dictionary<string, string>
|
||||
@@ -94,7 +92,7 @@ public sealed class PlondsDeltaBuilder
|
||||
BaselineVersion: options.BaselineVersion);
|
||||
}
|
||||
|
||||
internal static string ValidateHashAlgorithmInternal(string algorithm)
|
||||
private static string ValidateHashAlgorithm(string algorithm)
|
||||
{
|
||||
var normalized = algorithm.Trim().ToLowerInvariant();
|
||||
if (normalized is not (PlondsConstants.HashAlgorithmSha256 or PlondsConstants.HashAlgorithmMd5))
|
||||
@@ -115,22 +113,22 @@ public sealed class PlondsDeltaBuilder
|
||||
foreach (var path in currentManifest.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var current = currentManifest[path];
|
||||
var currentHash = GetHash(current, hashAlgorithm);
|
||||
var currentHash = ComputeHash(current.FullPath, hashAlgorithm);
|
||||
|
||||
if (previousManifest.TryGetValue(path, out var previous))
|
||||
{
|
||||
var previousHash = GetHash(previous, hashAlgorithm);
|
||||
var previousHash = ComputeHash(previous.FullPath, hashAlgorithm);
|
||||
if (string.Equals(currentHash, previousHash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionReuse, currentHash, current.Size, hashAlgorithm);
|
||||
continue;
|
||||
}
|
||||
|
||||
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionReplace, currentHash, current.Size, hashAlgorithm);
|
||||
continue;
|
||||
}
|
||||
|
||||
var action = previousManifest.ContainsKey(path)
|
||||
? PlondsConstants.ActionReplace
|
||||
: PlondsConstants.ActionAdd;
|
||||
filesMap[path] = new PlondsFileEntry(action, currentHash, current.Size, hashAlgorithm);
|
||||
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionAdd, currentHash, current.Size, hashAlgorithm);
|
||||
}
|
||||
|
||||
foreach (var path in previousManifest.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
||||
@@ -144,16 +142,6 @@ public sealed class PlondsDeltaBuilder
|
||||
return filesMap;
|
||||
}
|
||||
|
||||
private static string GetHash(PayloadUtilities.FileFingerprint fingerprint, string hashAlgorithm)
|
||||
{
|
||||
if (hashAlgorithm == PlondsConstants.HashAlgorithmMd5)
|
||||
{
|
||||
return ComputeMd5Hex(fingerprint.FullPath);
|
||||
}
|
||||
|
||||
return fingerprint.Sha256;
|
||||
}
|
||||
|
||||
private static Dictionary<string, PlondsChangedFileEntry> BuildChangedFilesMap(
|
||||
IReadOnlyDictionary<string, PlondsFileEntry> filesMap,
|
||||
string hashAlgorithm)
|
||||
@@ -228,6 +216,15 @@ public sealed class PlondsDeltaBuilder
|
||||
return !string.Equals(current.Sha256, previous.Sha256, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
internal static string ComputeHash(string filePath, string hashAlgorithm)
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var hash = hashAlgorithm == PlondsConstants.HashAlgorithmMd5
|
||||
? MD5.HashData(stream)
|
||||
: SHA256.HashData(stream);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string ComputeMd5Hex(string filePath)
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
|
||||
Reference in New Issue
Block a user