Compare commits

..

No commits in common. "4da2ed9f3dc9047c6ca93ee44cd2f1d1e27dc3e5" and "775e915cfa5a58a89eb8dd19cc5285a3d1791a9f" have entirely different histories.

17 changed files with 132 additions and 588 deletions

BIN
0.2.zip

Binary file not shown.

BIN
0.3.zip

Binary file not shown.

BIN
0.4.zip

Binary file not shown.

BIN
0.5.zip

Binary file not shown.

BIN
0.6.zip

Binary file not shown.

BIN
0.7.zip

Binary file not shown.

View File

@ -4,7 +4,7 @@ namespace RedundancyFinder
{ {
public class Finder public class Finder
{ {
public CancellationToken cancellation; List<Task> tasks = new List<Task>();
Dictionary<string, Redundancy> redundancies = new Dictionary<string, Redundancy>(); Dictionary<string, Redundancy> redundancies = new Dictionary<string, Redundancy>();
@ -12,8 +12,6 @@ namespace RedundancyFinder
string[] extensions; string[] extensions;
List<string> ignorePaths = new List<string>();
public event EventHandler<DirectoryErrorEventArgs>? DirectoryError; public event EventHandler<DirectoryErrorEventArgs>? DirectoryError;
public event EventHandler<FileErrorEventArgs>? FileError; public event EventHandler<FileErrorEventArgs>? FileError;
public event EventHandler<FileFoundEventArgs>? FileFound; public event EventHandler<FileFoundEventArgs>? FileFound;
@ -21,14 +19,9 @@ 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));
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);
@ -38,40 +31,31 @@ namespace RedundancyFinder
ProcessFile(path); ProcessFile(path);
} }
} }
// Wait for all tasks to complete
Task.WaitAll(tasks.ToArray());
} }
private void ProcessDirectory(string directoryPath) private void ProcessDirectory(string directoryPath)
{ {
if (cancellation.IsCancellationRequested) try
{
// Check if the directory is hidden and skip it if true
var attributes = File.GetAttributes(directoryPath);
if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
{ {
return; return;
} }
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
var attributes = File.GetAttributes(subDirectory);
if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
{
continue;
}
try try
{ {
ProcessDirectory(subDirectory); ProcessDirectory(subDirectory);
@ -91,22 +75,14 @@ namespace RedundancyFinder
private void ProcessFile(string filePath) private void ProcessFile(string filePath)
{ {
if (cancellation.IsCancellationRequested)
{
return;
}
if (ignorePaths.Contains(filePath))
{
return;
}
if (!extensions.Contains(Path.GetExtension(filePath))) if (!extensions.Contains(Path.GetExtension(filePath)))
{ {
return; return;
} }
//TaskStarted?.Invoke(this, filePath); Task task = new(() =>
//ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath }); {
ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath });
try try
{ {
@ -117,14 +93,19 @@ 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))
{
return;
}
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));
} }
@ -132,7 +113,10 @@ namespace RedundancyFinder
{ {
FileError?.Invoke(this, new FileErrorEventArgs() { Exception = ex, Path = filePath }); FileError?.Invoke(this, new FileErrorEventArgs() { Exception = ex, Path = filePath });
} }
});
task.Start();
TaskStarted?.Invoke(this, filePath);
tasks.Add(task);
} }
private static string ComputeFileHash(string filePath) private static string ComputeFileHash(string filePath)

View File

@ -4,7 +4,6 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Configurations>Debug;Release;Analyze</Configurations>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -1,102 +0,0 @@
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 AnalyzeCommand : Command<AnalyzeCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[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)
{
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
.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 * (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("Extension");
table.AddColumn(new TableColumn("Files").RightAligned());
table.AddColumn(new TableColumn("Size").RightAligned());
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 = Global.GetSizeFormat((ulong)size);
var redundancySize = groups[extension].RedundancySize;
var redundancySizeFormat = Global.GetSizeFormat((ulong)redundancySize);
table.AddRow(
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);
return 0;
}
}
}

View File

@ -1,115 +0,0 @@
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;
}
}
}

View File

