Skip to content

Commit

Permalink
Go back to simple bytewise comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
lewing committed Feb 12, 2025
1 parent d67d10b commit 218f024
Showing 1 changed file with 48 additions and 95 deletions.
143 changes: 48 additions & 95 deletions src/tasks/Common/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,90 +214,60 @@ public static (int, string) TryRunProcess(
return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n'));
}

internal abstract class Readable<TContent, TReader> :
IDisposable
where TContent : IEquatable<TContent>
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<TContent, TReader> streamA, Readable<TContent, TReader> 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<char, StreamReader>
{
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<byte, FileStream>
{
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);

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 218f024

Please sign in to comment.