Multi-Platform Package Manager for Stable Diffusion
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
6.6 KiB

using System.Buffers;
using System.Diagnostics;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Blake3;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;
namespace StabilityMatrix.Core.Helper;
public static class FileHash
{
public static async Task<string> GetHashAsync(
HashAlgorithm hashAlgorithm,
Stream stream,
byte[] buffer,
Action<ulong>? progress = default
)
{
ulong totalBytesRead = 0;
using (hashAlgorithm)
{
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false)) != 0)
{
totalBytesRead += (ulong)bytesRead;
hashAlgorithm.TransformBlock(buffer, 0, bytesRead, null, 0);
progress?.Invoke(totalBytesRead);
}
hashAlgorithm.TransformFinalBlock(buffer, 0, 0);
var hash = hashAlgorithm.Hash;
if (hash == null || hash.Length == 0)
{
throw new InvalidOperationException("Hash algorithm did not produce a hash.");
}
return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
}
}
public static async Task<string> GetSha256Async(string filePath, IProgress<ProgressReport>? progress = default)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"Could not find file: {filePath}");
}
var totalBytes = Convert.ToUInt64(new FileInfo(filePath).Length);
var shared = ArrayPool<byte>.Shared;
var buffer = shared.Rent((int)FileTransfers.GetBufferSize(totalBytes));
try
{
await using var stream = File.OpenRead(filePath);
var hash = await GetHashAsync(
SHA256.Create(),
stream,
buffer,
totalBytesRead =>
{
progress?.Report(new ProgressReport(totalBytesRead, totalBytes, type: ProgressType.Hashing));
}
)
.ConfigureAwait(false);
return hash;
}
finally
{
shared.Return(buffer);
}
}
public static async Task<string> GetBlake3Async(string filePath, IProgress<ProgressReport>? progress = default)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"Could not find file: {filePath}");
}
var totalBytes = Convert.ToUInt64(new FileInfo(filePath).Length);
var readBytes = 0ul;
var shared = ArrayPool<byte>.Shared;
var buffer = shared.Rent(GetBufferSize(totalBytes));
try
{
await using var stream = File.OpenRead(filePath);
using var hasher = Hasher.New();
while (true)
{
var bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false);
if (bytesRead == 0)
{
break;
}
readBytes += (ulong)bytesRead;
hasher.Update(buffer.AsSpan(0, bytesRead));
progress?.Report(new ProgressReport(readBytes, totalBytes));
}
return hasher.Finalize().ToString();
}
finally
{
shared.Return(buffer);
}
}
public static async Task<Hash> GetBlake3Async(Stream stream, IProgress<ProgressReport>? progress = default)
{
var totalBytes = Convert.ToUInt64(stream.Length);
var readBytes = 0ul;
var shared = ArrayPool<byte>.Shared;
var buffer = shared.Rent((int)FileTransfers.GetBufferSize(totalBytes));
try
{
using var hasher = Hasher.New();
while (true)
{
var bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false);
if (bytesRead == 0)
{
break;
}
readBytes += (ulong)bytesRead;
hasher.Update(buffer.AsSpan(0, bytesRead));
progress?.Report(new ProgressReport(readBytes, totalBytes));
}
return hasher.Finalize();
}
finally
{
shared.Return(buffer);
}
}
/// <summary>
/// Get the Blake3 hash of a span of data with multi-threading.
/// </summary>
public static Hash GetBlake3Parallel(ReadOnlySpan<byte> data)
{
using var hasher = Hasher.New();
hasher.UpdateWithJoin(data);
return hasher.Finalize();
}
/// <summary>
/// Task.Run wrapped <see cref="GetBlake3Parallel"/>
/// </summary>
public static Task<Hash> GetBlake3ParallelAsync(ReadOnlyMemory<byte> data)
{
return Task.Run(() => GetBlake3Parallel(data.Span));
}
/// <summary>
/// Get the Blake3 hash of a file as memory-mapped with multi-threading.
/// </summary>
public static Hash GetBlake3MemoryMappedParallel(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException(filePath);
}
var totalBytes = Convert.ToInt64(new FileInfo(filePath).Length);
using var hasher = Hasher.New();
// Memory map
using var fileStream = File.OpenRead(filePath);
using var memoryMappedFile = MemoryMappedFile.CreateFromFile(
fileStream,
null,
totalBytes,
MemoryMappedFileAccess.Read,
HandleInheritability.None,
false
);
using var accessor = memoryMappedFile.CreateViewAccessor(0, totalBytes, MemoryMappedFileAccess.Read);
Debug.Assert(accessor.Capacity == fileStream.Length);
var buffer = new byte[accessor.Capacity];
accessor.ReadArray(0, buffer, 0, buffer.Length);
hasher.UpdateWithJoin(buffer);
return hasher.Finalize();
}
/// <summary>
/// Task.Run wrapped <see cref="GetBlake3MemoryMappedParallel"/>
/// </summary>
public static Task<Hash> GetBlake3MemoryMappedParallelAsync(string filePath)
{
return Task.Run(() => GetBlake3MemoryMappedParallel(filePath));
}
/// <summary>
/// Determines suitable buffer size for hashing based on stream length.
/// </summary>
private static int GetBufferSize(ulong totalBytes) =>
totalBytes switch
{
< Size.MiB => 8 * (int)Size.KiB,
< 500 * Size.MiB => 16 * (int)Size.KiB,
< Size.GiB => 32 * (int)Size.KiB,
_ => 64 * (int)Size.KiB
};
}