using System.Security.Cryptography; namespace RedundancyFinder { public class Finder { List tasks = new List(); Dictionary redundancies = new Dictionary(); 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; foreach (var path in paths) { if (Directory.Exists(path)) { ProcessDirectory(path); } else if (File.Exists(path)) { ProcessFile(path); } } // Wait for all tasks to complete Task.WaitAll(tasks.ToArray()); } private void ProcessDirectory(string directoryPath) { try { // Check if the directory is hidden and skip it if true var attributes = File.GetAttributes(directoryPath); if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { return; } // Process files in the current directory foreach (var file in Directory.GetFiles(directoryPath)) { ProcessFile(file); } // Recursively process subdirectories foreach (var subDirectory in Directory.GetDirectories(directoryPath)) { try { ProcessDirectory(subDirectory); } catch (Exception ex) { DirectoryError?.Invoke(this, new DirectoryErrorEventArgs() { Exception = ex, Path = subDirectory }); } } } catch (Exception ex) { DirectoryError?.Invoke(this, new DirectoryErrorEventArgs() { Exception = ex, Path = directoryPath }); } } private void ProcessFile(string filePath) { if (!extensions.Contains(Path.GetExtension(filePath))) { return; } Task task = new(() => { 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)) { 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) { FileError?.Invoke(this, new FileErrorEventArgs() { Exception = ex, Path = filePath }); } }); task.Start(); TaskStarted?.Invoke(this, filePath); tasks.Add(task); } private static string ComputeFileHash(string filePath) { using var sha256 = SHA256.Create(); using var stream = File.OpenRead(filePath); var hash = sha256.ComputeHash(stream); return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } } }