release 0.1

This commit is contained in:
nihil 2025-05-10 03:12:05 +02:00
parent f11bc2b2b8
commit 3a934cbcb5
13 changed files with 429 additions and 66 deletions

View File

@ -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. 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

View File

@ -0,0 +1,8 @@
namespace RedundancyFinder
{
public class DirectoryErrorEventArgs
{
public Exception Exception { get; set; }
public string Path { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace RedundancyFinder
{
public class FileErrorEventArgs
{
public Exception Exception { get; set; }
public string Path { get; set; }
}
}

View File

@ -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;
}
}
}

View File

@ -6,13 +6,17 @@ namespace RedundancyFinder
{ {
List<Task> tasks = new List<Task>(); List<Task> tasks = new List<Task>();
Dictionary<string, Redundancy> redundancies = new Dictionary<string, Redundancy>();
Dictionary<object, Redundancy> redundancies = new Dictionary<object, Redundancy>(); public Dictionary<string, Redundancy> Redundancies { get => redundancies; }
public Dictionary<object, Redundancy> Redundancies { get => redundancies; }
string[] extensions; string[] extensions;
public event EventHandler<DirectoryErrorEventArgs>? DirectoryError;
public event EventHandler<FileErrorEventArgs>? FileError;
public event EventHandler<FileFoundEventArgs>? FileFound;
public event EventHandler<string>? TaskStarted;
public event EventHandler<ProcessingFileEventArgs>? ProcessingFile;
public void FindRedundancies(string[] paths, string[] extensions) public void FindRedundancies(string[] paths, string[] extensions)
{ {
this.extensions = extensions; this.extensions = extensions;
@ -30,14 +34,6 @@ namespace RedundancyFinder
// Wait for all tasks to complete // Wait for all tasks to complete
Task.WaitAll(tasks.ToArray()); 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) private void ProcessDirectory(string directoryPath)
@ -57,23 +53,15 @@ namespace RedundancyFinder
{ {
ProcessDirectory(subDirectory); ProcessDirectory(subDirectory);
} }
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"Access denied to directory: {subDirectory}. Skipping. Error: {ex.Message}");
}
catch (Exception ex) 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) 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(() => Task task = new(() =>
{ {
Console.WriteLine($"Processing file: {filePath}"); ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath });
try try
{ {
var fileHash = ComputeFileHash(filePath); var fileHash = ComputeFileHash(filePath);
if (!redundancies.ContainsKey(fileHash)) if (fileHash == null)
{ {
long fileSize = new FileInfo(filePath).Length; return;
redundancies.Add(fileHash, new Redundancy() { Hash = fileHash, FileSize = fileSize });
} }
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) catch (Exception ex)
{ {
Console.WriteLine($"Error processing file {filePath}: {ex.Message}"); FileError?.Invoke(this, new FileErrorEventArgs() { Exception = ex, Path = filePath });
} }
}); });
task.Start(); task.Start();
TaskStarted?.Invoke(this, filePath);
tasks.Add(task); tasks.Add(task);
} }
private string ComputeFileHash(string filePath) private static string ComputeFileHash(string filePath)
{ {
using var sha256 = SHA256.Create(); using var sha256 = SHA256.Create();
using var stream = File.OpenRead(filePath); using var stream = File.OpenRead(filePath);

View File

@ -0,0 +1,7 @@
namespace RedundancyFinder
{
public class Organisation
{
}
}

View File

@ -0,0 +1,7 @@
namespace RedundancyFinder
{
public class ProcessingFileEventArgs
{
public string? Path { get; set; }
}
}

View File

@ -1,7 +1,9 @@
namespace RedundancyFinder namespace RedundancyFinder
{ {
[Serializable]
public class Redundancy public class Redundancy
{ {
public List<string> Paths { get; set; } = new List<string>(); public List<string> Paths { get; set; } = new List<string>();
public object Hash { get; set; } public object Hash { get; set; }

View File

@ -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<DirectoryNode> Children { get; private set; }
public DirectoryNode(string name)
{
Name = name;
Children = new List<DirectoryNode>();
}
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;
}
}
}

View File

@ -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<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;}
}
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;
}
}
}

View File

@ -1,42 +1,23 @@
using System; using System;
using System.Text.Json;
using RedundancyFinder; using RedundancyFinder;
using RedundancyFinderCLI;
using Spectre.Console;
using Spectre.Console.Cli;
if (args.Length == 0) internal class Program
{ {
Console.WriteLine("No arguments provided."); private static void Main(string[] args)
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)
{ {
Console.WriteLine(path); var app = new CommandApp<FinderCommand>();
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}");

View File

@ -2,7 +2,7 @@
"profiles": { "profiles": {
"RedundancyFinderCLI": { "RedundancyFinderCLI": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "D:\\" "commandLineArgs": "C:\\ -v"
} }
} }
} }

View File

@ -7,6 +7,11 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\RedundancyFinder\RedundancyFinder.csproj" /> <ProjectReference Include="..\RedundancyFinder\RedundancyFinder.csproj" />
</ItemGroup> </ItemGroup>