Compare commits
3 Commits
f82beb62ed
...
3834c2fba0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3834c2fba0 | |||
| f8d8c4461f | |||
| 30738eef24 |
@ -4,6 +4,8 @@ namespace RedundancyFinder
|
|||||||
{
|
{
|
||||||
public class Finder
|
public class Finder
|
||||||
{
|
{
|
||||||
|
public CancellationToken cancellation;
|
||||||
|
|
||||||
Dictionary<string, Redundancy> redundancies = new Dictionary<string, Redundancy>();
|
Dictionary<string, Redundancy> redundancies = new Dictionary<string, Redundancy>();
|
||||||
|
|
||||||
public Dictionary<string, Redundancy> Redundancies { get => redundancies; }
|
public Dictionary<string, Redundancy> Redundancies { get => redundancies; }
|
||||||
@ -19,10 +21,14 @@ namespace RedundancyFinder
|
|||||||
public event EventHandler<ProcessingFileEventArgs>? ProcessingFile;
|
public event EventHandler<ProcessingFileEventArgs>? ProcessingFile;
|
||||||
public void FindRedundancies(string[] paths, string[] extensions)
|
public void FindRedundancies(string[] paths, string[] extensions)
|
||||||
{
|
{
|
||||||
redundancies?.Values.SelectMany(x => x.Paths).ToList().ForEach(x => ignorePaths.Add(x));
|
Redundancies?.Values.SelectMany(x => x.Paths).ToList().ForEach(x => ignorePaths.Add(x));
|
||||||
this.extensions = extensions;
|
this.extensions = extensions;
|
||||||
foreach (var path in paths)
|
foreach (var path in paths)
|
||||||
{
|
{
|
||||||
|
if (cancellation.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (Directory.Exists(path))
|
if (Directory.Exists(path))
|
||||||
{
|
{
|
||||||
ProcessDirectory(path);
|
ProcessDirectory(path);
|
||||||
@ -36,17 +42,29 @@ namespace RedundancyFinder
|
|||||||
|
|
||||||
private void ProcessDirectory(string directoryPath)
|
private void ProcessDirectory(string directoryPath)
|
||||||
{
|
{
|
||||||
|
if (cancellation.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Process files in the current directory
|
// Process files in the current directory
|
||||||
foreach (var file in Directory.GetFiles(directoryPath))
|
foreach (var file in Directory.GetFiles(directoryPath))
|
||||||
{
|
{
|
||||||
|
if (cancellation.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
ProcessFile(file);
|
ProcessFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively process subdirectories
|
// Recursively process subdirectories
|
||||||
foreach (var subDirectory in Directory.GetDirectories(directoryPath))
|
foreach (var subDirectory in Directory.GetDirectories(directoryPath))
|
||||||
{
|
{
|
||||||
|
if (cancellation.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Check if the directory is hidden and skip it if true
|
// Check if the directory is hidden and skip it if true
|
||||||
var attributes = File.GetAttributes(subDirectory);
|
var attributes = File.GetAttributes(subDirectory);
|
||||||
if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
|
if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
|
||||||
@ -73,7 +91,11 @@ namespace RedundancyFinder
|
|||||||
|
|
||||||
private void ProcessFile(string filePath)
|
private void ProcessFile(string filePath)
|
||||||
{
|
{
|
||||||
if(ignorePaths.Contains(filePath))
|
if (cancellation.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ignorePaths.Contains(filePath))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -83,8 +105,8 @@ namespace RedundancyFinder
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskStarted?.Invoke(this, filePath);
|
//TaskStarted?.Invoke(this, filePath);
|
||||||
ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath });
|
//ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath });
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -95,14 +117,14 @@ namespace RedundancyFinder
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long fileSize = new FileInfo(filePath).Length;
|
long fileSize = new FileInfo(filePath).Length;
|
||||||
lock (redundancies)
|
lock (Redundancies)
|
||||||
{
|
{
|
||||||
if (!redundancies.ContainsKey(fileHash))
|
if (!Redundancies.ContainsKey(fileHash))
|
||||||
{
|
{
|
||||||
var redundancy = new Redundancy() { Hash = fileHash, FileSize = fileSize };
|
var redundancy = new Redundancy() { Hash = fileHash, FileSize = fileSize };
|
||||||
redundancies.Add(fileHash, redundancy);
|
Redundancies.Add(fileHash, redundancy);
|
||||||
}
|
}
|
||||||
redundancies[fileHash].Paths.Add(filePath);
|
Redundancies[fileHash].Paths.Add(filePath);
|
||||||
}
|
}
|
||||||
FileFound?.Invoke(this, new FileFoundEventArgs(filePath, fileHash, fileSize));
|
FileFound?.Invoke(this, new FileFoundEventArgs(filePath, fileHash, fileSize));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ namespace RedundancyFinderCLI
|
|||||||
|
|
||||||
[Description("File extensions to search for. Comma separated.")]
|
[Description("File extensions to search for. Comma separated.")]
|
||||||
[CommandOption("-e|--extensions")]
|
[CommandOption("-e|--extensions")]
|
||||||
[DefaultValue(".jpg,.webp,.raw,.pdf,.xsl,.xslx,.doc,.docx,.txt,.jpeg,.mov,.mp4,.mp3,.wav,.bmp,.gif,.png,.cu,.mid,.msb ,.mov,.avi,.wmv,.flv,.m4v,.bak ,.cpr ,.xml,.psd")]
|
[DefaultValue(".jpg,.webp,.raw,.pdf,.xsl,.xslx,.doc,.docx,.txt,.jpeg,.mov,.mp4,.mp3,.wav,.bmp,.gif,.png,.cu,.mid,.msb,.mov,.avi,.wmv,.flv,.m4v,.bak ,.cpr,.xml,.psd")]
|
||||||
public string? Extensions { get; init; }
|
public string? Extensions { get; init; }
|
||||||
|
|
||||||
[Description("Show all information.")]
|
[Description("Show all information.")]
|
||||||
@ -35,42 +35,62 @@ namespace RedundancyFinderCLI
|
|||||||
}
|
}
|
||||||
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
|
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
|
||||||
{
|
{
|
||||||
WriteLine($"[yellow]Analyzing {settings.Path}[/]");
|
|
||||||
|
|
||||||
var redundancies = JsonConvert.DeserializeObject<Dictionary<string, Redundancy>>(File.ReadAllText(settings.Path));
|
Dictionary<string, Redundancy> redundancies = null;
|
||||||
|
|
||||||
|
|
||||||
|
AnsiConsole.Status()
|
||||||
|
.Start($"[yellow]Analyzing [/]'{settings.Path}'", ctx =>
|
||||||
|
{
|
||||||
|
ctx.Spinner(Spinner.Known.Clock);
|
||||||
|
ctx.SpinnerStyle = Style.Parse("yellow bold");
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
redundancies = JsonConvert.DeserializeObject<Dictionary<string, Redundancy>>(File.ReadAllText(settings.Path));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (redundancies == null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var groups = redundancies
|
var groups = redundancies
|
||||||
.GroupBy(x => Path.GetExtension(x.Value.Paths[0]), x => x.Value)
|
.GroupBy(x => Path.GetExtension(x.Value.Paths[0]), x => x.Value)
|
||||||
.ToDictionary(x => x.Key, x => new
|
.ToDictionary(x => x.Key, x => new
|
||||||
{
|
{
|
||||||
FileSize = x.Sum(y => y.FileSize),
|
FileSize = x.Sum(y => y.FileSize),
|
||||||
RedundancySize = x.Sum(y => y.FileSize) * (x.Sum(y => y.Paths.Count) - 1),
|
RedundancySize = x.Sum(y => y.FileSize * (y.Paths.Count - 1)),
|
||||||
RedundancyCount = x.Sum(y => y.Paths.Count)-1,
|
RedundancyCount = x.Sum(y => y.Paths.Count) - 1,
|
||||||
|
RedundantFiles = x.Count(y => y.Paths.Count > 1)
|
||||||
});
|
});
|
||||||
// x => new { FileSize = x, RedundancySize = x.Sum()*(x.Count()-1)}
|
// x => new { FileSize = x, RedundancySize = x.Sum()*(x.Count()-1)}
|
||||||
var extensions = settings.Extensions.Split(",", StringSplitOptions.RemoveEmptyEntries);
|
var extensions = settings.Extensions.Split(",", StringSplitOptions.RemoveEmptyEntries);
|
||||||
var table = new Table();
|
var table = new Table();
|
||||||
|
|
||||||
table.AddColumn("Type");
|
table.AddColumn("Extension");
|
||||||
|
table.AddColumn(new TableColumn("Files").RightAligned());
|
||||||
table.AddColumn(new TableColumn("Size").RightAligned());
|
table.AddColumn(new TableColumn("Size").RightAligned());
|
||||||
table.AddColumn(new TableColumn("Redundancy size").RightAligned());
|
table.AddColumn(new TableColumn("Redundancies").RightAligned());
|
||||||
table.AddColumn(new TableColumn("Redundancy count").RightAligned());
|
table.AddColumn(new TableColumn("Redundancies size").RightAligned());
|
||||||
foreach (var extension in extensions)
|
|
||||||
|
foreach (var extension in extensions.OrderBy(x => x))
|
||||||
{
|
{
|
||||||
if (groups.ContainsKey(extension))
|
if (groups.ContainsKey(extension))
|
||||||
{
|
{
|
||||||
|
|
||||||
var size = groups[extension].FileSize;
|
var size = groups[extension].FileSize;
|
||||||
var sizeFormat = GetSizeFormat((ulong)size);
|
var sizeFormat = Global.GetSizeFormat((ulong)size);
|
||||||
|
|
||||||
var redundancySize = groups[extension].RedundancySize;
|
var redundancySize = groups[extension].RedundancySize;
|
||||||
var redundancySizeFormat = GetSizeFormat((ulong)redundancySize);
|
var redundancySizeFormat = Global.GetSizeFormat((ulong)redundancySize);
|
||||||
table.AddRow(
|
table.AddRow(
|
||||||
new Text(extension),
|
new Text(extension),
|
||||||
|
new Markup($"[darkgreen]{groups[extension].RedundantFiles}[/]"),
|
||||||
new Markup($"[green]{sizeFormat}[/]"),
|
new Markup($"[green]{sizeFormat}[/]"),
|
||||||
new Markup($"[yellow]{redundancySizeFormat}[/]"),
|
new Markup($"[cyan]{groups[extension].RedundancyCount}[/]"),
|
||||||
new Markup($"{groups[extension].RedundancyCount}"));
|
new Markup($"[yellow]{redundancySizeFormat}[/]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnsiConsole.Write(table);
|
AnsiConsole.Write(table);
|
||||||
@ -78,31 +98,5 @@ namespace RedundancyFinderCLI
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void WriteLine(string v)
|
|
||||||
{
|
|
||||||
string now = Markup.Escape($"[{DateTime.Now.ToString("HH:mm:ss")}]");
|
|
||||||
AnsiConsole.MarkupLine($"[gray]{now}[/] {v}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetSizeFormat(ulong totalSize)
|
|
||||||
{
|
|
||||||
string sizeUnit = "B";
|
|
||||||
double size = totalSize;
|
|
||||||
while (size > 1024)
|
|
||||||
{
|
|
||||||
size /= 1024d;
|
|
||||||
sizeUnit = sizeUnit switch
|
|
||||||
{
|
|
||||||
"B" => "KB",
|
|
||||||
"KB" => "MB",
|
|
||||||
"MB" => "GB",
|
|
||||||
"GB" => "TB",
|
|
||||||
_ => sizeUnit
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
string sizeFormat = $"{size:.00} {sizeUnit}";
|
|
||||||
return sizeFormat;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
RedundancyFinderCLI/DeleteCommand.cs
Normal file
115
RedundancyFinderCLI/DeleteCommand.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using RedundancyFinder;
|
||||||
|
using Spectre.Console;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
namespace RedundancyFinderCLI
|
||||||
|
{
|
||||||
|
internal sealed class DeleteCommand : Command<DeleteCommand.Settings>
|
||||||
|
{
|
||||||
|
public sealed class Settings : CommandSettings
|
||||||
|
{
|
||||||
|
[Description("Paths to Keep.")]
|
||||||
|
[DefaultValue(new string[] { "H:\\" })]
|
||||||
|
[CommandArgument(1, "[path]")]
|
||||||
|
public string[]? FoldersToKeep { get; init; }
|
||||||
|
|
||||||
|
[Description("Path to analyze.")]
|
||||||
|
[DefaultValue("redundancies.json")]
|
||||||
|
[CommandArgument(0, "[path]")]
|
||||||
|
public string? Path { get; init; }
|
||||||
|
|
||||||
|
[Description("File extensions to search for. Comma separated.")]
|
||||||
|
[CommandOption("-e|--extensions")]
|
||||||
|
[DefaultValue(".jpg,.webp,.raw,.pdf,.xsl,.xslx,.doc,.docx,.txt,.jpeg,.mov,.mp4,.mp3,.wav,.bmp,.gif,.png,.cu,.mid,.msb,.mov,.avi,.wmv,.flv,.m4v,.bak,.cpr,.xml,.psd")]
|
||||||
|
public string? Extensions { get; init; }
|
||||||
|
|
||||||
|
[Description("Show all information.")]
|
||||||
|
[CommandOption("-v|--verbose")]
|
||||||
|
[DefaultValue(false)]
|
||||||
|
public bool Verbose { get; init; }
|
||||||
|
|
||||||
|
}
|
||||||
|
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
|
||||||
|
{
|
||||||
|
Global.WriteLine($"[yellow]Analyzing {settings.Path}[/]");
|
||||||
|
|
||||||
|
var redundancies = JsonConvert.DeserializeObject<Dictionary<string, Redundancy>>(File.ReadAllText(settings.Path));
|
||||||
|
|
||||||
|
var pathsToDelete = new List<string>();
|
||||||
|
|
||||||
|
foreach (var redundancy in redundancies.Values)
|
||||||
|
{
|
||||||
|
var pathToKeep = redundancy.Paths.FirstOrDefault(x => settings.FoldersToKeep.Any(y => x.StartsWith(y)));
|
||||||
|
if (pathToKeep != default)
|
||||||
|
{
|
||||||
|
|
||||||
|
foreach (var path in redundancy.Paths)
|
||||||
|
{
|
||||||
|
if (path != pathToKeep)
|
||||||
|
{
|
||||||
|
pathsToDelete.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(settings.Verbose)
|
||||||
|
{
|
||||||
|
if (redundancy.Paths.Count > 0)
|
||||||
|
{
|
||||||
|
Global.WriteLine($"[blue]Skipping [/]'{redundancy.Paths.FirstOrDefault()}'[blue]. No paths to keep![/]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Global.WriteLine($"[yellow]Skipping [/]'{redundancy.Hash}'[/].[blue] No paths![/]");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var path in pathsToDelete)
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteLine(path);
|
||||||
|
}
|
||||||
|
if (pathsToDelete.Count == 0)
|
||||||
|
{
|
||||||
|
Global.WriteLine("[yellow]Nothing to delete![/]");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
var confirmation = AnsiConsole.Prompt(
|
||||||
|
new TextPrompt<bool>("Delete all of the above?")
|
||||||
|
.AddChoice(true)
|
||||||
|
.AddChoice(false)
|
||||||
|
.DefaultValue(true)
|
||||||
|
.WithConverter(choice => choice ? "y" : "n"));
|
||||||
|
|
||||||
|
if (confirmation)
|
||||||
|
{
|
||||||
|
foreach (var path in pathsToDelete)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Global.WriteLine($"[red]Error deleting file: [/]'{path}'\nMessage:\n{e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Global.WriteLine($"[yellow]Deleted file: [/]'{path}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,29 +34,39 @@ namespace RedundancyFinderCLI
|
|||||||
[Description("Output path.")]
|
[Description("Output path.")]
|
||||||
[CommandOption("-o|--output")]
|
[CommandOption("-o|--output")]
|
||||||
[DefaultValue("redundancies.json")]
|
[DefaultValue("redundancies.json")]
|
||||||
public string? OutputPath {get; init;}
|
public string? OutputPath { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CancellationTokenSource cancellation = new CancellationTokenSource();
|
||||||
|
Task task = null;
|
||||||
|
Finder finder = new Finder();
|
||||||
|
Settings settings = null;
|
||||||
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
|
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
|
||||||
{
|
{
|
||||||
|
finder.cancellation = cancellation.Token;
|
||||||
|
this.settings = settings;
|
||||||
var extensions = settings.Extensions.Split(",", StringSplitOptions.RemoveEmptyEntries);
|
var extensions = settings.Extensions.Split(",", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
Finder finder = new Finder();
|
|
||||||
int hashingTasks = 0;
|
int hashingTasks = 0;
|
||||||
int hashingTasksFinished = 0;
|
int hashingTasksFinished = 0;
|
||||||
|
|
||||||
// Register the ProcessExit event to save redundancies on exit
|
// Register the ProcessExit event to save redundancies on exit
|
||||||
AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
|
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
|
||||||
{
|
{
|
||||||
SaveRedundancies(finder, settings.OutputPath);
|
End();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register the CancelKeyPress event to handle Ctrl+C
|
// Register the CancelKeyPress event to handle Ctrl+C
|
||||||
Console.CancelKeyPress += (sender, e) =>
|
Console.CancelKeyPress += (sender, e) =>
|
||||||
{
|
{
|
||||||
|
cancellation.Cancel(); // Cancel the ongoing tasks
|
||||||
e.Cancel = true; // Prevent the process from terminating immediately
|
e.Cancel = true; // Prevent the process from terminating immediately
|
||||||
WriteLine("[yellow]Ctrl+C detected. Saving redundancies before exiting...[/]");
|
|
||||||
SaveRedundancies(finder, settings.OutputPath ?? "redundancies.json");
|
Global.WriteLine("[yellow]Ctrl+C detected. Saving redundancies before exiting...[/]");
|
||||||
Environment.Exit(0); // Exit the application gracefully
|
|
||||||
|
End();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load existing redundancies if the output file exists
|
// Load existing redundancies if the output file exists
|
||||||
@ -74,13 +84,13 @@ namespace RedundancyFinderCLI
|
|||||||
finder.Redundancies[entry.Key] = entry.Value;
|
finder.Redundancies[entry.Key] = entry.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLine($"[yellow]Resumed from existing output file: [/]'{settings.OutputPath}'");
|
Global.WriteLine($"[yellow]Resumed from existing output file: [/]'{settings.OutputPath}'");
|
||||||
WriteLine($"[yellow]Loaded {finder.Redundancies.Count} redundancies from the file.[/]");
|
Global.WriteLine($"[yellow]Loaded {finder.Redundancies.Count} redundancies from the file.[/]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
WriteLine($"[red]Failed to load existing output file: {ex.Message}[/]");
|
Global.WriteLine($"[red]Failed to load existing output file: {Markup.Escape(ex.Message)}[/]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,14 +100,14 @@ namespace RedundancyFinderCLI
|
|||||||
{
|
{
|
||||||
if (settings.Verbose)
|
if (settings.Verbose)
|
||||||
{
|
{
|
||||||
WriteLine($"[red]Access denied to file: [/]{e.Path}. Skipping. Error: [red]{e.Exception.Message}[/]");
|
Global.WriteLine($"[red]Access denied to file: [/]{e.Path}. Skipping. Error: [red]{Markup.Escape(e.Exception.Message)}[/]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (settings.Verbose)
|
if (settings.Verbose)
|
||||||
{
|
{
|
||||||
WriteLine($"[red] Error processing file:\n[/]Path:{e.Path}\n[red]{e.Exception.Message}[/]");
|
Global.WriteLine($"[red] Error processing file:\n[/]Path:{e.Path}\n[red]{Markup.Escape(e.Exception.Message)}[/]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -108,14 +118,14 @@ namespace RedundancyFinderCLI
|
|||||||
{
|
{
|
||||||
if (settings.Verbose)
|
if (settings.Verbose)
|
||||||
{
|
{
|
||||||
WriteLine($"[red]Access denied to directory: [/]{e.Path}. Skipping. Error: [red]{e.Exception.Message}[/]");
|
Global.WriteLine($"[red]Access denied to directory: [/]{e.Path}. Skipping. Error: [red]{Markup.Escape(e.Exception.Message)}[/]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (settings.Verbose)
|
if (settings.Verbose)
|
||||||
{
|
{
|
||||||
WriteLine($"[red] Error processing directory:\n[/]Path:{e.Path}\n[red]{e.Exception.Message}[/]");
|
Global.WriteLine($"[red] Error processing directory:\n[/]Path:{e.Path}\n[red]{Markup.Escape(e.Exception.Message)}[/]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -124,109 +134,102 @@ namespace RedundancyFinderCLI
|
|||||||
{
|
{
|
||||||
if (settings.Verbose)
|
if (settings.Verbose)
|
||||||
{
|
{
|
||||||
WriteLine($"[green]Processing file: [/]{e.Path}");
|
Global.WriteLine($"[green]Processing file: [/]{e.Path}");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var p = AnsiConsole.Progress();
|
|
||||||
p.Start(ctx =>
|
finder.TaskStarted += (sender, e) =>
|
||||||
{
|
{
|
||||||
finder.TaskStarted += (sender, e) =>
|
hashingTasks++;
|
||||||
|
if (settings.Verbose)
|
||||||
{
|
{
|
||||||
hashingTasks++;
|
Global.WriteLine($"[green]Task started: [/]{e}");
|
||||||
if (settings.Verbose)
|
|
||||||
{
|
|
||||||
WriteLine($"[green]Task started: [/]{e}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
finder.FileFound += (sender, e) =>
|
|
||||||
{
|
|
||||||
if (settings.Verbose)
|
|
||||||
{
|
|
||||||
WriteLine($"[green]File found: [/]{e.FilePath} {GetSizeFormat((ulong)e.Size)} ");
|
|
||||||
}
|
|
||||||
hashingTasksFinished++;
|
|
||||||
};
|
|
||||||
|
|
||||||
finder.FindRedundancies(settings.SearchPaths, extensions);
|
|
||||||
});
|
|
||||||
|
|
||||||
SaveRedundancies(finder, settings.OutputPath);
|
|
||||||
|
|
||||||
ulong totalSize = finder.Redundancies.Select(x => (ulong)x.Value.FileSize).Aggregate((a, b) => a + b);
|
|
||||||
string sizeFormat = GetSizeFormat(totalSize);
|
|
||||||
WriteLine($"Total Size: [green]{sizeFormat}[/]");
|
|
||||||
|
|
||||||
if (settings.Verbose)
|
|
||||||
{
|
|
||||||
foreach (var redundancy in finder.Redundancies.Values)
|
|
||||||
{
|
|
||||||
AnsiConsole.WriteLine($"Hash: {redundancy.Hash}");
|
|
||||||
AnsiConsole.WriteLine("Paths:");
|
|
||||||
foreach (var path in redundancy.Paths)
|
|
||||||
{
|
|
||||||
AnsiConsole.WriteLine(path);
|
|
||||||
}
|
|
||||||
AnsiConsole.WriteLine();
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
finder.FileFound += (sender, e) =>
|
||||||
|
{
|
||||||
|
if (settings.Verbose)
|
||||||
|
{
|
||||||
|
Global.WriteLine($"[green]File found: [/]{e.FilePath} [darkgreen]{Global.GetSizeFormat((ulong)e.Size)}[/]");
|
||||||
|
}
|
||||||
|
hashingTasksFinished++;
|
||||||
|
};
|
||||||
|
|
||||||
|
task = Task.Run(() =>
|
||||||
|
{
|
||||||
|
finder.FindRedundancies(settings.SearchPaths, extensions);
|
||||||
|
}, cancellation.Token);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
task.Wait(cancellation.Token);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
|
||||||
|
End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
WriteLine($"[red] Error:\n[/]{e.Message}");
|
Global.WriteLine($"[red] Error:\n[/]{e.Message}");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void End()
|
||||||
|
{
|
||||||
|
SaveRedundancies(finder, settings.OutputPath);
|
||||||
|
ulong totalSize = finder.Redundancies.Select(x => (ulong)x.Value.FileSize).Aggregate((a, b) => a + b);
|
||||||
|
string sizeFormat = Global.GetSizeFormat(totalSize);
|
||||||
|
Global.WriteLine($"Total Size: [green]{sizeFormat}[/]");
|
||||||
|
Environment.Exit(0); // Exit the application gracefully
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void SaveRedundancies(Finder finder, string outputPath)
|
private void SaveRedundancies(Finder finder, string outputPath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = JsonConvert.SerializeObject(finder.Redundancies, Formatting.Indented);
|
|
||||||
|
|
||||||
// Check if path is relative or absolute
|
// Check if path is relative or absolute
|
||||||
if (!Path.IsPathRooted(outputPath))
|
if (!Path.IsPathRooted(outputPath))
|
||||||
{
|
{
|
||||||
outputPath = Path.Combine(Directory.GetCurrentDirectory(), outputPath);
|
outputPath = Path.Combine(Directory.GetCurrentDirectory(), outputPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string json = null;
|
||||||
|
// AnsiConsole.Status()
|
||||||
|
//.Start(Global.Format($"[yellow]Writing to [/]'{outputPath}' [yellow] This may take a long time.[/]"), ctx =>
|
||||||
|
//{
|
||||||
|
// ctx.Spinner(Spinner.Known.Clock);
|
||||||
|
// ctx.SpinnerStyle = Style.Parse("yellow bold");
|
||||||
|
// Thread.Sleep(1000);
|
||||||
|
|
||||||
|
// lock (finder.Redundancies)
|
||||||
|
// {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
//});
|
||||||
|
json = JsonConvert.SerializeObject(finder.Redundancies, Formatting.Indented);
|
||||||
File.WriteAllText(outputPath, json);
|
File.WriteAllText(outputPath, json);
|
||||||
WriteLine($"[yellow]Wrote [/]{finder.Redundancies.Count}[yellow] redundancies to [/]'{outputPath}'");
|
|
||||||
|
Global.WriteLine($"[yellow]Wrote [/]{finder.Redundancies.Count}[yellow] redundancies to [/]'{outputPath}'");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
WriteLine($"[red]Failed to save redundancies: {ex.Message}[/]");
|
Global.WriteLine($"[red]Failed to save redundancies: {ex.Message}[/]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void WriteLine(string v)
|
|
||||||
{
|
|
||||||
string now = Markup.Escape($"[{DateTime.Now.ToString("HH:mm:ss")}]");
|
|
||||||
AnsiConsole.MarkupLine($"[gray]{now}[/] {v}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetSizeFormat(ulong totalSize)
|
|
||||||
{
|
|
||||||
string sizeUnit = "B";
|
|
||||||
while (totalSize > 1024)
|
|
||||||
{
|
|
||||||
totalSize /= 1024;
|
|
||||||
sizeUnit = sizeUnit switch
|
|
||||||
{
|
|
||||||
"B" => "KB",
|
|
||||||
"KB" => "MB",
|
|
||||||
"MB" => "GB",
|
|
||||||
"GB" => "TB",
|
|
||||||
_ => sizeUnit
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
string sizeFormat = $"{totalSize:.##} {sizeUnit}";
|
|
||||||
return sizeFormat;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
RedundancyFinderCLI/Global.cs
Normal file
45
RedundancyFinderCLI/Global.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace RedundancyFinderCLI
|
||||||
|
{
|
||||||
|
public static class Global
|
||||||
|
{
|
||||||
|
|
||||||
|
public static void WriteLine(string v)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine(Format(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Format(string v)
|
||||||
|
{
|
||||||
|
string now = Markup.Escape($"[{DateTime.Now.ToString("HH:mm:ss")}]");
|
||||||
|
return $"[gray]{now}[/] {v}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetSizeFormat(ulong totalSize)
|
||||||
|
{
|
||||||
|
string sizeUnit = "B";
|
||||||
|
double size = totalSize;
|
||||||
|
while (size > 1024)
|
||||||
|
{
|
||||||
|
size /= 1024d;
|
||||||
|
sizeUnit = sizeUnit switch
|
||||||
|
{
|
||||||
|
"B" => "KB",
|
||||||
|
"KB" => "MB",
|
||||||
|
"MB" => "GB",
|
||||||
|
"GB" => "TB",
|
||||||
|
_ => sizeUnit
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
string sizeFormat = $"{size:.00} {sizeUnit}";
|
||||||
|
return sizeFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using RedundancyFinder;
|
using RedundancyFinder;
|
||||||
using RedundancyFinderCLI;
|
using RedundancyFinderCLI;
|
||||||
@ -9,10 +10,12 @@ internal class Program
|
|||||||
{
|
{
|
||||||
private static void Main(string[] args)
|
private static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
Console.OutputEncoding = Encoding.UTF8;
|
||||||
var app = new CommandApp<FinderCommand>();
|
var app = new CommandApp<FinderCommand>();
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.AddCommand<AnalyzeCommand>("analyze");
|
config.AddCommand<AnalyzeCommand>("analyze");
|
||||||
|
config.AddCommand<DeleteCommand>("delete");
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
||||||
config.PropagateExceptions();
|
config.PropagateExceptions();
|
||||||
|
|||||||
@ -7,6 +7,10 @@
|
|||||||
"Analyze": {
|
"Analyze": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "analyze"
|
"commandLineArgs": "analyze"
|
||||||
|
},
|
||||||
|
"Delete": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "delete redundancies.json C:\\Users\\daskn\\Pictures\\B\\ -v"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user