using mdfinder.hashprovider; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Forms; using System.Windows.Media.Imaging; namespace mdfinder { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { /// The media extentions. private static readonly string[] MEDIA_EXTENTIONS = new[] { ".AVI", ".MPG", ".MPEG", ".MP3", ".MP4", ".MKV", ".WAV" }; /// The image extentions. private static readonly string[] IMAGE_EXTENTIONS = new[] { ".JPG", ".JPEG", ".PNG", ".BMP", ".TIF", ".TIFF", ".ICO", "GIF" }; /// The text extentions. private static readonly string[] TEXT_EXTENTIONS = new[] { ".TXT", ".XML", ".HTM", ".HTML", ".JS", ".CSS" }; #region Properties /// Gets or sets the database. /// The database. public DBHelper Database { get; set; } /// Gets or sets the scanner. /// The scanner. public Scanner Scanner { get; set; } /// Gets or sets the hash providers. /// The hash providers. public IEnumerable HashProviders { get; set; } /// Gets or sets the default provider. /// The default provider. public IHashProvider DefaultProvider { get; set; } /// Gets or sets the scan results. /// The scan results. public ScanResults ScanResults { get; set; } #endregion Properties #region Constructors /// Default constructor for the main window of the application. public MainWindow() { this.Database = new DBHelper(); this.Scanner = new Scanner(); this.DefaultProvider = new MD5HashProvider(); this.HashProviders = GetProviderPlugins(); this.ScanResults = new ScanResults(); this.Scanner.DirectoryFound += (sender, args) => Dispatcher.Invoke(() => txtProgressLabel.Content = args.Directory.Name); this.Scanner.FilesFound += (sender, args) => { foreach(var file in args.Files) { if(Properties.Settings.Default.SkipEmptyFiles && file.Length == 0) { break; } var provider = this.HashProviders.Where(hp => hp.FileExtensions.Contains(file.Extension.ToUpper())).OrderByDescending(hp => hp.Priority).FirstOr(this.DefaultProvider); this.Database.InsertFileRecord(file.FullName, file.Length, provider.GetHash(file), provider.Name); Dispatcher.Invoke(() => txtProgressLabel.Content = file.FullName); } }; this.Scanner.ReportProgress += (sender, args) => Dispatcher.Invoke(() => { if(args.Processed > 0) { this.progressBar.Value = args.Percentage * 100; this.taskBarInfo.ProgressValue = args.Percentage; } else { this.taskBarInfo.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None; } }); InitializeComponent(); } #endregion Constructors #region Methods /// Gets the provider plugins from a folder. /// /// An enumerator that allows foreach to be used to process the provider plugins in this /// collection. /// private IEnumerable GetProviderPlugins() { if(!string.IsNullOrWhiteSpace(Properties.Settings.Default.ProviderFolder) && Directory.Exists(Properties.Settings.Default.ProviderFolder)) { var directory = new DirectoryInfo(Properties.Settings.Default.ProviderFolder); foreach(var pluginFile in directory.GetFiles("*.dll")) { var assembly = Assembly.LoadFrom(pluginFile.FullName); foreach(var type in assembly.GetTypes().Where(t => t.GetInterface("IHashProvider") != null)) { yield return Activator.CreateInstance(type) as IHashProvider; } } } } /// Sets duplicate file collection. /// The duplicates. private void SetDuplicateFileCollection(IEnumerable duplicates) { this.ScanResults.DuplicateFiles = duplicates; } /// Gets the duplicate files in this collection. /// /// An enumerator that allows foreach to be used to process the duplicate files in this /// collection. /// private IEnumerable GetDuplicateFiles() { return this.Database.GetFileRecords().GroupBy(fr => fr.Hash).Where(g => g.Count() > 1).Select(g => new DuplicateFileGroup(g)).ToArray(); } /// Resets the media preview. private void ResetMediaPreview() { this.mediaPreview.Stop(); this.mediaPreview.Source = null; this.imagePanel.RowDefinitions.Clear(); this.imagePanel.Children.Clear(); this.textPreview.Text = string.Empty; this.mediaPreviewContainer.Visibility = Visibility.Hidden; this.imagePanel.Visibility = Visibility.Hidden; this.textPreview.Visibility = Visibility.Hidden; this.stackNoPreview.Visibility = Visibility.Visible; } private void ProcessDuplicateFileGroups(string tag, bool archive, params DuplicateFileGroup[] duplicateFileGroups) { new Thread(() => { var actionableFiles = Enumerable.Empty(); var archiveName = string.Format("archive-{0}.zip", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")); this.Dispatcher.Invoke(() => this.IsEnabled = false); if(duplicateFileGroups.Any()) { foreach(var duplicateFileGroup in duplicateFileGroups) { if(duplicateFileGroup != null) { if(tag == "largest") { actionableFiles = duplicateFileGroup.FileRecords.OrderByDescending(fr => fr.Size).Skip(1); } else if(tag == "smallest") { actionableFiles = duplicateFileGroup.FileRecords.OrderBy(fr => fr.Size).Skip(1); } else { actionableFiles = duplicateFileGroup.FileRecords.Where(fr => !fr.Keep); } ZipArchive zipFile = null; if(archive) { if(duplicateFileGroups.Count() == 1) { archiveName = string.Format("{0}.zip", duplicateFileGroup.Hash); } zipFile = ZipFile.Open(System.IO.Path.Combine(Properties.Settings.Default.ArchiveFolder, archiveName), ZipArchiveMode.Update); } if(archive && zipFile != null) { //Zip everything up. foreach(var file in actionableFiles) { zipFile.CreateEntryFromFile(file.Path.LocalPath, System.IO.Path.GetFileName(file.Path.LocalPath)); } zipFile.Dispose(); } // Make sure the garbage collector has enough time to clear up all // references to the files. GC.Collect(); GC.WaitForPendingFinalizers(); foreach(var file in actionableFiles) { //Do the deletion try { File.Delete(file.Path.LocalPath); this.Database.RemoveFileRecord(file.Id); } catch(Exception ex) { var r = System.Windows.MessageBox.Show(ex.Message, string.Format("Error deleting {0}", file.Path)); } } SetDuplicateFileCollection(GetDuplicateFiles()); } } } this.Dispatcher.Invoke(() => this.IsEnabled = true); }).Start(); } /// Event handler. Called by btnFilePicker for click events. /// Source of the event. /// Routed event information. private void btnFilePicker_Click(object sender, RoutedEventArgs e) { var fbd = new FolderBrowserDialog(); if(fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { txtScanLocation.Text = fbd.SelectedPath; } } /// Event handler. Called by btnScan for click events. /// Source of the event. /// Routed event information. private void btnScan_Click(object sender, RoutedEventArgs e) { var location = txtScanLocation.Text; ResetMediaPreview(); if(!this.Scanner.IsScanning) { new Thread(() => { this.Scanner.Scan(location); this.Dispatcher.Invoke(() => txtProgressLabel.Content = string.Empty); this.Dispatcher.Invoke(() => progressBar.Value = 0); this.Dispatcher.Invoke(() => taskBarInfo.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Normal); this.Dispatcher.Invoke(() => taskBarInfo.ProgressValue = 0); this.Dispatcher.Invoke(() => SetDuplicateFileCollection(GetDuplicateFiles())); }).Start(); } } /// Event handler. Called by BtnClear for click events. /// Source of the event. /// Routed event information. private void BtnClear_Click(object sender, RoutedEventArgs e) { this.Database.Clear(); ResetMediaPreview(); SetDuplicateFileCollection(Enumerable.Empty()); } private void BtnNotDuplicate_Click(object sender, RoutedEventArgs e) { if(this.ScanResults.SelectedDuplicateFileGroup != null) { foreach(var fileRecord in this.ScanResults.SelectedDuplicateFileGroup.FileRecords) { this.Database.RemoveFileRecord(fileRecord.Id); } ResetMediaPreview(); this.ScanResults.DuplicateFiles = this.ScanResults.DuplicateFiles.Except(new[] { this.ScanResults.SelectedDuplicateFileGroup }); this.ScanResults.SelectedDuplicateFileGroup = null; } } /// Event handler. Called by Hyperlink for click events. /// Source of the event. /// Routed event information. private void Hyperlink_Click(object sender, RoutedEventArgs e) { Hyperlink link = (Hyperlink)e.OriginalSource; Process.Start(link.NavigateUri.AbsoluteUri); } /// Event handler. Called by MenuOptions for click events. /// Source of the event. /// Routed event information. private void MenuOptions_Click(object sender, RoutedEventArgs e) { var optionsWindow = new OptionsWindow(); optionsWindow.ShowDialog(); this.HashProviders = GetProviderPlugins(); } /// Event handler. Called by MenuAbout for click events. /// Source of the event. /// Routed event information. private void MenuAbout_Click(object sender, RoutedEventArgs e) { var aboutWindow = new AboutWindow(); aboutWindow.Show(); } /// Event handler. Called by ListBoxDupes for initialized events. /// Source of the event. /// Event information. private void ListBoxDupes_Initialized(object sender, EventArgs e) { new Thread(() => { this.Dispatcher.Invoke(() => SetDuplicateFileCollection(GetDuplicateFiles())); }).Start(); } /// Event handler. Called by ListBoxDupes for selection changed events. /// Source of the event. /// Selection changed event information. private void ListBoxDupes_SelectionChanged(object sender, SelectionChangedEventArgs e) { if(e.AddedItems.Count > 0) { this.mediaPreview.Source = null; this.imagePanel.Children.Clear(); this.imagePanel.RowDefinitions.Clear(); this.textPreview.Text = string.Empty; var rowCount = 0; var selectedGroup = e.AddedItems[0] as DuplicateFileGroup; if(selectedGroup.FileRecords.All(f => IMAGE_EXTENTIONS.Contains(System.IO.Path.GetExtension(f.Path.LocalPath).ToUpper()))) { this.SingleFileGroupDuplicateActions.Visibility = Visibility.Hidden; this.AllFileGroupsDuplicateActions.Visibility = Visibility.Visible; for(int i = 0; i < selectedGroup.FileRecords.Count; i++) { var rowDefinition = new RowDefinition(); rowDefinition.Height = new GridLength(1, GridUnitType.Star); this.imagePanel.RowDefinitions.Add(rowDefinition); } foreach(var item in selectedGroup.FileRecords) { var fileRecord = item as FileRecord; var extension = System.IO.Path.GetExtension(fileRecord.Path.LocalPath).ToUpper(); if(IMAGE_EXTENTIONS.Contains(extension)) { this.mediaPreview.Source = null; var image = new Image(); image.Source = new BitmapImage(fileRecord.Path); this.imagePanel.Children.Add(image); Grid.SetRow(image, rowCount++); this.textPreview.Text = string.Empty; this.mediaPreviewContainer.Visibility = Visibility.Hidden; this.imagePanel.Visibility = Visibility.Visible; this.textPreview.Visibility = Visibility.Hidden; this.stackNoPreview.Visibility = Visibility.Hidden; } else if(TEXT_EXTENTIONS.Contains(extension)) { this.mediaPreview.Source = null; this.imagePanel.Children.Clear(); this.imagePanel.RowDefinitions.Clear(); this.textPreview.Text = File.ReadAllText(fileRecord.Path.LocalPath); this.mediaPreviewContainer.Visibility = Visibility.Hidden; this.imagePanel.Visibility = Visibility.Hidden; this.textPreview.Visibility = Visibility.Visible; this.stackNoPreview.Visibility = Visibility.Hidden; } } this.ScanResults.SelectedDuplicateFileGroup = selectedGroup; } else { this.ScanResults.SelectedDuplicateFileGroup = e.AddedItems[0] as DuplicateFileGroup; } } else { this.ScanResults.SelectedDuplicateFileGroup = null; } } /// Event handler. Called by PerformDuplicateAction for click events. /// Source of the event. /// Routed event information. private void PerformDuplicateAction_Click(object sender, RoutedEventArgs e) { var tag = (sender as System.Windows.Controls.Button).Tag.ToString(); var duplicateFileGroup = this.listBoxDupes.SelectedItem as DuplicateFileGroup; var archive = this.checkboxArchiveRemainingFiles.IsChecked ?? false; this.ScanResults.SelectedDuplicateFileGroup = null; ResetMediaPreview(); ProcessDuplicateFileGroups(tag, archive, duplicateFileGroup); } private void PerformDuplicateActionAll_Click(object sender, RoutedEventArgs e) { var tag = (sender as System.Windows.Controls.Button).Tag.ToString(); var duplicateFileGroups = new List(); var archive = this.checkboxArchiveRemainingFiles.IsChecked ?? false; foreach(var item in this.listBoxDupes.Items) { duplicateFileGroups.Add(item as DuplicateFileGroup); } this.ScanResults.SelectedDuplicateFileGroup = null; ResetMediaPreview(); ProcessDuplicateFileGroups(tag, archive, duplicateFileGroups.ToArray()); } /// /// Event handler. Called by DatagridFileList for selection changed events. /// /// Source of the event. /// Selection changed event information. private void DatagridFileList_SelectionChanged(object sender, SelectionChangedEventArgs e) { if(e.AddedItems.Count == 1) { var fileRecord = e.AddedItems[0] as FileRecord; if(fileRecord != null) { this.SingleFileGroupDuplicateActions.Visibility = Visibility.Visible; this.AllFileGroupsDuplicateActions.Visibility = Visibility.Hidden; var extension = System.IO.Path.GetExtension(fileRecord.Path.LocalPath).ToUpper(); if(MEDIA_EXTENTIONS.Contains(extension)) { this.mediaPreview.Source = fileRecord.Path; this.imagePanel.Children.Clear(); this.imagePanel.RowDefinitions.Clear(); this.textPreview.Text = string.Empty; this.mediaPreviewContainer.Visibility = Visibility.Visible; this.imagePanel.Visibility = Visibility.Hidden; this.textPreview.Visibility = Visibility.Hidden; this.stackNoPreview.Visibility = Visibility.Hidden; } else if(IMAGE_EXTENTIONS.Contains(extension)) { var rowDefinition = new RowDefinition(); rowDefinition.Height = new GridLength(1, GridUnitType.Star); var image = new Image(); image.Source = new BitmapImage(fileRecord.Path); Grid.SetRow(image, 0); this.mediaPreview.Source = null; this.imagePanel.Children.Clear(); this.imagePanel.RowDefinitions.Clear(); this.imagePanel.RowDefinitions.Add(rowDefinition); this.imagePanel.Children.Add(image); this.textPreview.Text = string.Empty; this.mediaPreviewContainer.Visibility = Visibility.Hidden; this.imagePanel.Visibility = Visibility.Visible; this.textPreview.Visibility = Visibility.Hidden; this.stackNoPreview.Visibility = Visibility.Hidden; } else if(TEXT_EXTENTIONS.Contains(extension)) { this.mediaPreview.Source = null; this.imagePanel.Children.Clear(); this.imagePanel.RowDefinitions.Clear(); this.textPreview.Text = File.ReadAllText(fileRecord.Path.LocalPath); this.mediaPreviewContainer.Visibility = Visibility.Hidden; this.imagePanel.Visibility = Visibility.Hidden; this.textPreview.Visibility = Visibility.Visible; this.stackNoPreview.Visibility = Visibility.Hidden; } else //Can't preivew. { ResetMediaPreview(); } } } } } #endregion Methods }