@ -34,65 +34,23 @@ 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
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) => finder.FileError += (sender, e) =>
{ {
@ -100,14 +58,14 @@ namespace RedundancyFinderCLI
{ {
if (settings.Verbose) if (settings.Verbose)
{ {
Global.WriteLine($"[red]Access denied to file: [/]{e.Path}. Skipping. Error: [red]{Markup.Escape(e.Exception.Message)}[/]"); AnsiConsole.MarkupLine($"[red]Access denied to file: [/]{e.Path}. Skipping. Error: [red]{e.Exception.Message}[/]");
} }
} }
else else
{ {
if (settings.Verbose) if (settings.Verbose)
{ {
Global.WriteLine($"[red] Error processing file:\n[/]Path:{e.Path}\n[red]{Markup.Escape(e.Exception.Message)}[/]"); AnsiConsole.MarkupLine($"[red] Error processing file:\n[/]Path:{e.Path}\n[red]{e.Exception.Message}[/]");
} }
} }
}; };
@ -118,14 +76,14 @@ namespace RedundancyFinderCLI
{ {
if (settings.Verbose) if (settings.Verbose)
{ {
Global.WriteLine($"[red]Access denied to directory: [/]{e.Path}. Skipping. Error: [red]{Markup.Escape(e.Exception.Message)}[/]"); AnsiConsole.MarkupLine($"[red]Access denied to directory: [/]{e.Path}. Skipping. Error: [red]{e.Exception.Message}[/]");
} }
} }
else else
{ {
if (settings.Verbose) if (settings.Verbose)
{ {
Global.WriteLine($"[red] Error processing directory:\n[/]Path:{e.Path}\n[red]{Markup.Escape(e.Exception.Message)}[/]"); AnsiConsole.MarkupLine($"[red] Error processing directory:\n[/]Path:{e.Path}\n[red]{e.Exception.Message}[/]");
} }
} }
}; };
@ -134,102 +92,104 @@ namespace RedundancyFinderCLI
{ {
if (settings.Verbose) if (settings.Verbose)
{ {
Global.WriteLine($"[green]Processing file: [/]{e.Path}"); AnsiConsole.MarkupLine($"[green]Processing file: [/]{e.Path}");
} }
}; };
try try
{ {
var p = AnsiConsole.Progress();
p
.Start(ctx =>
{
// Define tasks
//var hashing = ctx.AddTask("[green]Hashing Files[/]",autoStart:false,);
finder.TaskStarted += (sender, e) => finder.TaskStarted += (sender, e) =>
{ {
hashingTasks++; hashingTasks++;
if (settings.Verbose) if (settings.Verbose)
{ {
Global.WriteLine($"[green]Task started: [/]{e}"); AnsiConsole.MarkupLine($"[green]Task started: [/]{e}");
} }
//hashing.MaxValue = hashingTasks;
}; };
finder.FileFound += (sender, e) => finder.FileFound += (sender, e) =>
{ {
if (settings.Verbose) if (settings.Verbose)
{ {
Global.WriteLine($"[green]File found: [/]{e.FilePath} [darkgreen]{Global.GetSizeFormat((ulong)e.Size)}[/]"); AnsiConsole.MarkupLine($"[green]File found: [/]{e.FilePath} {GetSizeFormat((ulong)e.Size)} ");
} }
hashingTasksFinished++; hashingTasksFinished++;
//hashing.Value = hashingTasksFinished;
}; };
//hashing.Value = hashing.MaxValue;
task = Task.Run(() =>
{
finder.FindRedundancies(settings.SearchPaths, extensions); finder.FindRedundancies(settings.SearchPaths, extensions);
}, cancellation.Token); //hashing.StopTask();
try
});
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)
task.Wait(cancellation.Token); {
AnsiConsole.WriteLine($"Hash: {redundancy.Hash}");
AnsiConsole.WriteLine("Paths:");
foreach (var path in redundancy.Paths)
{
AnsiConsole.WriteLine(path);
} }
catch (Exception) AnsiConsole.WriteLine();
{
} }
finally
{
End();
} }
} }
catch (Exception e) catch (Exception e)
{ {
Global.WriteLine($"[red] Error:\n[/]{e.Message}"); AnsiConsole.MarkupLine($"[red] Error:\n[/]{e.Message}");
} }
return 0; return 0;
} }
private void End() private static string GetSizeFormat(ulong totalSize)
{ {
SaveRedundancies(finder, settings.OutputPath); string sizeUnit = "B";
ulong totalSize = finder.Redundancies.Select(x => (ulong)x.Value.FileSize).Aggregate((a, b) => a + b); while (totalSize > 1024)
string sizeFormat = Global.GetSizeFormat(totalSize); {
Global.WriteLine($"Total Size: [green]{sizeFormat}[/]"); totalSize /= 1024;
Environment.Exit(0); // Exit the application gracefully sizeUnit = sizeUnit switch
{
"B" => "KB",
"KB" => "MB",
"MB" => "GB",
"GB" => "TB",
_ => sizeUnit
};
} }
string sizeFormat = $"{totalSize:.##} {sizeUnit}";
private void SaveRedundancies(Finder finder, string outputPath) return sizeFormat;
{
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}[/]");
}
}
} }
} }

View File

