using System.Security.Cryptography; namespace RedundancyFinder { public class Finder { public CancellationToken cancellation; Dictionary redundancies = new Dictionary(); public Dictionary Redundancies { get => redundancies; } string[] extensions; List ignorePaths = new List(); 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) { Redundancies?.Values.SelectMany(x => x.Paths).ToList().ForEach(x => ignorePaths.Add(x)); this.extensions = extensions; foreach (var path in paths) { if (cancellation.IsCancellationRequested) { return; } if (Directory.Exists(path)) { ProcessDirectory(path); } else if (File.Exists(path)) { ProcessFile(path); } } } private void ProcessDirectory(string directoryPath) { if (cancellation.IsCancellationRequested) { return; } try { // Process files in the current directory foreach (var file in Directory.GetFiles(directoryPath)) { if (cancellation.IsCancellationRequested) { return; } ProcessFile(file); } // Recursively process subdirectories 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 { 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 (cancellation.IsCancellationRequested) { return; } if (ignorePaths.Contains(filePath)) { return; } if (!extensions.Contains(Path.GetExtension(filePath))) { return; } //TaskStarted?.Invoke(this, 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)) { 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 }); } } 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(); } } }