Editor/HumanoidMocap/Inference/FileChecksum.cs
using System;
using System.IO;
using System.Security.Cryptography;
namespace HumanoidMocap.Inference;
/// <summary>SHA-256 of a file, remembered beside it. Model checkpoints run to several gigabytes and every
/// capture verified them again, twice for some, which cost seconds per job. The digest is stored in
/// <c><file>.sha256</c> with the file's length and modification time and reused while both are
/// unchanged; a partial, replaced or rewritten file changes them and is hashed again.</summary>
public static class FileChecksum
{
/// <summary>Upper-case hexadecimal SHA-256 of the file.</summary>
public static string Sha256(string path)
{
var info=new FileInfo(path);if(!info.Exists)throw new FileNotFoundException("Missing file.",path);
var stamp=$"{info.Length}|{info.LastWriteTimeUtc.Ticks}|";var sidecar=path+".sha256";
try
{
if(File.Exists(sidecar)&&File.ReadAllText(sidecar) is var text&&text.StartsWith(stamp,StringComparison.Ordinal)&&text.Length==stamp.Length+64)
return text[stamp.Length..];
}
catch(IOException){}catch(UnauthorizedAccessException){}
string digest;using(var input=File.OpenRead(path))digest=Convert.ToHexString(SHA256.HashData(input));
// Only record the digest if the file did not change while it was read.
info.Refresh();
if($"{info.Length}|{info.LastWriteTimeUtc.Ticks}|"==stamp)
try{File.WriteAllText(sidecar,stamp+digest);}catch(IOException){}catch(UnauthorizedAccessException){}
return digest;
}
public static bool Matches(string path,string sha256)=>Sha256(path).Equals(sha256,StringComparison.OrdinalIgnoreCase);
}