@ -1,45 +0,0 @@
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;
}
}
}

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Text;
using System.Text.Json; using System.Text.Json;
using RedundancyFinder; using RedundancyFinder;
using RedundancyFinderCLI; using RedundancyFinderCLI;
@ -10,15 +9,10 @@ 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<DeleteCommand>("delete");
config.AddCommand<SanitizeCommand>("sanitize");
#if DEBUG #if DEBUG
config.PropagateExceptions(); config.PropagateExceptions();
config.ValidateExamples(); config.ValidateExamples();
#endif #endif

View File

@ -2,19 +2,7 @@
"profiles": { "profiles": {
"RedundancyFinderCLI": { "RedundancyFinderCLI": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "C:\\Users\\daskn\\Pictures\\ -v" "commandLineArgs": "C:\\ -v"
},
"Analyze": {
"commandName": "Project",
"commandLineArgs": "analyze"
},
"Delete": {
"commandName": "Project",
"commandLineArgs": "delete redundancies.json C:\\Users\\daskn\\Pictures\\ -v"
},
"Sanitize": {
"commandName": "Project",
"commandLineArgs": "sanitize C:\\Users\\daskn\\Pictures\\ -v -d redundancies.json"
} }
} }
} }

View File

@ -5,7 +5,6 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Configurations>Debug;Release;Analyze</Configurations>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,118 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
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 SanitizeCommand : Command<SanitizeCommand.Settings>
{
public sealed class Settings : CommandSettings
{
[Description("Path to sanitize.")]
[CommandArgument(0, "[path]")]
public string? Path { get; init; }
[Description("Show all information.")]
[CommandOption("-v|--verbose")]
[DefaultValue(false)]
public bool Verbose { get; init; }
[Description("Show all information.")]
[CommandOption("-d|--deleteNonExistent <pathToSource>")]
[DefaultValue("redundancies.json")]
public string? PathToSource { get; init; }
}
Settings settings = null;
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
this.settings = settings;
Global.WriteLine($"[white]Sanitizing {settings.Path}[/]");
Global.WriteLine($"[yellow]Deleting empty folders[/]");
int count = DeleteEmptyFolders(settings.Path);
if (File.Exists(settings.PathToSource))
{
DeleteNonExistentPaths();
}
Global.WriteLine($"[green]Deleted [/]{count}[green] empty folders[/]");
return 0;
}
public void DeleteNonExistentPaths()
{
Dictionary<string, Redundancy> redundancies = null;
AnsiConsole.Status()
.Start($"[yellow]Loading [/]'{settings.PathToSource}'", ctx =>
{
ctx.Spinner(Spinner.Known.Clock);
ctx.SpinnerStyle = Style.Parse("yellow bold");
Thread.Sleep(1000);
redundancies = JsonConvert.DeserializeObject<Dictionary<string, Redundancy>>(File.ReadAllText(settings.PathToSource));
});
foreach (var redundancy in redundancies)
{
var paths = redundancy.Value.Paths;
foreach (var path in paths.ToList())
{
Global.WriteLine($"[green]Checking file: {path}[/]");
if (!File.Exists(path))
{
Global.WriteLine($"[red]Deleting non existent file: {path}[/]");
redundancy.Value.Paths.Remove(path);
}
}
}
var json = JsonConvert.SerializeObject(redundancies, Formatting.Indented);
File.WriteAllText(settings.PathToSource, json);
}
public static int DeleteEmptyFolders(string directoryPath, int count = 0)
{
try
{
// Get all subdirectories
foreach (var subDirectory in Directory.GetDirectories(directoryPath))
{
// Recursively delete empty folders in subdirectories
count = DeleteEmptyFolders(subDirectory, count);
}
// Check if the current directory is empty
if (Directory.GetFiles(directoryPath).Length == 0 && Directory.GetDirectories(directoryPath).Length == 0)
{
Directory.Delete(directoryPath);
count++;
Global.WriteLine($"Deleted empty folder: {directoryPath}");
}
}
catch (Exception ex)
{
Global.WriteLine($"[red]Error deleting folder [/]'{directoryPath}':\n [red]{ex.Message}[/]");
}
return count;
}
}
}

View File

@ -1,11 +1,11 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.13.35818.85 VisualStudioVersion = 17.13.35818.85 d17.13
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RedundancyFinder", "RedundancyFinder\RedundancyFinder.csproj", "{925C533F-2205-4848-B742-CB013F81DF91}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedundancyFinder", "RedundancyFinder\RedundancyFinder.csproj", "{925C533F-2205-4848-B742-CB013F81DF91}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RedundancyFinderCLI", "RedundancyFinderCLI\RedundancyFinderCLI.csproj", "{7187EE24-4F0D-48F3-B76C-DAECD4A96F76}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedundancyFinderCLI", "RedundancyFinderCLI\RedundancyFinderCLI.csproj", "{7187EE24-4F0D-48F3-B76C-DAECD4A96F76}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution