nihil f8d8c4461f Delete command imlpemented
Cancellation
Formatting
2025-05-11 00:19:58 +02:00

236 lines
8.2 KiB
C#

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<FinderCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[Description("Paths to search.")]
[CommandArgument(0, "<searchPaths>")]
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; }
}
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);
int hashingTasks = 0;
int hashingTasksFinished = 0;
// Register the ProcessExit event to save redundancies on exit
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
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
Global.WriteLine("[yellow]Ctrl+C detected. Saving redundancies before exiting...[/]");
End();
};
// Load existing redundancies if the output file exists
if (File.Exists(settings.OutputPath))
{
try
{
var existingData = File.ReadAllText(settings.OutputPath);
var existingRedundancies = JsonConvert.DeserializeObject<Dictionary<string, Redundancy>>(existingData);
if (existingRedundancies != null)
{
foreach (var entry in existingRedundancies)
{
finder.Redundancies[entry.Key] = entry.Value;
}
Global.WriteLine($"[yellow]Resumed from existing output file: [/]'{settings.OutputPath}'");
Global.WriteLine($"[yellow]Loaded {finder.Redundancies.Count} redundancies from the file.[/]");
}
}
catch (Exception ex)
{
Global.WriteLine($"[red]Failed to load existing output file: {Markup.Escape(ex.Message)}[/]");
}
}
finder.FileError += (sender, e) =>
{
if (e.Exception is UnauthorizedAccessException)
{
if (settings.Verbose)
{
Global.WriteLine($"[red]Access denied to file: [/]{e.Path}. Skipping. Error: [red]{Markup.Escape(e.Exception.Message)}[/]");
}
}
else
{
if (settings.Verbose)
{
Global.WriteLine($"[red] Error processing file:\n[/]Path:{e.Path}\n[red]{Markup.Escape(e.Exception.Message)}[/]");
}
}
};
finder.DirectoryError += (sender, e) =>
{
if (e.Exception is UnauthorizedAccessException)
{
if (settings.Verbose)
{
Global.WriteLine($"[red]Access denied to directory: [/]{e.Path}. Skipping. Error: [red]{Markup.Escape(e.Exception.Message)}[/]");
}
}
else
{
if (settings.Verbose)
{
Global.WriteLine($"[red] Error processing directory:\n[/]Path:{e.Path}\n[red]{Markup.Escape(e.Exception.Message)}[/]");
}
}
};
finder.ProcessingFile += (sender, e) =>
{
if (settings.Verbose)
{
Global.WriteLine($"[green]Processing file: [/]{e.Path}");
}
};
try
{
finder.TaskStarted += (sender, e) =>
{
hashingTasks++;
if (settings.Verbose)
{
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)
{
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
{
// 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);
Global.WriteLine($"[yellow]Wrote [/]{finder.Redundancies.Count}[yellow] redundancies to [/]'{outputPath}'");
}
catch (Exception ex)
{
Global.WriteLine($"[red]Failed to save redundancies: {ex.Message}[/]");
}
}
}
}