1
0
mirror of https://github.com/oliverbooth/fdup.git synced 2024-11-10 04:05:40 +00:00

Compare commits

..

6 Commits

Author SHA1 Message Date
c65d6ed414
chore: bump to 1.2.0 2024-04-17 14:48:43 +01:00
f07fd7f641
ci: add automatic tagged release 2024-04-17 14:48:17 +01:00
8273a34fef
feat: default to . to reduce need for it as cli arg 2024-04-17 14:35:30 +01:00
7da6faff83
fix: use ConcurrentBag for hash cache 2024-04-17 14:35:04 +01:00
3396c2bc74
refactor: move stack push to method
reduces method complexity and now Rider isn't crying
2024-04-17 14:24:54 +01:00
435318cd20
feat: enable verbose output to list hash of every file 2024-04-17 14:24:18 +01:00
5 changed files with 166 additions and 27 deletions

66
.github/workflows/prerelease.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Tagged Pre-Release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+-*"
jobs:
release:
name: "Tagged Pre-Release"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Get version from tag
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
- name: Build and Publish
run: |
dotnet publish -c Release -p PublishSingleFile=true -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }} -o ./publish/win-x64 -r win-x64
dotnet publish -c Release -p PublishSingleFile=true -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }} -o ./publish/win-x86 -r win-x86
dotnet publish -c Release -p PublishSingleFile=true -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }} -o ./publish/linux-x64 -r linux-x64
dotnet publish -c Release -p PublishSingleFile=true -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }} -o ./publish/osx-x64 -r osx-x64
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: publish
path: publish/
- name: Create Release
id: create_release
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
- name: Upload Release Assets
id: upload-release-assets
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: |
./publish/win-x64/fdup.exe
./publish/win-x86/fdup.exe
./publish/linux-x64/fdup
./publish/osx-x64/fdup
asset_name: |
fdup-${{ steps.get_version.outputs.VERSION }}-win-x64.exe
fdup-${{ steps.get_version.outputs.VERSION }}-win-x86.exe
fdup-${{ steps.get_version.outputs.VERSION }}-linux_x64
fdup-${{ steps.get_version.outputs.VERSION }}-macos_x64
asset_content_type: application/octet-stream
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

66
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Tagged Release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
jobs:
release:
name: "Tagged Release"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Get version from tag
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
- name: Build and Publish
run: |
dotnet publish -c Release -p PublishSingleFile=true -o ./publish/win-x64 -r win-x64
dotnet publish -c Release -p PublishSingleFile=true -o ./publish/win-x86 -r win-x86
dotnet publish -c Release -p PublishSingleFile=true -o ./publish/linux-x64 -r linux-x64
dotnet publish -c Release -p PublishSingleFile=true -o ./publish/osx-x64 -r osx-x64
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: publish
path: publish/
- name: Create Release
id: create_release
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
- name: Upload Release Assets
id: upload-release-assets
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: |
./publish/win-x64/fdup.exe
./publish/win-x86/fdup.exe
./publish/linux-x64/fdup
./publish/osx-x64/fdup
asset_name: |
fdup-${{ steps.get_version.outputs.VERSION }}-win-x64.exe
fdup-${{ steps.get_version.outputs.VERSION }}-win-x86.exe
fdup-${{ steps.get_version.outputs.VERSION }}-linux_x64
fdup-${{ steps.get_version.outputs.VERSION }}-macos_x64
asset_content_type: application/octet-stream
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -6,7 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AssemblyName>fdup</AssemblyName> <AssemblyName>fdup</AssemblyName>
<VersionPrefix>1.1.0</VersionPrefix> <VersionPrefix>1.2.0</VersionPrefix>
<Authors>Oliver Booth</Authors> <Authors>Oliver Booth</Authors>
</PropertyGroup> </PropertyGroup>

View File

