From f8d8c4461f7bd4fa51a6402d4cb9009dc724c147 Mon Sep 17 00:00:00 2001 From: nihil Date: Sun, 11 May 2025 00:19:58 +0200 Subject: [PATCH] Delete command imlpemented Cancellation Formatting --- RedundancyFinder/Finder.cs | 38 +++- RedundancyFinderCLI/AnalyzeCommand.cs | 74 ++++---- RedundancyFinderCLI/DeleteCommand.cs | 39 +--- RedundancyFinderCLI/FinderCommand.cs | 173 +++++++++--------- RedundancyFinderCLI/Global.cs | 45 +++++ RedundancyFinderCLI/Program.cs | 2 + .../Properties/launchSettings.json | 2 +- 7 files changed, 206 insertions(+), 167 deletions(-) create mode 100644 RedundancyFinderCLI/Global.cs diff --git a/RedundancyFinder/Finder.cs b/RedundancyFinder/Finder.cs index 28b1e99..2f566f0 100644 --- a/RedundancyFinder/Finder.cs +++ b/RedundancyFinder/Finder.cs @@ -4,6 +4,8 @@ namespace RedundancyFinder { public class Finder { + public CancellationToken cancellation; + Dictionary redundancies = new Dictionary(); public Dictionary Redundancies { get => redundancies; } @@ -19,10 +21,14 @@ namespace RedundancyFinder public event EventHandler? ProcessingFile; 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; foreach (var path in paths) { + if (cancellation.IsCancellationRequested) + { + return; + } if (Directory.Exists(path)) { ProcessDirectory(path); @@ -36,17 +42,29 @@ namespace RedundancyFinder private void ProcessDirectory(string directoryPath) { + if (cancellation.IsCancellationRequested) + { + return; + } try { // Process files in the current directory foreach (var file in Directory.GetFiles(directoryPath)) { + if (cancellation.IsCancellationRequested) + { + return; + } ProcessFile(file); } // Recursively process subdirectories foreach (var subDirectory in Directory.GetDirectories(directoryPath)) { + if (cancellation.IsCancellationRequested) + { + return; + } // Check if the directory is hidden and skip it if true var attributes = File.GetAttributes(subDirectory); if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) @@ -73,7 +91,11 @@ namespace RedundancyFinder private void ProcessFile(string filePath) { - if(ignorePaths.Contains(filePath)) + if (cancellation.IsCancellationRequested) + { + return; + } + if (ignorePaths.Contains(filePath)) { return; } @@ -83,8 +105,8 @@ namespace RedundancyFinder return; } - TaskStarted?.Invoke(this, filePath); - ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath }); + //TaskStarted?.Invoke(this, filePath); + //ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath }); try { @@ -95,14 +117,14 @@ namespace RedundancyFinder return; } 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 }; - 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)); } diff --git a/RedundancyFinderCLI/AnalyzeCommand.cs b/RedundancyFinderCLI/AnalyzeCommand.cs index 8d0ae9b..0a5d0e9 100644 --- a/RedundancyFinderCLI/AnalyzeCommand.cs +++ b/RedundancyFinderCLI/AnalyzeCommand.cs @@ -35,42 +35,62 @@ namespace RedundancyFinderCLI } public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) { - WriteLine($"[yellow]Analyzing {settings.Path}[/]"); - var redundancies = JsonConvert.DeserializeObject>(File.ReadAllText(settings.Path)); + Dictionary 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>(File.ReadAllText(settings.Path)); + + }); + + + + if (redundancies == null) + { + + } + var groups = redundancies .GroupBy(x => Path.GetExtension(x.Value.Paths[0]), x => x.Value) .ToDictionary(x => x.Key, x => new { FileSize = x.Sum(y => y.FileSize), - RedundancySize = x.Sum(y => y.FileSize) * (x.Sum(y => y.Paths.Count) - 1), - RedundancyCount = 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, + RedundantFiles = x.Count(y => y.Paths.Count > 1) }); // x => new { FileSize = x, RedundancySize = x.Sum()*(x.Count()-1)} var extensions = settings.Extensions.Split(",", StringSplitOptions.RemoveEmptyEntries); 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("Redundancy size").RightAligned()); - table.AddColumn(new TableColumn("Redundancy count").RightAligned()); - foreach (var extension in extensions) + table.AddColumn(new TableColumn("Redundancies").RightAligned()); + table.AddColumn(new TableColumn("Redundancies size").RightAligned()); + + foreach (var extension in extensions.OrderBy(x => x)) { if (groups.ContainsKey(extension)) { var size = groups[extension].FileSize; - var sizeFormat = GetSizeFormat((ulong)size); + var sizeFormat = Global.GetSizeFormat((ulong)size); var redundancySize = groups[extension].RedundancySize; - var redundancySizeFormat = GetSizeFormat((ulong)redundancySize); + var redundancySizeFormat = Global.GetSizeFormat((ulong)redundancySize); table.AddRow( - new Text(extension), - new Markup($"[green]{sizeFormat}[/]"), - new Markup($"[yellow]{redundancySizeFormat}[/]"), - new Markup($"{groups[extension].RedundancyCount}")); + new Text(extension), + new Markup($"[darkgreen]{groups[extension].RedundantFiles}[/]"), + new Markup($"[green]{sizeFormat}[/]"), + new Markup($"[cyan]{groups[extension].RedundancyCount}[/]"), + new Markup($"[yellow]{redundancySizeFormat}[/]")); } } 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; - } } } diff --git a/RedundancyFinderCLI/DeleteCommand.cs b/RedundancyFinderCLI/DeleteCommand.cs index d587453..86ab400 100644 --- a/RedundancyFinderCLI/DeleteCommand.cs +++ b/RedundancyFinderCLI/DeleteCommand.cs @@ -40,7 +40,7 @@ namespace RedundancyFinderCLI } public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) { - WriteLine($"[yellow]Analyzing {settings.Path}[/]"); + Global.WriteLine($"[yellow]Analyzing {settings.Path}[/]"); var redundancies = JsonConvert.DeserializeObject>(File.ReadAllText(settings.Path)); @@ -64,11 +64,11 @@ namespace RedundancyFinderCLI { if (redundancy.Paths.Count > 0) { - WriteLine($"[blue]Skipping [/]'{redundancy.Paths.FirstOrDefault()}'[blue]. No paths to keep![/]"); + Global.WriteLine($"[blue]Skipping [/]'{redundancy.Paths.FirstOrDefault()}'[blue]. No paths to keep![/]"); } else { - WriteLine($"[yellow]Skipping [/]'{redundancy.Hash}'[/].[blue] No paths![/]"); + Global.WriteLine($"[yellow]Skipping [/]'{redundancy.Hash}'[/].[blue] No paths![/]"); } } @@ -81,7 +81,7 @@ namespace RedundancyFinderCLI } if (pathsToDelete.Count == 0) { - WriteLine("[yellow]Nothing to delete![/]"); + Global.WriteLine("[yellow]Nothing to delete![/]"); return 0; } @@ -102,41 +102,14 @@ namespace RedundancyFinderCLI } catch (Exception e) { - WriteLine($"[red]Error deleting file: [/]'{path}'\nMessage:\n{e.Message}"); + Global.WriteLine($"[red]Error deleting file: [/]'{path}'\nMessage:\n{e.Message}"); } - WriteLine($"[yellow]Deleted file: [/]'{path}'"); + Global.WriteLine($"[yellow]Deleted file: [/]'{path}'"); } } return 0; } - - 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; - } } } diff --git a/RedundancyFinderCLI/FinderCommand.cs b/RedundancyFinderCLI/FinderCommand.cs index 3d02653..8517962 100644 --- a/RedundancyFinderCLI/FinderCommand.cs +++ b/RedundancyFinderCLI/FinderCommand.cs @@ -34,29 +34,39 @@ namespace RedundancyFinderCLI [Description("Output path.")] [CommandOption("-o|--output")] [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) { + finder.cancellation = cancellation.Token; + this.settings = settings; var extensions = settings.Extensions.Split(",", StringSplitOptions.RemoveEmptyEntries); - Finder finder = new Finder(); + int hashingTasks = 0; int hashingTasksFinished = 0; // 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 Console.CancelKeyPress += (sender, e) => { + cancellation.Cancel(); // Cancel the ongoing tasks e.Cancel = true; // Prevent the process from terminating immediately - WriteLine("[yellow]Ctrl+C detected. Saving redundancies before exiting...[/]"); - SaveRedundancies(finder, settings.OutputPath ?? "redundancies.json"); - Environment.Exit(0); // Exit the application gracefully + + Global.WriteLine("[yellow]Ctrl+C detected. Saving redundancies before exiting...[/]"); + + End(); + }; // Load existing redundancies if the output file exists @@ -74,13 +84,13 @@ namespace RedundancyFinderCLI finder.Redundancies[entry.Key] = entry.Value; } - WriteLine($"[yellow]Resumed from existing output file: [/]'{settings.OutputPath}'"); - WriteLine($"[yellow]Loaded {finder.Redundancies.Count} redundancies from the file.[/]"); + Global.WriteLine($"[yellow]Resumed from existing output file: [/]'{settings.OutputPath}'"); + Global.WriteLine($"[yellow]Loaded {finder.Redundancies.Count} redundancies from the file.[/]"); } } 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) { - 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 { 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) { - 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 { 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) { - WriteLine($"[green]Processing file: [/]{e.Path}"); + Global.WriteLine($"[green]Processing file: [/]{e.Path}"); } }; try { - var p = AnsiConsole.Progress(); - p.Start(ctx => + + finder.TaskStarted += (sender, e) => { - finder.TaskStarted += (sender, e) => + hashingTasks++; + if (settings.Verbose) { - hashingTasks++; - 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(); + Global.WriteLine($"[green]Task started: [/]{e}"); } + }; + + 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) { - WriteLine($"[red] Error:\n[/]{e.Message}"); + Global.WriteLine($"[red] Error:\n[/]{e.Message}"); } 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) { try { - var json = JsonConvert.SerializeObject(finder.Redundancies, Formatting.Indented); - // Check if path is relative or absolute if (!Path.IsPathRooted(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); - WriteLine($"[yellow]Wrote [/]{finder.Redundancies.Count}[yellow] redundancies to [/]'{outputPath}'"); + + Global.WriteLine($"[yellow]Wrote [/]{finder.Redundancies.Count}[yellow] redundancies to [/]'{outputPath}'"); } 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; - } } } diff --git a/RedundancyFinderCLI/Global.cs b/RedundancyFinderCLI/Global.cs new file mode 100644 index 0000000..1c9db3d --- /dev/null +++ b/RedundancyFinderCLI/Global.cs @@ -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; + } + } +} diff --git a/RedundancyFinderCLI/Program.cs b/RedundancyFinderCLI/Program.cs index feb5640..2eec723 100644 --- a/RedundancyFinderCLI/Program.cs +++ b/RedundancyFinderCLI/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Text.Json; using RedundancyFinder; using RedundancyFinderCLI; @@ -9,6 +10,7 @@ internal class Program { private static void Main(string[] args) { + Console.OutputEncoding = Encoding.UTF8; var app = new CommandApp(); app.Configure(config => { diff --git a/RedundancyFinderCLI/Properties/launchSettings.json b/RedundancyFinderCLI/Properties/launchSettings.json index 51ea2fa..c206fc7 100644 --- a/RedundancyFinderCLI/Properties/launchSettings.json +++ b/RedundancyFinderCLI/Properties/launchSettings.json @@ -10,7 +10,7 @@ }, "Delete": { "commandName": "Project", - "commandLineArgs": "delete -v" + "commandLineArgs": "delete redundancies.json C:\\Users\\daskn\\Pictures\\B\\ -v" } } } \ No newline at end of file