release 0.1
This commit is contained in:
parent
f11bc2b2b8
commit
3a934cbcb5
67
README.md
67
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
|
||||
|
||||
8
RedundancyFinder/DirectoryErrorEventArgs.cs
Normal file
8
RedundancyFinder/DirectoryErrorEventArgs.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace RedundancyFinder
|
||||
{
|
||||
public class DirectoryErrorEventArgs
|
||||
{
|
||||
public Exception Exception { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
||||
8
RedundancyFinder/FileErrorEventArgs.cs
Normal file
8
RedundancyFinder/FileErrorEventArgs.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace RedundancyFinder
|
||||
{
|
||||
public class FileErrorEventArgs
|
||||
{
|
||||
public Exception Exception { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
||||
16
RedundancyFinder/FileFoundEventArgs.cs
Normal file
16
RedundancyFinder/FileFoundEventArgs.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,13 +6,17 @@ namespace RedundancyFinder
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
|
||||
Dictionary<string, Redundancy> redundancies = new Dictionary<string, Redundancy>();
|
||||
|
||||
Dictionary<object, Redundancy> redundancies = new Dictionary<object, Redundancy>();
|
||||
|
||||
public Dictionary<object, Redundancy> Redundancies { get => redundancies; }
|
||||
public Dictionary<string, Redundancy> Redundancies { get => redundancies; }
|
||||
|
||||
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)
|
||||
{
|
||||
this.extensions = extensions;
|
||||
@ -30,14 +34,6 @@ namespace RedundancyFinder
|
||||
|
||||
// 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)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Access denied to directory: {subDirectory}. Skipping. Error: {ex.Message}");
|
||||
DirectoryError?.Invoke(this, new DirectoryErrorEventArgs() { Exception = ex, Path = subDirectory });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An error occurred while processing directory: {subDirectory}. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (fileHash == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
long fileSize = new FileInfo(filePath).Length;
|
||||
lock (redundancies)
|
||||
{
|
||||
if (redundancies.ContainsKey(fileHash))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!redundancies.ContainsKey(fileHash))
|
||||
{
|
||||
long fileSize = new FileInfo(filePath).Length;
|
||||
redundancies.Add(fileHash, new Redundancy() { Hash = fileHash, FileSize = fileSize });
|
||||
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);
|
||||
|
||||
7
RedundancyFinder/Organisation.cs
Normal file
7
RedundancyFinder/Organisation.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace RedundancyFinder
|
||||
{
|
||||
public class Organisation
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
7
RedundancyFinder/ProcessingFileEventArgs.cs
Normal file
7
RedundancyFinder/ProcessingFileEventArgs.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace RedundancyFinder
|
||||
{
|
||||
public class ProcessingFileEventArgs
|
||||
{
|
||||
public string? Path { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
namespace RedundancyFinder
|
||||
{
|
||||
[Serializable]
|
||||
public class Redundancy
|
||||
{
|
||||
|
||||
public List<string> Paths { get; set; } = new List<string>();
|
||||
public object Hash { get; set; }
|
||||
|
||||
|
||||
62
RedundancyFinder/TreeNode.cs
Normal file
62
RedundancyFinder/TreeNode.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
195
RedundancyFinderCLI/FinderCommand.cs
Normal file
195
RedundancyFinderCLI/FinderCommand.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var app = new CommandApp<FinderCommand>();
|
||||
app.Configure(config =>
|
||||
{
|
||||
#if DEBUG
|
||||
config.PropagateExceptions();
|
||||
config.ValidateExamples();
|
||||
#endif
|
||||
});
|
||||
|
||||
app.Run(args.ToList());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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}");
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"profiles": {
|
||||
"RedundancyFinderCLI": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "D:\\"
|
||||
"commandLineArgs": "C:\\ -v"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,11 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RedundancyFinder\RedundancyFinder.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user