@ -1,6 +1,4 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics;
using System.Security;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using Spectre.Console; using Spectre.Console;
@ -10,7 +8,7 @@ namespace FindDuplicates;
internal sealed class ListCommand : AsyncCommand<ListSettings> internal sealed class ListCommand : AsyncCommand<ListSettings>
{ {
private readonly ConcurrentDictionary<string, List<FileInfo>> _fileHashMap = new(); private readonly ConcurrentDictionary<string, ConcurrentBag<FileInfo>> _fileHashMap = new();
public override async Task<int> ExecuteAsync(CommandContext context, ListSettings settings) public override async Task<int> ExecuteAsync(CommandContext context, ListSettings settings)
{ {
@ -31,7 +29,7 @@ internal sealed class ListCommand : AsyncCommand<ListSettings>
AnsiConsole.WriteLine(); AnsiConsole.WriteLine();
int duplicates = 0; int duplicates = 0;
foreach ((string hash, List<FileInfo> files) in _fileHashMap) foreach ((string hash, ConcurrentBag<FileInfo> files) in _fileHashMap)
{ {
int fileCount = files.Count; int fileCount = files.Count;
@ -91,18 +89,7 @@ internal sealed class ListCommand : AsyncCommand<ListSettings>
if (relativePath != ".") if (relativePath != ".")
AnsiConsole.MarkupLineInterpolated($"Searching [cyan]{relativePath}[/]"); AnsiConsole.MarkupLineInterpolated($"Searching [cyan]{relativePath}[/]");
if (settings.Recursive) AddChildDirectories(settings, currentDirectory, directoryStack);
{
try
{
foreach (DirectoryInfo childDirectory in currentDirectory.EnumerateDirectories())
directoryStack.Push(childDirectory);
}
catch (Exception ex)
{
AnsiConsole.MarkupLineInterpolated($"[red]Error:[/] {ex.Message}");
}
}
try try
{ {
@ -110,7 +97,7 @@ internal sealed class ListCommand : AsyncCommand<ListSettings>
{ {
string relativeFilePath = Path.GetRelativePath(inputDirectory.FullName, file.FullName); string relativeFilePath = Path.GetRelativePath(inputDirectory.FullName, file.FullName);
AnsiConsole.MarkupLineInterpolated($"Checking hash for [cyan]{relativeFilePath}[/]"); AnsiConsole.MarkupLineInterpolated($"Checking hash for [cyan]{relativeFilePath}[/]");
tasks.Add(Task.Run(() => ProcessFile(file))); tasks.Add(Task.Run(() => ProcessFile(file, settings)));
} }
} }
catch (Exception ex) catch (Exception ex)
@ -120,7 +107,7 @@ internal sealed class ListCommand : AsyncCommand<ListSettings>
} }
} }
private void ProcessFile(FileInfo file) private void ProcessFile(FileInfo file, ListSettings settings)
{ {
Span<byte> buffer = stackalloc byte[64]; Span<byte> buffer = stackalloc byte[64];
try try
@ -129,13 +116,27 @@ internal sealed class ListCommand : AsyncCommand<ListSettings>
using BufferedStream bufferedStream = new BufferedStream(stream, 1048576 /* 1MB */); using BufferedStream bufferedStream = new BufferedStream(stream, 1048576 /* 1MB */);
SHA512.HashData(bufferedStream, buffer); SHA512.HashData(bufferedStream, buffer);
string hash = ByteSpanToString(buffer); string hash = ByteSpanToString(buffer);
Trace.WriteLine($"{file.FullName}: {hash}"); if (settings.Verbose)
AnsiConsole.WriteLine($"{file.FullName} ->\n {hash}");
if (!_fileHashMap.TryGetValue(hash, out List<FileInfo>? cache)) ConcurrentBag<FileInfo> cache = _fileHashMap.GetOrAdd(hash, _ => []);
_fileHashMap[hash] = cache = new List<FileInfo>(); cache.Add(file);
}
catch (Exception ex)
{
AnsiConsole.MarkupLineInterpolated($"[red]Error:[/] {ex.Message}");
}
}
lock (cache) private static void AddChildDirectories(ListSettings settings, DirectoryInfo directory, Stack<DirectoryInfo> stack)
cache.Add(file); {
if (!settings.Recursive)
return;
try
{
foreach (DirectoryInfo childDirectory in directory.EnumerateDirectories())
stack.Push(childDirectory);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -5,12 +5,18 @@ namespace FindDuplicates;
internal sealed class ListSettings : CommandSettings internal sealed class ListSettings : CommandSettings
{ {
[CommandArgument(0, "<path>")] [CommandArgument(0, "[path]")]
[Description("The path to search.")] [Description("The path to search. Defaults to the current directory.")]
public string InputPath { get; set; } = string.Empty; [DefaultValue(".")]
public string InputPath { get; set; } = ".";
[CommandOption("-r|--recursive")] [CommandOption("-r|--recursive")]
[Description("When this flag is set, the directory will be scanned recursively. This may take longer.")] [Description("When this flag is set, the directory will be scanned recursively. This may take longer.")]
[DefaultValue(false)] [DefaultValue(false)]
public bool Recursive { get; set; } = false; public bool Recursive { get; set; } = false;
[CommandOption("--verbose")]
[Description("Enable verbose output.")]
[DefaultValue(false)]
public bool Verbose { get; set; } = false;
} }