feat: add support for multiple hash algs

This commit is contained in:
Oliver Booth 2024-04-17 16:11:01 +01:00
parent 01e06a46cf
commit 801dfe09cb
Signed by: oliverbooth
GPG Key ID: E60B570D1B7557B5
7 changed files with 95 additions and 4 deletions

View File

@ -0,0 +1,26 @@
using System.ComponentModel;
using Humanizer;
using Spectre.Console;
using Spectre.Console.Cli;
namespace FindDuplicates;
[Description("Display a list of usable hashing algorithms.")]
internal sealed class AlgListCommand : Command
{
public override int Execute(CommandContext context)
{
AnsiConsole.WriteLine("The default algorithm fdup uses is SHA512.");
AnsiConsole.MarkupLine("To specify a different one, use the [cyan]-a[/] or [cyan]--algorithm[/] flag, and pass one of the values below:");
var table = new Table();
table.AddColumn("Algorithm");
table.AddColumn("Value");
foreach (Algorithm algorithm in Enum.GetValues<Algorithm>())
table.AddRow($"{algorithm.Humanize()}", $"{algorithm.ToString().ToLower()}");
AnsiConsole.Write(table);
return 0;
}
}

View File

@ -0,0 +1,15 @@
using System.ComponentModel;
namespace FindDuplicates;
internal enum Algorithm
{
[Description("SHA512")] Sha512,
[Description("SHA384")] Sha384,
[Description("SHA256")] Sha256,
[Description("SHA3-512")] Sha3512,
[Description("SHA3-384")] Sha3384,
[Description("SHA3-256")] Sha3256,
[Description("SHA1")] Sha1,
[Description("MD5")] Md5
}

View File

@ -0,0 +1,42 @@
using System.Security.Cryptography;
namespace FindDuplicates;
internal static class AlgorithmExtensions
{
public static int GetByteCount(this Algorithm algorithm)
{
return algorithm switch
{
Algorithm.Sha512 => SHA512.HashSizeInBytes,
Algorithm.Sha384 => SHA384.HashSizeInBytes,
Algorithm.Sha256 => SHA256.HashSizeInBytes,
Algorithm.Sha3512 => SHA3_512.HashSizeInBytes,
Algorithm.Sha3384 => SHA3_384.HashSizeInBytes,
Algorithm.Sha3256 => SHA3_256.HashSizeInBytes,
Algorithm.Sha1 => SHA1.HashSizeInBytes,
Algorithm.Md5 => MD5.HashSizeInBytes,
_ => 0
};
}
public static int HashData(this Algorithm algorithm, Stream source, Span<byte> destination)
{
// I'd love to use a dictionary to cache the function map, but you can't use Span<> as a type argument,
// probably due to the fact that a lambda heap allocs, and we can't have that for ref structs, oh no (!)
// so enjoy this absolutely cursed switch expression which checks each algorithm separately. I hate it too.
return algorithm switch
{
Algorithm.Sha512 => SHA512.HashData(source, destination),
Algorithm.Sha384 => SHA384.HashData(source, destination),
Algorithm.Sha256 => SHA256.HashData(source, destination),
Algorithm.Sha3512 => SHA3_512.HashData(source, destination),
Algorithm.Sha3384 => SHA3_384.HashData(source, destination),
Algorithm.Sha3256 => SHA3_256.HashData(source, destination),
Algorithm.Sha1 => SHA1.HashData(source, destination),
Algorithm.Md5 => MD5.HashData(source, destination),
_ => -1
};
}
}

View File

@ -33,6 +33,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Spectre.Console.Cli" Version="0.48.0"/>
</ItemGroup>

View File

@ -1,6 +1,6 @@
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
using Humanizer;
using Spectre.Console;
using Spectre.Console.Cli;
@ -21,6 +21,7 @@ internal sealed class ListCommand : AsyncCommand<ListSettings>
AnsiConsole.MarkupLineInterpolated($"Searching [cyan]{inputDirectory.FullName}[/]");
AnsiConsole.MarkupLine($"Recursive mode is {(settings.Recursive ? "[green]ON" : "[red]OFF")}[/]");
AnsiConsole.MarkupLine($"Using hash algorithm [cyan]{settings.Algorithm.Humanize()}[/]");
await AnsiConsole.Status()
.StartAsync("Waiting to hash files...", DoHashWaitAsync)
@ -109,12 +110,12 @@ internal sealed class ListCommand : AsyncCommand<ListSettings>
private void ProcessFile(FileInfo file, ListSettings settings)
{
Span<byte> buffer = stackalloc byte[64];
Span<byte> buffer = stackalloc byte[settings.Algorithm.GetByteCount()];
try
{
using FileStream stream = file.OpenRead();
using BufferedStream bufferedStream = new BufferedStream(stream, 1048576 /* 1MB */);
SHA512.HashData(bufferedStream, buffer);
settings.Algorithm.HashData(bufferedStream, buffer);
string hash = ByteSpanToString(buffer);
if (settings.Verbose)
AnsiConsole.WriteLine($"{file.FullName} ->\n {hash}");
@ -146,7 +147,7 @@ internal sealed class ListCommand : AsyncCommand<ListSettings>
private static string ByteSpanToString(ReadOnlySpan<byte> buffer)
{
var builder = new StringBuilder();
var builder = new StringBuilder(buffer.Length * 2);
foreach (byte b in buffer)
builder.Append($"{b:X2}");

View File

@ -10,6 +10,11 @@ internal sealed class ListSettings : CommandSettings
[DefaultValue(".")]
public string InputPath { get; set; } = ".";
[CommandOption("-a|--algorithm <algorithm>")]
[Description("The hash algorithm used for comparison. Defaults to SHA512. For a list of all available algorithms, run fdup alglist")]
[DefaultValue(Algorithm.Sha512)]
public Algorithm Algorithm { get; set; } = Algorithm.Sha512;
[CommandOption("-r|--recursive")]
[Description("Scans the directory recursively. This may increase run time and is not advised to use when at high order directories such as C: or /")]
[DefaultValue(false)]

View File

@ -2,4 +2,5 @@ using FindDuplicates;
using Spectre.Console.Cli;
var app = new CommandApp<ListCommand>();
app.Configure(cfg => cfg.AddCommand<AlgListCommand>("alglist"));
await app.RunAsync(args).ConfigureAwait(false);