diff --git a/X10D.Tests/src/Core/FileInfoTests.cs b/X10D.Tests/src/Core/FileInfoTests.cs new file mode 100644 index 0000000..34362cb --- /dev/null +++ b/X10D.Tests/src/Core/FileInfoTests.cs @@ -0,0 +1,48 @@ +using System.Security.Cryptography; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace X10D.Tests.Core; + +[TestClass] +public class FileInfoTests +{ + [TestMethod] + public void GetHash() + { + string fileName = $"temp.{DateTimeOffset.Now.ToUnixTimeSeconds()}.bin"; + if (File.Exists(fileName)) + { + Assert.Fail("Temporary file already exists"); + } + + const string expectedHash = "0A4D55A8D778E5022FAB701977C5D840BBC486D0"; // SHA-1 + File.WriteAllText(fileName, "Hello World"); + Assert.IsTrue(File.Exists(fileName), $"File.Exists(\"{fileName}\")"); + + byte[] hash = new FileInfo(fileName).GetHash(); + string actualHash = BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant(); + Assert.AreEqual(expectedHash, actualHash); + + File.Delete(fileName); // cleanup is important + } + + [TestMethod] + public void GetHash_NullFileInfo() + { + // any HashAlgorithm will do, but SHA1 is used above. so to remain consistent, we use it here + Assert.ThrowsException(() => ((FileInfo?)null)!.GetHash()); + } + + [TestMethod] + public void GetHash_InvalidFile() + { + string fileName = $"temp.{DateTimeOffset.Now.ToUnixTimeSeconds()}.bin"; + if (File.Exists(fileName)) + { + Assert.Fail("Temporary file already exists"); + } + + // any HashAlgorithm will do, but SHA1 is used above. so to remain consistent, we use it here + Assert.ThrowsException(() => new FileInfo(fileName).GetHash()); + } +} diff --git a/X10D/src/FileInfoExtensions/FileInfoExtensions.cs b/X10D/src/FileInfoExtensions/FileInfoExtensions.cs new file mode 100644 index 0000000..fed6688 --- /dev/null +++ b/X10D/src/FileInfoExtensions/FileInfoExtensions.cs @@ -0,0 +1,43 @@ +using System.Security.Cryptography; + +namespace X10D; + +/// +/// Extension methods for . +/// +public static class FileInfoExtensions +{ + /// + /// Computes the hash of a file using the specified hash algorithm. + /// + /// The file whose hash to compute. + /// A derived type. + /// A array representing the hash of the file. + /// is . + /// The file pointed at by does not exist. + /// The opened file stream cannot be read. + /// + /// does not offer a static Create method. + /// -or- + /// + /// An invocation to the static Create method defined in returned + /// . + /// + /// + public static byte[] GetHash(this FileInfo value) + where T : HashAlgorithm + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (!value.Exists) + { + throw new FileNotFoundException("Cannot get hash of non-existent file.", value.FullName); + } + + using FileStream stream = value.OpenRead(); + return stream.GetHash(); + } +} diff --git a/X10D/src/StreamExtensions/StreamExtensions.cs b/X10D/src/StreamExtensions/StreamExtensions.cs index 8d59457..d992394 100644 --- a/X10D/src/StreamExtensions/StreamExtensions.cs +++ b/X10D/src/StreamExtensions/StreamExtensions.cs @@ -19,6 +19,7 @@ public static partial class StreamExtensions /// The stream whose hash is to be computed. /// A array representing the hash of the stream. /// is . + /// The stream does not support reading. /// /// does not offer a static Create method. /// -or- @@ -26,6 +27,10 @@ public static partial class StreamExtensions /// . /// /// The stream has already been disposed. + /// + /// The specified does not offer a parameterless Create method, or its Create + /// method returns a type that is not assignable to . + /// /// This method consumes the stream from its current position!. public static byte[] GetHash(this Stream stream) where T : HashAlgorithm @@ -41,7 +46,9 @@ public static partial class StreamExtensions } Type type = typeof(T); - MethodInfo? createMethod = type.GetMethod("Create", BindingFlags.Public | BindingFlags.Static); + + MethodInfo? createMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(c => c.Name == "Create" && c.GetParameters().Length == 0); if (createMethod is null) { throw new TypeInitializationException(type.FullName,