mirror of https://github.com/wagesj45/mdfinder.git
513 lines
22 KiB
C#
513 lines
22 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for MainWindow.xaml
|
|
/// </summary>
|
|
public partial class MainWindow : Window
|
|
{
|
|
/// <summary> The media extentions. </summary>
|
|
private static readonly string[] MEDIA_EXTENTIONS = new[] { ".AVI", ".MPG", ".MPEG", ".MP3", ".MP4", ".MKV", ".WAV" };
|
|
|
|
/// <summary> The image extentions. </summary>
|
|
private static readonly string[] IMAGE_EXTENTIONS = new[] { ".JPG", ".JPEG", ".PNG", ".BMP", ".TIF", ".TIFF", ".ICO", "GIF" };
|
|
|
|
/// <summary> The text extentions. </summary>
|
|
private static readonly string[] TEXT_EXTENTIONS = new[] { ".TXT", ".XML", ".HTM", ".HTML", ".JS", ".CSS" };
|
|
|
|
#region Properties
|
|
|
|
/// <summary> Gets or sets the database. </summary>
|
|
/// <value> The database. </value>
|
|
public DBHelper Database { get; set; }
|
|
|
|
/// <summary> Gets or sets the scanner. </summary>
|
|
/// <value> The scanner. </value>
|
|
public Scanner Scanner { get; set; }
|
|
|
|
/// <summary> Gets or sets the hash providers. </summary>
|
|
/// <value> The hash providers. </value>
|
|
public IEnumerable<IHashProvider> HashProviders { get; set; }
|
|
|
|
/// <summary> Gets or sets the default provider. </summary>
|
|
/// <value> The default provider. </value>
|
|
public IHashProvider DefaultProvider { get; set; }
|
|
|
|
/// <summary> Gets or sets the scan results. </summary>
|
|
/// <value> The scan results. </value>
|
|
public ScanResults ScanResults { get; set; }
|
|
|
|
#endregion Properties
|
|
|
|
#region Constructors
|
|
|
|
/// <summary> Default constructor for the main window of the application. </summary>
|
|
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
|
|
|
|
/// <summary> Gets the provider plugins from a folder. </summary>
|
|
/// <returns>
|
|
/// An enumerator that allows foreach to be used to process the provider plugins in this
|
|
/// collection.
|
|
/// </returns>
|
|
private IEnumerable<IHashProvider> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary> Sets duplicate file collection. </summary>
|
|
/// <param name="duplicates"> The duplicates. </param>
|
|
private void SetDuplicateFileCollection(IEnumerable<DuplicateFileGroup> duplicates)
|
|
{
|
|
this.ScanResults.DuplicateFiles = duplicates;
|
|
}
|
|
|
|
/// <summary> Gets the duplicate files in this collection. </summary>
|
|
/// <returns>
|
|
/// An enumerator that allows foreach to be used to process the duplicate files in this
|
|
/// collection.
|
|
/// </returns>
|
|
private IEnumerable<DuplicateFileGroup> GetDuplicateFiles()
|
|
{
|
|
return this.Database.GetFileRecords().GroupBy(fr => fr.Hash).Where(g => g.Count() > 1).Select(g => new DuplicateFileGroup(g)).ToArray();
|
|
}
|
|
|
|
/// <summary> Resets the media preview. </summary>
|
|
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<FileRecord>();
|
|
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();
|
|
}
|
|
|
|
/// <summary> Event handler. Called by btnFilePicker for click events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Routed event information. </param>
|
|
private void btnFilePicker_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var fbd = new FolderBrowserDialog();
|
|
|
|
if(fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
|
{
|
|
txtScanLocation.Text = fbd.SelectedPath;
|
|
}
|
|
}
|
|
|
|
/// <summary> Event handler. Called by btnScan for click events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Routed event information. </param>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary> Event handler. Called by BtnClear for click events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Routed event information. </param>
|
|
private void BtnClear_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
this.Database.Clear();
|
|
ResetMediaPreview();
|
|
SetDuplicateFileCollection(Enumerable.Empty<DuplicateFileGroup>());
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary> Event handler. Called by Hyperlink for click events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Routed event information. </param>
|
|
private void Hyperlink_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
Hyperlink link = (Hyperlink)e.OriginalSource;
|
|
Process.Start(link.NavigateUri.AbsoluteUri);
|
|
}
|
|
|
|
/// <summary> Event handler. Called by MenuOptions for click events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Routed event information. </param>
|
|
private void MenuOptions_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var optionsWindow = new OptionsWindow();
|
|
|
|
optionsWindow.ShowDialog();
|
|
|
|
this.HashProviders = GetProviderPlugins();
|
|
}
|
|
|
|
/// <summary> Event handler. Called by MenuAbout for click events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Routed event information. </param>
|
|
private void MenuAbout_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var aboutWindow = new AboutWindow();
|
|
|
|
aboutWindow.Show();
|
|
}
|
|
|
|
/// <summary> Event handler. Called by ListBoxDupes for initialized events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Event information. </param>
|
|
private void ListBoxDupes_Initialized(object sender, EventArgs e)
|
|
{
|
|
new Thread(() =>
|
|
{
|
|
this.Dispatcher.Invoke(() => SetDuplicateFileCollection(GetDuplicateFiles()));
|
|
}).Start();
|
|
}
|
|
|
|
/// <summary> Event handler. Called by ListBoxDupes for selection changed events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Selection changed event information. </param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary> Event handler. Called by PerformDuplicateAction for click events. </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Routed event information. </param>
|
|
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<DuplicateFileGroup>();
|
|
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());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event handler. Called by DatagridFileList for selection changed events.
|
|
/// </summary>
|
|
/// <param name="sender"> Source of the event. </param>
|
|
/// <param name="e"> Selection changed event information. </param>
|
|
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
|
|
} |