diff --git a/README.md b/README.md index a27b608..05cedaf 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,70 @@ Searches for redundant files on the filesystem and copies it to the chosen locat It also finds files that are not on the target locations and copies them too. +## TODO + +1) Festplatten: Laufwerksbuchstaben zum Namen hinzufügen +2) Bootstick (Windows 11) erstellen +3) Liste aller Dateiendungen: +.jpg +.webp +.raw +.pdf +.xsl +.xslx +.doc +.docx +.txt +.jpeg +.mov +.mp4 +.mp3 +.wav +.bmp +.gif +.png +.cu +.mid +.msb (Mobile Sheets Pro) +.mov +.avi +.wmv +.flv +.m4v +.bak (Cubase) +.cpr (Cubase) +.xml +.psd + + +4) Platten zum Zusammenfassen: +* WD Intern 1 Terra (G) Musikdaten +* Musik/Download(WDC2Terra) (K) Extern +* Bilder Videos (WDC Extern) + +Ziel/Basisplatte: WD 4 Terra +Die Platte auf die alle kommt! + +.bak Dateien im Aufnahmen Ordner sind Cubase Backups + + + + + + +1. PC Neu aufsetzen + +2. Redundanzen entfernen +3. Fehlende Dateien auf die Zielplatte +4. Ordnen +* Ordnerstruktur: +* Cubase: + - Audio + - Images + - .cpr + - .bak (nicht zwangsläufig) +* Photoshop: + Unterordner Beibehalten +* Zielordner: + - Videos: Jahr/Monat + - Bilder: Jahr/Monat diff --git a/RedundancyFinder/DirectoryErrorEventArgs.cs b/RedundancyFinder/DirectoryErrorEventArgs.cs new file mode 100644 index 0000000..07947b9 --- /dev/null +++ b/RedundancyFinder/DirectoryErrorEventArgs.cs @@ -0,0 +1,8 @@ +namespace RedundancyFinder +{ + public class DirectoryErrorEventArgs + { + public Exception Exception { get; set; } + public string Path { get; set; } + } +} \ No newline at end of file diff --git a/RedundancyFinder/FileErrorEventArgs.cs b/RedundancyFinder/FileErrorEventArgs.cs new file mode 100644 index 0000000..7ef9e0b --- /dev/null +++ b/RedundancyFinder/FileErrorEventArgs.cs @@ -0,0 +1,8 @@ +namespace RedundancyFinder +{ + public class FileErrorEventArgs + { + public Exception Exception { get; set; } + public string Path { get; set; } + } +} \ No newline at end of file diff --git a/RedundancyFinder/FileFoundEventArgs.cs b/RedundancyFinder/FileFoundEventArgs.cs new file mode 100644 index 0000000..6532878 --- /dev/null +++ b/RedundancyFinder/FileFoundEventArgs.cs @@ -0,0 +1,16 @@ +namespace RedundancyFinder +{ + public class FileFoundEventArgs + { + public string FilePath { get; } + public string Hash { get; } + public long Size { get; } + + public FileFoundEventArgs(string filePath, string hash, long size) + { + FilePath = filePath; + Hash = hash; + Size = size; + } + } +} \ No newline at end of file diff --git a/RedundancyFinder/Finder.cs b/RedundancyFinder/Finder.cs index 1514e9c..8ec7da6 100644 --- a/RedundancyFinder/Finder.cs +++ b/RedundancyFinder/Finder.cs @@ -6,13 +6,17 @@ namespace RedundancyFinder { List tasks = new List(); + Dictionary redundancies = new Dictionary(); - Dictionary redundancies = new Dictionary(); - - public Dictionary Redundancies { get => redundancies; } + public Dictionary Redundancies { get => redundancies; } string[] extensions; + public event EventHandler? DirectoryError; + public event EventHandler? FileError; + public event EventHandler? FileFound; + public event EventHandler? TaskStarted; + public event EventHandler? ProcessingFile; public void FindRedundancies(string[] paths, string[] extensions) { this.extensions = extensions; @@ -28,16 +32,8 @@ namespace RedundancyFinder } } - // Wait for all tasks to complete + // Wait for all tasks to complete Task.WaitAll(tasks.ToArray()); - - foreach (var redundancy in redundancies.Values.ToList()) - { - if (redundancy.Paths.Count == 1) - { - redundancies.Remove(redundancy.Hash); - } - } } private void ProcessDirectory(string directoryPath) @@ -57,23 +53,15 @@ namespace RedundancyFinder { ProcessDirectory(subDirectory); } - catch (UnauthorizedAccessException ex) - { - Console.WriteLine($"Access denied to directory: {subDirectory}. Skipping. Error: {ex.Message}"); - } catch (Exception ex) { - Console.WriteLine($"An error occurred while processing directory: {subDirectory}. Error: {ex.Message}"); + DirectoryError?.Invoke(this, new DirectoryErrorEventArgs() { Exception = ex, Path = subDirectory }); } } } - catch (UnauthorizedAccessException ex) - { - Console.WriteLine($"Access denied to directory: {directoryPath}. Skipping. Error: {ex.Message}"); - } catch (Exception ex) { - Console.WriteLine($"An error occurred while processing directory: {directoryPath}. Error: {ex.Message}"); + DirectoryError?.Invoke(this, new DirectoryErrorEventArgs() { Exception = ex, Path = directoryPath }); } } @@ -87,27 +75,44 @@ namespace RedundancyFinder Task task = new(() => { - Console.WriteLine($"Processing file: {filePath}"); + ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath }); + try { + var fileHash = ComputeFileHash(filePath); - if (!redundancies.ContainsKey(fileHash)) + if (fileHash == null) { - long fileSize = new FileInfo(filePath).Length; - redundancies.Add(fileHash, new Redundancy() { Hash = fileHash, FileSize = fileSize }); + return; } - redundancies[fileHash].Paths.Add(filePath); + long fileSize = new FileInfo(filePath).Length; + lock (redundancies) + { + if (redundancies.ContainsKey(fileHash)) + { + return; + } + + if (!redundancies.ContainsKey(fileHash)) + { + var redundancy = new Redundancy() { Hash = fileHash, FileSize = fileSize }; + redundancies.Add(fileHash, redundancy); + } + redundancies[fileHash].Paths.Add(filePath); + } + FileFound?.Invoke(this, new FileFoundEventArgs(filePath, fileHash, fileSize)); } catch (Exception ex) { - Console.WriteLine($"Error processing file {filePath}: {ex.Message}"); + FileError?.Invoke(this, new FileErrorEventArgs() { Exception = ex, Path = filePath }); } }); task.Start(); + TaskStarted?.Invoke(this, filePath); tasks.Add(task); } - private string ComputeFileHash(string filePath) + private static string ComputeFileHash(string filePath) { using var sha256 = SHA256.Create(); using var stream = File.OpenRead(filePath); diff --git a/RedundancyFinder/Organisation.cs b/RedundancyFinder/Organisation.cs new file mode 100644 index 0000000..96410ab --- /dev/null +++ b/RedundancyFinder/Organisation.cs @@ -0,0 +1,7 @@ +namespace RedundancyFinder +{ + public class Organisation + { + + } +} \ No newline at end of file diff --git a/RedundancyFinder/ProcessingFileEventArgs.cs b/RedundancyFinder/ProcessingFileEventArgs.cs new file mode 100644 index 0000000..5c90621 --- /dev/null +++ b/RedundancyFinder/ProcessingFileEventArgs.cs @@ -0,0 +1,7 @@ +namespace RedundancyFinder +{ + public class ProcessingFileEventArgs + { + public string? Path { get; set; } + } +} \ No newline at end of file diff --git a/RedundancyFinder/Redundancy.cs b/RedundancyFinder/Redundancy.cs index b1e1a8b..459168a 100644 --- a/RedundancyFinder/Redundancy.cs +++ b/RedundancyFinder/Redundancy.cs @@ -1,7 +1,9 @@ namespace RedundancyFinder { + [Serializable] public class Redundancy { + public List Paths { get; set; } = new List(); public object Hash { get; set; } diff --git a/RedundancyFinder/TreeNode.cs b/RedundancyFinder/TreeNode.cs new file mode 100644 index 0000000..ef2f1db --- /dev/null +++ b/RedundancyFinder/TreeNode.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RedundancyFinder +{ + using System; + using System.Collections.Generic; + + using System; + using System.Collections.Generic; + + public class DirectoryNode + { + public string Name { get; set; } + public List Children { get; private set; } + + public DirectoryNode(string name) + { + Name = name; + Children = new List(); + } + + public void AddChild(DirectoryNode child) + { + Children.Add(child); + } + + public void RemoveChild(DirectoryNode child) + { + Children.Remove(child); + } + + public void Traverse(int level = 0) + { + Console.WriteLine(new string(' ', level * 2) + Name); + foreach (var child in Children) + { + child.Traverse(level + 1); + } + } + + public DirectoryNode? Find(string name) + { + if (Name == name) + return this; + + foreach (var child in Children) + { + var result = child.Find(name); + if (result != null) + return result; + } + + return null; + } + } + + +} diff --git a/RedundancyFinderCLI/FinderCommand.cs b/RedundancyFinderCLI/FinderCommand.cs new file mode 100644 index 0000000..80d3495 --- /dev/null +++ b/RedundancyFinderCLI/FinderCommand.cs @@ -0,0 +1,195 @@ +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.MarkupLine($"Hash: {redundancy.Hash}"); + AnsiConsole.MarkupLine("Paths:"); + foreach (var path in redundancy.Paths) + { + AnsiConsole.MarkupLine(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; + } + } +} diff --git a/RedundancyFinderCLI/Program.cs b/RedundancyFinderCLI/Program.cs index c59a3d8..7caf140 100644 --- a/RedundancyFinderCLI/Program.cs +++ b/RedundancyFinderCLI/Program.cs @@ -1,42 +1,23 @@ using System; +using System.Text.Json; using RedundancyFinder; +using RedundancyFinderCLI; +using Spectre.Console; +using Spectre.Console.Cli; -if (args.Length == 0) +internal class Program { - Console.WriteLine("No arguments provided."); - return; -} - -Finder finder = new Finder(); - - -finder.FindRedundancies(new[] { "D:\\" }, new[] { ".jpg", ".jpeg", ".bmp", ".gif", ".mp4", ".mp3" }); - - -foreach (var redundancy in finder.Redundancies.Values) -{ - Console.WriteLine($"Hash: {redundancy.Hash}"); - Console.WriteLine("Paths:"); - foreach (var path in redundancy.Paths) + private static void Main(string[] args) { - Console.WriteLine(path); + var app = new CommandApp(); + app.Configure(config => + { +#if DEBUG + config.PropagateExceptions(); + config.ValidateExamples(); +#endif + }); + + app.Run(args.ToList()); } - Console.WriteLine(); -} - -ulong totalSize = finder.Redundancies.Select(x => (ulong)x.Value.FileSize).Aggregate((a, b) => a + b); -string sizeUnit = "B"; -while (totalSize > 1024) -{ - totalSize /= 1024; - sizeUnit = sizeUnit switch - { - "B" => "KB", - "KB" => "MB", - "MB" => "GB", - "GB" => "TB", - _ => sizeUnit - }; - -} -Console.WriteLine($"Total Size: {totalSize:.##} {sizeUnit}"); +} \ No newline at end of file diff --git a/RedundancyFinderCLI/Properties/launchSettings.json b/RedundancyFinderCLI/Properties/launchSettings.json index e48f7ad..b459f95 100644 --- a/RedundancyFinderCLI/Properties/launchSettings.json +++ b/RedundancyFinderCLI/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "RedundancyFinderCLI": { "commandName": "Project", - "commandLineArgs": "D:\\" + "commandLineArgs": "C:\\ -v" } } } \ No newline at end of file diff --git a/RedundancyFinderCLI/RedundancyFinderCLI.csproj b/RedundancyFinderCLI/RedundancyFinderCLI.csproj index 905edbc..01dc379 100644 --- a/RedundancyFinderCLI/RedundancyFinderCLI.csproj +++ b/RedundancyFinderCLI/RedundancyFinderCLI.csproj @@ -7,6 +7,11 @@ enable + + + + +