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 FinderCommand : Command { public sealed class Settings : CommandSettings { [Description("Paths to search.")] [CommandArgument(0, "")] public string[] SearchPaths { 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; } [Description("Output path.")] [CommandOption("-o|--output")] [DefaultValue("redundancies.json")] public string? OutputPath {get; init;} } public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) { var extensions = settings.Extensions.Split(",", StringSplitOptions.RemoveEmptyEntries); Finder finder = new Finder(); int hashingTasks = 0; int hashingTasksFinished = 0; finder.FileError += (sender, e) => { if (e.Exception is UnauthorizedAccessException) { if (settings.Verbose) { AnsiConsole.MarkupLine($"[red]Access denied to file: [/]{e.Path}. Skipping. Error: [red]{e.Exception.Message}[/]"); } } else { if (settings.Verbose) { AnsiConsole.MarkupLine($"[red] Error processing file:\n[/]Path:{e.Path}\n[red]{e.Exception.Message}[/]"); } } }; finder.DirectoryError += (sender, e) => { if (e.Exception is UnauthorizedAccessException) { if (settings.Verbose) { AnsiConsole.MarkupLine($"[red]Access denied to directory: [/]{e.Path}. Skipping. Error: [red]{e.Exception.Message}[/]"); } } else { if (settings.Verbose) { AnsiConsole.MarkupLine($"[red] Error processing directory:\n[/]Path:{e.Path}\n[red]{e.Exception.Message}[/]"); } } }; finder.ProcessingFile += (sender, e) => { if (settings.Verbose) { AnsiConsole.MarkupLine($"[green]Processing file: [/]{e.Path}"); } }; try { var p = AnsiConsole.Progress(); p .Start(ctx => { // Define tasks //var hashing = ctx.AddTask("[green]Hashing Files[/]",autoStart:false,); finder.TaskStarted += (sender, e) => { hashingTasks++; if (settings.Verbose) { AnsiConsole.MarkupLine($"[green]Task started: [/]{e}"); } //hashing.MaxValue = hashingTasks; }; finder.FileFound += (sender, e) => { if (settings.Verbose) { AnsiConsole.MarkupLine($"[green]File found: [/]{e.FilePath} {GetSizeFormat((ulong)e.Size)} "); } hashingTasksFinished++; //hashing.Value = hashingTasksFinished; }; //hashing.Value = hashing.MaxValue; finder.FindRedundancies(settings.SearchPaths, extensions); //hashing.StopTask(); }); var json = JsonConvert.SerializeObject(finder.Redundancies, Formatting.Indented); File.WriteAllText(settings.OutputPath, json); AnsiConsole.MarkupLine($"[yellow]Wrote [/]{finder.Redundancies.Count}[yellow] redundancies to [/]'{settings.OutputPath}'"); ulong totalSize = finder.Redundancies.Select(x => (ulong)x.Value.FileSize).Aggregate((a, b) => a + b); string sizeFormat = GetSizeFormat(totalSize); AnsiConsole.MarkupLine($"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(); } } } catch (Exception e) { AnsiConsole.MarkupLine($"[red] Error:\n[/]{e.Message}"); } return 0; } 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; } } }