From a8d2d14ee015fc1bcf43931171a20247d29e4ffb Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 7 Feb 2025 19:23:37 -0600 Subject: [PATCH 1/7] Simplify comparisons --- src/tasks/Common/Utils.cs | 64 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 1e724bf3ce2d8d..526a0962c0d19e 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -214,14 +214,72 @@ public static (int, string) TryRunProcess( return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); } + private static bool CompareFiles(string filePath1, string filePath2) + { + const int bufferSize = 8192; + byte[] bufferA = new byte[bufferSize]; + byte[] bufferB = new byte[bufferSize]; + + using (FileStream fsA = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan)) + using (FileStream fsB = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan)) + { + int readA = 0; + int readB = 0; + int consumedA = 0; + int consumedB = 0; + + while (true) + { + if (consumedA == readA) + { + readA = fsA.Read(bufferA, 0, bufferSize); + consumedA = 0; + } + + if (consumedB == readB) + { + readB = fsB.Read(bufferB, 0, bufferSize); + consumedB = 0; + } + + if (readA == 0 && readB == 0) + return true; + + if (readA == 0 || readB == 0) + return false; + + int overlap = Math.Min(readA - consumedA, readB - consumedB); + if (!SequenceEqual(bufferA, consumedA, bufferB, consumedB, overlap)) + return false; + + consumedA += overlap; + consumedB += overlap; + } + } + } + + private static bool SequenceEqual(byte[] arrayA, int offsetA, byte[] arrayB, int offsetB, int count) + { +#if NET || NETCOREAPP + return arrayA.AsSpan(offsetA, count).SequenceEqual(arrayB.AsSpan(offsetB, count)); +#else + for (int i = 0; i < count; i++) + { + if (arrayA[offsetA + i] != arrayB[offsetB + i]) + return false; + } + return true; +#endif + } + +#pragma warning disable IDE0060 // Remove unused parameter public static bool CopyIfDifferent(string src, string dst, bool useHash) +#pragma warning restore IDE0060 // Remove unused parameter { if (!File.Exists(src)) throw new ArgumentException($"Cannot find {src} file to copy", nameof(src)); - bool areDifferent = !File.Exists(dst) || - (useHash && ComputeHash(src) != ComputeHash(dst)) || - (File.ReadAllText(src) != File.ReadAllText(dst)); + bool areDifferent = !File.Exists(dst) || !CompareFiles(src, dst); if (areDifferent) File.Copy(src, dst, true); From 8df167b1e112b0acd13130508b0e32d154939379 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 8 Feb 2025 14:49:20 -0600 Subject: [PATCH 2/7] Preserve the original useHash behavior --- src/tasks/Common/FileCache.cs | 3 +- src/tasks/Common/Utils.cs | 108 ++++++++++++------ .../WasmAppBuilder/WasmAppBuilderBaseTask.cs | 4 +- 3 files changed, 78 insertions(+), 37 deletions(-) diff --git a/src/tasks/Common/FileCache.cs b/src/tasks/Common/FileCache.cs index 05d92bdbc2c8ee..c1e83199375d12 100644 --- a/src/tasks/Common/FileCache.cs +++ b/src/tasks/Common/FileCache.cs @@ -33,7 +33,8 @@ public FileCache(string? cacheFilePath, TaskLoggingHelper log) Enabled = true; if (File.Exists(cacheFilePath)) { - _oldCache = JsonSerializer.Deserialize(File.ReadAllText(cacheFilePath), s_jsonOptions); + using FileStream fs = File.OpenRead(cacheFilePath); + _oldCache = JsonSerializer.Deserialize(fs, s_jsonOptions); } _oldCache ??= new(); diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 526a0962c0d19e..c98f68ddcf88ff 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -214,53 +214,95 @@ public static (int, string) TryRunProcess( return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); } - private static bool CompareFiles(string filePath1, string filePath2) + private static bool FilesTextEqual(string filePath1, string filePath2) + { + const int bufferSize = 4096; + char[] bufferA = new char[bufferSize]; + char[] bufferB = new char[bufferSize]; + + using StreamReader srA = new StreamReader(filePath1, Encoding.UTF8, true, bufferSize); + using StreamReader srB = new StreamReader(filePath2, Encoding.UTF8, true, bufferSize); + + int readA = 0; + int readB = 0; + int consumedA = 0; + int consumedB = 0; + + while (true) + { + if (consumedA == readA) + { + readA = srA.Read(bufferA, 0, bufferSize); + consumedA = 0; + } + + if (consumedB == readB) + { + readB = srB.Read(bufferB, 0, bufferSize); + consumedB = 0; + } + + if (readA == 0 && readB == 0) + return true; + + if (readA == 0 || readB == 0) + return false; + + int overlap = Math.Min(readA - consumedA, readB - consumedB); + if (!SequenceEqual(bufferA, consumedA, bufferB, consumedB, overlap)) + return false; + + consumedA += overlap; + consumedB += overlap; + } + } + + private static bool FilesEqual(string filePath1, string filePath2) { const int bufferSize = 8192; byte[] bufferA = new byte[bufferSize]; byte[] bufferB = new byte[bufferSize]; - using (FileStream fsA = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan)) - using (FileStream fsB = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan)) - { - int readA = 0; - int readB = 0; - int consumedA = 0; - int consumedB = 0; + using FileStream fsA = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan); + using FileStream fsB = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan); + + int readA = 0; + int readB = 0; + int consumedA = 0; + int consumedB = 0; - while (true) + while (true) + { + if (consumedA == readA) { - if (consumedA == readA) - { - readA = fsA.Read(bufferA, 0, bufferSize); - consumedA = 0; - } + readA = fsA.Read(bufferA, 0, bufferSize); + consumedA = 0; + } - if (consumedB == readB) - { - readB = fsB.Read(bufferB, 0, bufferSize); - consumedB = 0; - } + if (consumedB == readB) + { + readB = fsB.Read(bufferB, 0, bufferSize); + consumedB = 0; + } - if (readA == 0 && readB == 0) - return true; + if (readA == 0 && readB == 0) + return true; - if (readA == 0 || readB == 0) - return false; + if (readA == 0 || readB == 0) + return false; - int overlap = Math.Min(readA - consumedA, readB - consumedB); - if (!SequenceEqual(bufferA, consumedA, bufferB, consumedB, overlap)) - return false; + int overlap = Math.Min(readA - consumedA, readB - consumedB); + if (!SequenceEqual(bufferA, consumedA, bufferB, consumedB, overlap)) + return false; - consumedA += overlap; - consumedB += overlap; - } + consumedA += overlap; + consumedB += overlap; } } - private static bool SequenceEqual(byte[] arrayA, int offsetA, byte[] arrayB, int offsetB, int count) + private static bool SequenceEqual(T[] arrayA, int offsetA, T[] arrayB, int offsetB, int count) : where T : IEquatable { -#if NET || NETCOREAPP +#if NET return arrayA.AsSpan(offsetA, count).SequenceEqual(arrayB.AsSpan(offsetB, count)); #else for (int i = 0; i < count; i++) @@ -272,14 +314,12 @@ private static bool SequenceEqual(byte[] arrayA, int offsetA, byte[] arrayB, int #endif } -#pragma warning disable IDE0060 // Remove unused parameter public static bool CopyIfDifferent(string src, string dst, bool useHash) -#pragma warning restore IDE0060 // Remove unused parameter { if (!File.Exists(src)) throw new ArgumentException($"Cannot find {src} file to copy", nameof(src)); - bool areDifferent = !File.Exists(dst) || !CompareFiles(src, dst); + bool areDifferent = !File.Exists(dst) || (useHash && !FilesEqual(src, dst)) || !FilesTextEqual(src, dst); if (areDifferent) File.Copy(src, dst, true); diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs b/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs index 4b2271d7ae4a3f..4cbaeaf747d72b 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs @@ -105,8 +105,8 @@ protected virtual void UpdateRuntimeConfigJson() if (matchingAssemblies.Length > 1) throw new LogAsErrorException($"Found more than one assembly matching the main assembly name {MainAssemblyName}: {string.Join(",", matchingAssemblies)}"); - var rootNode = JsonNode.Parse(File.ReadAllText(RuntimeConfigJsonPath), - new JsonNodeOptions { PropertyNameCaseInsensitive = true }); + using FileStream rcs = File.OpenRead(RuntimeConfigJsonPath); + var rootNode = JsonNode.Parse(rcs, new JsonNodeOptions { PropertyNameCaseInsensitive = true }); if (rootNode == null) throw new LogAsErrorException($"Failed to parse {RuntimeConfigJsonPath}"); From 5a019ef3919b756b45ee9864867e2967e19b1fa4 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 8 Feb 2025 15:07:27 -0600 Subject: [PATCH 3/7] Fix typo --- src/tasks/Common/Utils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index c98f68ddcf88ff..9cff0ff38b3aea 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -300,14 +300,14 @@ private static bool FilesEqual(string filePath1, string filePath2) } } - private static bool SequenceEqual(T[] arrayA, int offsetA, T[] arrayB, int offsetB, int count) : where T : IEquatable + private static bool SequenceEqual(T[] arrayA, int offsetA, T[] arrayB, int offsetB, int count) where T : IEquatable { #if NET return arrayA.AsSpan(offsetA, count).SequenceEqual(arrayB.AsSpan(offsetB, count)); #else for (int i = 0; i < count; i++) { - if (arrayA[offsetA + i] != arrayB[offsetB + i]) + if (arrayA[offsetA + i].Equals (arrayB[offsetB + i])) return false; } return true; From 92b66ab6507737422cc31c729d94138aa082ea75 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 8 Feb 2025 20:25:29 -0600 Subject: [PATCH 4/7] add some useless abstraction --- src/tasks/Common/Utils.cs | 146 ++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 77 deletions(-) diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 9cff0ff38b3aea..1f6ad94f415156 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -214,104 +214,96 @@ public static (int, string) TryRunProcess( return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); } - private static bool FilesTextEqual(string filePath1, string filePath2) + internal abstract class Readable : + IDisposable + where TContent : IEquatable + where TReader : IDisposable { - const int bufferSize = 4096; - char[] bufferA = new char[bufferSize]; - char[] bufferB = new char[bufferSize]; + protected abstract int Read(TContent[] buffer, int offset, int count); + protected TReader _reader; + protected Readable(TReader reader) => _reader = reader; + public void Dispose() => _reader.Dispose(); - using StreamReader srA = new StreamReader(filePath1, Encoding.UTF8, true, bufferSize); - using StreamReader srB = new StreamReader(filePath2, Encoding.UTF8, true, bufferSize); - - int readA = 0; - int readB = 0; - int consumedA = 0; - int consumedB = 0; - - while (true) + protected static bool ContentEqual(Readable streamA, Readable streamB, int bufferSize) { - if (consumedA == readA) - { - readA = srA.Read(bufferA, 0, bufferSize); - consumedA = 0; - } - - if (consumedB == readB) - { - readB = srB.Read(bufferB, 0, bufferSize); - consumedB = 0; - } - - if (readA == 0 && readB == 0) - return true; + TContent[] bufferA = new TContent[bufferSize]; + TContent[] bufferB = new TContent[bufferSize]; - if (readA == 0 || readB == 0) - return false; + int readA = 0; + int readB = 0; + int consumedA = 0; + int consumedB = 0; - int overlap = Math.Min(readA - consumedA, readB - consumedB); - if (!SequenceEqual(bufferA, consumedA, bufferB, consumedB, overlap)) - return false; + while (true) + { + if (consumedA == readA) + { + readA = streamA.Read(bufferA, 0, bufferSize); + consumedA = 0; + } - consumedA += overlap; - consumedB += overlap; - } - } + if (consumedB == readB) + { + readB = streamB.Read(bufferB, 0, bufferSize); + consumedB = 0; + } - private static bool FilesEqual(string filePath1, string filePath2) - { - const int bufferSize = 8192; - byte[] bufferA = new byte[bufferSize]; - byte[] bufferB = new byte[bufferSize]; + if (readA == 0 && readB == 0) + return true; - using FileStream fsA = new FileStream(filePath1, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan); - using FileStream fsB = new FileStream(filePath2, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan); + if (readA == 0 || readB == 0) + return false; - int readA = 0; - int readB = 0; - int consumedA = 0; - int consumedB = 0; + int overlap = Math.Min(readA - consumedA, readB - consumedB); + if (!SequenceEqual(bufferA, consumedA, bufferB, consumedB, overlap)) + return false; - while (true) - { - if (consumedA == readA) - { - readA = fsA.Read(bufferA, 0, bufferSize); - consumedA = 0; + consumedA += overlap; + consumedB += overlap; } - if (consumedB == readB) + static bool SequenceEqual(TContent[] bufferA, int offsetA, TContent[] bufferB, int offsetB, int count) { - readB = fsB.Read(bufferB, 0, bufferSize); - consumedB = 0; - } +#if NET + return bufferA.AsSpan(offsetA, count).SequenceEqual(bufferB.AsSpan(offsetB, count)); +#else + for (int i = 0; i < count; i++) + { + if (!bufferA[offsetA + i].Equals(bufferB[offsetB + i])) + return false; + } - if (readA == 0 && readB == 0) return true; +#endif + } + } + } - if (readA == 0 || readB == 0) - return false; - - int overlap = Math.Min(readA - consumedA, readB - consumedB); - if (!SequenceEqual(bufferA, consumedA, bufferB, consumedB, overlap)) - return false; - - consumedA += overlap; - consumedB += overlap; + private sealed class TextFileComparer : Readable + { + private const int _charCount = 4096; + private TextFileComparer(string path) : base(new StreamReader(path, Encoding.UTF8, true, _charCount)) { } + protected override int Read(char[] buffer, int offset, int count) => _reader.Read(buffer, offset, count); + public static bool ContentEqual(string filePath1, string filePath2) + { + using var streamA = new TextFileComparer(filePath1); + using var streamB = new TextFileComparer(filePath2); + return ContentEqual(streamA, streamB, bufferSize: _charCount); } } - private static bool SequenceEqual(T[] arrayA, int offsetA, T[] arrayB, int offsetB, int count) where T : IEquatable + private sealed class BinaryFileComparer : Readable { -#if NET - return arrayA.AsSpan(offsetA, count).SequenceEqual(arrayB.AsSpan(offsetB, count)); -#else - for (int i = 0; i < count; i++) + private const int _byteCount = 8192; + private BinaryFileComparer(string path) : base (new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, + bufferSize: _byteCount, FileOptions.SequentialScan)) {} + protected override int Read(byte[] buffer, int offset, int count) => _reader.Read(buffer, offset, count); + public static bool ContentEqual(string filePath1, string filePath2) { - if (arrayA[offsetA + i].Equals (arrayB[offsetB + i])) - return false; + using var streamA = new BinaryFileComparer(filePath1); + using var streamB = new BinaryFileComparer(filePath2); + return ContentEqual(streamA, streamB, bufferSize: _byteCount); } - return true; -#endif } public static bool CopyIfDifferent(string src, string dst, bool useHash) @@ -319,7 +311,7 @@ public static bool CopyIfDifferent(string src, string dst, bool useHash) if (!File.Exists(src)) throw new ArgumentException($"Cannot find {src} file to copy", nameof(src)); - bool areDifferent = !File.Exists(dst) || (useHash && !FilesEqual(src, dst)) || !FilesTextEqual(src, dst); + bool areDifferent = !File.Exists(dst) || (useHash && !BinaryFileComparer.ContentEqual(src, dst)) || (!useHash && !TextFileComparer.ContentEqual(src, dst)); if (areDifferent) File.Copy(src, dst, true); From d67d10b21ccdd8b5ac12a76f5ee9d794de29b6d7 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Mon, 10 Feb 2025 17:19:47 -0600 Subject: [PATCH 5/7] sequence equal will be handled by the compiler --- src/tasks/Common/Utils.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 1f6ad94f415156..6fe549619bac0a 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -255,33 +255,18 @@ protected static bool ContentEqual(Readable streamA, Readable return false; int overlap = Math.Min(readA - consumedA, readB - consumedB); - if (!SequenceEqual(bufferA, consumedA, bufferB, consumedB, overlap)) + if (!bufferA.AsSpan(consumedA, overlap).SequenceEqual(bufferB.AsSpan(consumedB, overlap))) return false; consumedA += overlap; consumedB += overlap; } - - static bool SequenceEqual(TContent[] bufferA, int offsetA, TContent[] bufferB, int offsetB, int count) - { -#if NET - return bufferA.AsSpan(offsetA, count).SequenceEqual(bufferB.AsSpan(offsetB, count)); -#else - for (int i = 0; i < count; i++) - { - if (!bufferA[offsetA + i].Equals(bufferB[offsetB + i])) - return false; - } - - return true; -#endif - } } } private sealed class TextFileComparer : Readable { - private const int _charCount = 4096; + private const int _charCount = 8192 / sizeof(char); private TextFileComparer(string path) : base(new StreamReader(path, Encoding.UTF8, true, _charCount)) { } protected override int Read(char[] buffer, int offset, int count) => _reader.Read(buffer, offset, count); public static bool ContentEqual(string filePath1, string filePath2) From 218f0247cf32b0685a585bdda9df4085fb893cdb Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 12 Feb 2025 15:18:28 -0600 Subject: [PATCH 6/7] Go back to simple bytewise comparison --- src/tasks/Common/Utils.cs | 143 +++++++++++++------------------------- 1 file changed, 48 insertions(+), 95 deletions(-) diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 6fe549619bac0a..4c8127eaf496d4 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -214,90 +214,60 @@ public static (int, string) TryRunProcess( return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); } - internal abstract class Readable : - IDisposable - where TContent : IEquatable - where TReader : IDisposable + private static bool ContentEqual(string fileA, string fileB) { - protected abstract int Read(TContent[] buffer, int offset, int count); - protected TReader _reader; - protected Readable(TReader reader) => _reader = reader; - public void Dispose() => _reader.Dispose(); + const int bufferSize = 8192; + FileStream streamA = new(fileA, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: bufferSize, FileOptions.SequentialScan); + FileStream streamB = new(fileB, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: bufferSize, FileOptions.SequentialScan); - protected static bool ContentEqual(Readable streamA, Readable streamB, int bufferSize) - { - TContent[] bufferA = new TContent[bufferSize]; - TContent[] bufferB = new TContent[bufferSize]; + if (streamA.Length != streamB.Length) + return false; - int readA = 0; - int readB = 0; - int consumedA = 0; - int consumedB = 0; + byte[] bufferA = new byte[bufferSize]; + byte[] bufferB = new byte[bufferSize]; - while (true) - { - if (consumedA == readA) - { - readA = streamA.Read(bufferA, 0, bufferSize); - consumedA = 0; - } + int readA = 0; + int readB = 0; + int consumedA = 0; + int consumedB = 0; - if (consumedB == readB) - { - readB = streamB.Read(bufferB, 0, bufferSize); - consumedB = 0; - } + while (true) + { + if (consumedA == readA) + { + readA = streamA.Read(bufferA, 0, bufferSize); + consumedA = 0; + } - if (readA == 0 && readB == 0) - return true; + if (consumedB == readB) + { + readB = streamB.Read(bufferB, 0, bufferSize); + consumedB = 0; + } - if (readA == 0 || readB == 0) - return false; + if (readA == 0 && readB == 0) + return true; - int overlap = Math.Min(readA - consumedA, readB - consumedB); - if (!bufferA.AsSpan(consumedA, overlap).SequenceEqual(bufferB.AsSpan(consumedB, overlap))) - return false; + if (readA == 0 || readB == 0) + return false; - consumedA += overlap; - consumedB += overlap; - } - } - } + int overlap = Math.Min(readA - consumedA, readB - consumedB); + if (!bufferA.AsSpan(consumedA, overlap).SequenceEqual(bufferB.AsSpan(consumedB, overlap))) + return false; - private sealed class TextFileComparer : Readable - { - private const int _charCount = 8192 / sizeof(char); - private TextFileComparer(string path) : base(new StreamReader(path, Encoding.UTF8, true, _charCount)) { } - protected override int Read(char[] buffer, int offset, int count) => _reader.Read(buffer, offset, count); - public static bool ContentEqual(string filePath1, string filePath2) - { - using var streamA = new TextFileComparer(filePath1); - using var streamB = new TextFileComparer(filePath2); - return ContentEqual(streamA, streamB, bufferSize: _charCount); - } - } - - private sealed class BinaryFileComparer : Readable - { - private const int _byteCount = 8192; - private BinaryFileComparer(string path) : base (new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, - bufferSize: _byteCount, FileOptions.SequentialScan)) {} - protected override int Read(byte[] buffer, int offset, int count) => _reader.Read(buffer, offset, count); - public static bool ContentEqual(string filePath1, string filePath2) - { - using var streamA = new BinaryFileComparer(filePath1); - using var streamB = new BinaryFileComparer(filePath2); - return ContentEqual(streamA, streamB, bufferSize: _byteCount); + consumedA += overlap; + consumedB += overlap; } } +#pragma warning disable IDE0060 // Remove unused parameter public static bool CopyIfDifferent(string src, string dst, bool useHash) +#pragma warning restore IDE0060 // Remove unused parameter { if (!File.Exists(src)) throw new ArgumentException($"Cannot find {src} file to copy", nameof(src)); - bool areDifferent = !File.Exists(dst) || (useHash && !BinaryFileComparer.ContentEqual(src, dst)) || (!useHash && !TextFileComparer.ContentEqual(src, dst)); - + bool areDifferent = !File.Exists(dst) || !ContentEqual(src, dst); if (areDifferent) File.Copy(src, dst, true); @@ -327,41 +297,24 @@ private static string ToBase64SafeString(byte[] data) private static byte[] ComputeHashFromStream(Stream stream, HashAlgorithmType algorithm) { - if (algorithm == HashAlgorithmType.SHA512) - { - using HashAlgorithm hashAlgorithm = SHA512.Create(); - return hashAlgorithm.ComputeHash(stream); - } - else if (algorithm == HashAlgorithmType.SHA384) - { - using HashAlgorithm hashAlgorithm = SHA384.Create(); - return hashAlgorithm.ComputeHash(stream); - } - else if (algorithm == HashAlgorithmType.SHA256) - { - using HashAlgorithm hashAlgorithm = SHA256.Create(); - return hashAlgorithm.ComputeHash(stream); - } - else + using HashAlgorithm hash = algorithm switch { - throw new ArgumentException($"Unsupported hash algorithm: {algorithm}"); - } + HashAlgorithmType.SHA512 => SHA512.Create(), + HashAlgorithmType.SHA384 => SHA384.Create(), + HashAlgorithmType.SHA256 => SHA256.Create(), + _ => throw new ArgumentException($"Unsupported hash algorithm: {algorithm}") + }; + return hash.ComputeHash(stream); } private static string EncodeHash(byte[] data, HashEncodingType encoding) { - if (encoding == HashEncodingType.Base64) - { - return Convert.ToBase64String(data); - } - else if (encoding == HashEncodingType.Base64Safe) + return encoding switch { - return ToBase64SafeString(data); - } - else - { - throw new ArgumentException($"Unsupported hash encoding: {encoding}"); - } + HashEncodingType.Base64 => Convert.ToBase64String(data), + HashEncodingType.Base64Safe => ToBase64SafeString(data), + _ => throw new ArgumentException($"Unsupported hash encoding: {encoding}") + }; } public static string ComputeHash(string filepath) From 929081a26003ba4166e682114f9c01b2579e797b Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 12 Feb 2025 18:05:44 -0600 Subject: [PATCH 7/7] Dispose the streams and add a comment --- src/tasks/Common/Utils.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 4c8127eaf496d4..fe5969f86a9cd6 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -214,11 +214,11 @@ public static (int, string) TryRunProcess( return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); } - private static bool ContentEqual(string fileA, string fileB) + private static bool ContentEqual(string filePathA, string filePathB) { const int bufferSize = 8192; - FileStream streamA = new(fileA, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: bufferSize, FileOptions.SequentialScan); - FileStream streamB = new(fileB, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: bufferSize, FileOptions.SequentialScan); + using FileStream streamA = new(filePathA, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: bufferSize, FileOptions.SequentialScan); + using FileStream streamB = new(filePathB, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: bufferSize, FileOptions.SequentialScan); if (streamA.Length != streamB.Length) return false; @@ -231,6 +231,12 @@ private static bool ContentEqual(string fileA, string fileB) int consumedA = 0; int consumedB = 0; + /* + Read both streams in parallel into rolling buffers, comparing overlapping bytes. + Advance the consumed amount by the overlap. Refill a buffer when the previous read is exhausted. + This keeps the comparison position in sync, even if the read amounts differ. + */ + while (true) { if (consumedA == readA)