using System.IO; using System.IO.Hashing; using System.Text; namespace Azaion.Common.Services; public static class HashExtensions { private const int SampleSize = 1024; private const int HashLength = 9; private const string VirtualHashPrefix = "V"; public static string CalcHash(this FileInfo file) { if (!file.Exists) return CalcVirtualHash(file.Name); var fileSize = file.Length; if (fileSize < SampleSize * 8) return CalcHashInner(File.ReadAllBytes(file.FullName)); var combinedData = new byte[8 + SampleSize * 3]; var offset = 0; var fileSizeBytes = BitConverter.GetBytes(fileSize); Array.Copy(fileSizeBytes, 0, combinedData, offset, 8); offset += 8; using var stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read); stream.ReadExactly(combinedData, offset, SampleSize); offset += SampleSize; stream.Position = (fileSize - SampleSize) / 2; stream.ReadExactly(combinedData, offset, SampleSize); offset += SampleSize; stream.Position = fileSize - SampleSize; stream.ReadExactly(combinedData, offset, SampleSize); return CalcHashInner(combinedData); } public static string CalcVirtualHash(string filename) { var bytes = Encoding.UTF8.GetBytes($"VIRTUAL:{filename}"); var hash = XxHash64.HashToUInt64(bytes); var hashBytes = BitConverter.GetBytes(hash); var base64 = Convert.ToBase64String(hashBytes); return VirtualHashPrefix + base64[..HashLength]; } public static bool IsVirtualHash(this string? hash) => !string.IsNullOrEmpty(hash) && hash.StartsWith(VirtualHashPrefix); private static string CalcHashInner(byte[] data) { var hash = XxHash64.HashToUInt64(data); var hashBytes = BitConverter.GetBytes(hash); var base64 = Convert.ToBase64String(hashBytes); return base64[..HashLength]; } }