mirror of
https://github.com/wagesj45/mdfinder.git
synced 2024-12-20 23:52:29 -06:00
Ugly Safety Checkin
Everything works. Mostly. It is kinda of flimsy so it needs to be cleaned up.
This commit is contained in:
parent
be05120bdb
commit
1de70724b0
17 changed files with 885 additions and 145 deletions
|
@ -36,6 +36,9 @@
|
|||
<setting name="Language" serializeAs="String">
|
||||
<value>en-US</value>
|
||||
</setting>
|
||||
<setting name="ArchiveFolder" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
</mdfinder.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
|
@ -63,8 +63,15 @@ namespace mdfinder
|
|||
/// <param name="hashProvider"> The hash provider. </param>
|
||||
public void InsertFileRecord(string path, long size, string hash, string hashProvider)
|
||||
{
|
||||
var fileRecord = new FileRecord() { Path = new Uri(path), Size = size, Hash = hash, HashProvider = hashProvider };
|
||||
this.FileRecordCollection.Insert(fileRecord);
|
||||
var fileRecord = new FileRecord(path, size, hash, hashProvider);
|
||||
this.FileRecordCollection.Upsert(fileRecord);
|
||||
}
|
||||
|
||||
/// <summary> Removes the file record described by ID. </summary>
|
||||
/// <param name="id"> The identifier. </param>
|
||||
public void RemoveFileRecord(string path)
|
||||
{
|
||||
this.FileRecordCollection.Delete(fr => fr.Id == path);
|
||||
}
|
||||
|
||||
/// <summary> Gets the file records in this collection. </summary>
|
||||
|
|
77
mdfinder/DuplicateFileGroup.cs
Normal file
77
mdfinder/DuplicateFileGroup.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mdfinder
|
||||
{
|
||||
public class DuplicateFileGroup : PropertyChangedAlerter
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary> Gets the hash. </summary>
|
||||
/// <value> The hash. </value>
|
||||
public string Hash { get; }
|
||||
|
||||
/// <summary> Gets the number of. </summary>
|
||||
/// <value> The count. </value>
|
||||
public int Count { get; }
|
||||
|
||||
/// <summary> Gets the total size of the file group. </summary>
|
||||
/// <value> The total number of size. </value>
|
||||
public long TotalSize { get; }
|
||||
|
||||
/// <summary> Gets the potential size saving. </summary>
|
||||
/// <value> The potential size saving. </value>
|
||||
public long PotentialSizeSaving { get; }
|
||||
|
||||
/// <summary> Gets or sets the file records. </summary>
|
||||
/// <value> The file records. </value>
|
||||
public List<FileRecord> FileRecords { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary> Constructor. </summary>
|
||||
/// <param name="fileRecords"> The file records. </param>
|
||||
/// <param name="savingsType"> (Optional) Type of the size savings to calculate. </param>
|
||||
public DuplicateFileGroup(IEnumerable<FileRecord> fileRecords, SavingsType savingsType = SavingsType.SaveBiggest)
|
||||
{
|
||||
this.FileRecords = new List<FileRecord>(fileRecords);
|
||||
|
||||
//Precalculate stats.
|
||||
this.Hash = this.FileRecords.Select(fr => fr.Hash).FirstOrDefault();
|
||||
this.Count = this.FileRecords.Count();
|
||||
|
||||
this.TotalSize = this.FileRecords.Sum(fr => fr.Size);
|
||||
|
||||
switch (savingsType)
|
||||
{
|
||||
case SavingsType.SaveBiggest:
|
||||
this.PotentialSizeSaving = this.FileRecords.OrderByDescending(fr => fr.Size).Skip(1).Sum(fr => fr.Size);
|
||||
break;
|
||||
case SavingsType.SaveSmallest:
|
||||
this.PotentialSizeSaving = this.FileRecords.OrderBy(fr => fr.Size).Skip(1).Sum(fr => fr.Size);
|
||||
break;
|
||||
case SavingsType.SaveMedian:
|
||||
//This is kind of hacky, but good enough for our purposes here. CLOSE ENOUGH
|
||||
var medianFileRecord = this.FileRecords.OrderBy(fr => fr.Size).ElementAt(this.Count / 2);
|
||||
this.PotentialSizeSaving = this.FileRecords.Except(new[] { medianFileRecord }).Sum(fr => fr.Size);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Values that represent the ways of saving space. </summary>
|
||||
public enum SavingsType
|
||||
{
|
||||
/// <summary> Saves the biggest, and presumably highest quality, file. </summary>
|
||||
SaveBiggest,
|
||||
/// <summary> Saves the smallest file. </summary>
|
||||
SaveSmallest,
|
||||
/// <summary> . </summary>
|
||||
SaveMedian
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,29 +6,149 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace mdfinder
|
||||
{
|
||||
public class FileRecord
|
||||
public class FileRecord : PropertyChangedAlerter
|
||||
{
|
||||
#region Members
|
||||
|
||||
/// <summary> The identifier. </summary>
|
||||
private string id;
|
||||
|
||||
/// <summary> Full pathname of the file. </summary>
|
||||
private Uri path;
|
||||
|
||||
/// <summary> The size. </summary>
|
||||
private long size;
|
||||
|
||||
/// <summary> The hash. </summary>
|
||||
private string hash;
|
||||
|
||||
/// <summary> The hash provider. </summary>
|
||||
private string hashProvider;
|
||||
|
||||
/// <summary> True to keep. </summary>
|
||||
private bool keep;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary> Gets or sets the identifier. </summary>
|
||||
/// <value> The identifier. </value>
|
||||
public Int64 Id { get; set; }
|
||||
public string Id
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.id = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets or sets the full pathname of the file. </summary>
|
||||
/// <value> The full pathname of the file. </value>
|
||||
public Uri Path { get; set; }
|
||||
public Uri Path
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.path;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.path = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets or sets the size. </summary>
|
||||
/// <value> The size. </value>
|
||||
public long Size { get; set; }
|
||||
public long Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.size;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.size = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets or sets the hash. </summary>
|
||||
/// <value> The hash. </value>
|
||||
public string Hash { get; set; }
|
||||
public string Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.hash;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.hash = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets or sets the hash provider. </summary>
|
||||
/// <value> The hash provider. </value>
|
||||
public string HashProvider { get; set; }
|
||||
public string HashProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.hashProvider;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.hashProvider = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets or sets a value indicating whether to keep the file when processing duplicates. </summary>
|
||||
/// <value> True if keep, false if not. </value>
|
||||
public bool Keep
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.keep;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.keep = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public FileRecord()
|
||||
{
|
||||
this.Id = string.Empty;
|
||||
this.Path = default(Uri);
|
||||
this.Size = 0;
|
||||
this.Hash = string.Empty;
|
||||
this.HashProvider = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary> Constructor. </summary>
|
||||
/// <param name="path"> Full pathname of the file. </param>
|
||||
/// <param name="size"> The size. </param>
|
||||
/// <param name="hash"> The hash. </param>
|
||||
/// <param name="hashProvider"> The hash provider. </param>
|
||||
public FileRecord(string path, long size, string hash, string hashProvider)
|
||||
{
|
||||
this.Id = path;
|
||||
this.Path = new Uri(path);
|
||||
this.Size = size;
|
||||
this.Hash = hash;
|
||||
this.HashProvider = hashProvider;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
@ -170,4 +170,49 @@
|
|||
</Path>
|
||||
</Viewbox>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="LargeFileIcon">
|
||||
<Viewbox>
|
||||
<Path Fill="#000000">
|
||||
<Path.Data>
|
||||
<PathGeometry Figures="M22 13v-13h-20v24h8.409c4.857 0 3.335-8 3.335-8 3.009.745 8.256.419 8.256-3zm-4-7h-12v-1h12v1zm0 3h-12v-1h12v1zm0 3h-12v-1h12v1zm-2.091 6.223c2.047.478 4.805-.279 6.091-1.179-1.494 1.998-5.23 5.708-7.432 6.881 1.156-1.168 1.563-4.234 1.341-5.702z" FillRule="NonZero"/>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
</Viewbox>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="SmallFileIcon">
|
||||
<Viewbox>
|
||||
<Path Fill="#000000">
|
||||
<Path.Data>
|
||||
<PathGeometry Figures="M4 22v-20h16v11.543c0 4.107-6 2.457-6 2.457s1.518 6-2.638 6h-7.362zm18-7.614v-14.386h-20v24h10.189c3.163 0 9.811-7.223 9.811-9.614zm-5-1.386h-10v-1h10v1zm0-4h-10v1h10v-1zm0-3h-10v1h10v-1z" FillRule="NonZero"/>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
</Viewbox>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="SelectedFileIcon">
|
||||
<Viewbox>
|
||||
<Path Fill="#000000">
|
||||
<Path.Data>
|
||||
<PathGeometry Figures="M18.764 17.385l2.66 5.423-2.441 1.192-2.675-5.474-3.308 2.863v-12.389l10 7.675-4.236.71zm2.236-7.385h2v-4h-2v4zm0 2.619l2 1.535v-2.154h-2v.619zm-10 8.77v-1.389h-4v2h4v-.611zm-8-3.389h-2v4h4v-2h-2v-2zm-2-14h2v-2h2v-2h-4v4zm2 8h-2v4h2v-4zm8-12h-4v2h4v-2zm6 0h-4v2h4v-2zm4 4h2v-4h-4v2h2v2zm-18 2h-2v4h2v-4z" FillRule="NonZero"/>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
</Viewbox>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="OpenFileIcon">
|
||||
<Viewbox>
|
||||
<Path Fill="#000000">
|
||||
<Path.Data>
|
||||
<PathGeometry Figures="M6 17c2.269-9.881 11-11.667 11-11.667v-3.333l7 6.637-7 6.696v-3.333s-6.17-.171-11 5zm12 .145v2.855h-16v-12h6.598c.768-.787 1.561-1.449 2.339-2h-10.937v16h20v-6.769l-2 1.914z" FillRule="NonZero"/>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
</Viewbox>
|
||||
</ControlTemplate>
|
||||
<ControlTemplate x:Key="NoPreviewIcon">
|
||||
<Viewbox>
|
||||
<Path Fill="#000000">
|
||||
<Path.Data>
|
||||
<PathGeometry Figures="M19.604 2.562l-3.346 3.137c-1.27-.428-2.686-.699-4.243-.699-7.569 0-12.015 6.551-12.015 6.551s1.928 2.951 5.146 5.138l-2.911 2.909 1.414 1.414 17.37-17.035-1.415-1.415zm-6.016 5.779c-3.288-1.453-6.681 1.908-5.265 5.206l-1.726 1.707c-1.814-1.16-3.225-2.65-4.06-3.66 1.493-1.648 4.817-4.594 9.478-4.594.927 0 1.796.119 2.61.315l-1.037 1.026zm-2.883 7.431l5.09-4.993c1.017 3.111-2.003 6.067-5.09 4.993zm13.295-4.221s-4.252 7.449-11.985 7.449c-1.379 0-2.662-.291-3.851-.737l1.614-1.583c.715.193 1.458.32 2.237.32 4.791 0 8.104-3.527 9.504-5.364-.729-.822-1.956-1.99-3.587-2.952l1.489-1.46c2.982 1.9 4.579 4.327 4.579 4.327z" FillRule="NonZero"/>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
</Viewbox>
|
||||
</ControlTemplate>
|
||||
</ResourceDictionary>
|
125
mdfinder/Localization/Localization.Designer.cs
generated
125
mdfinder/Localization/Localization.Designer.cs
generated
|
@ -106,7 +106,16 @@ namespace mdfinder.Localization {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Actions.
|
||||
/// Looks up a localized string similar to Archive Remaining Files.
|
||||
/// </summary>
|
||||
public static string ActionArchiveLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ActionArchiveLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Duplicate Actions.
|
||||
/// </summary>
|
||||
public static string ActionBarLabel {
|
||||
get {
|
||||
|
@ -114,6 +123,42 @@ namespace mdfinder.Localization {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Keep Largest.
|
||||
/// </summary>
|
||||
public static string ActionLargestLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ActionLargestLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to KeepSelected.
|
||||
/// </summary>
|
||||
public static string ActionSelectedLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ActionSelectedLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Keep Smallest.
|
||||
/// </summary>
|
||||
public static string ActionSmallestLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ActionSmallestLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Archive Folder.
|
||||
/// </summary>
|
||||
public static string ArchiveFolderLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ArchiveFolderLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Would you like to visit mdfinder to update to the latest version?.
|
||||
/// </summary>
|
||||
|
@ -159,6 +204,15 @@ namespace mdfinder.Localization {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Keep File.
|
||||
/// </summary>
|
||||
public static string ColumnHeaderKeep {
|
||||
get {
|
||||
return ResourceManager.GetString("ColumnHeaderKeep", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hash.
|
||||
/// </summary>
|
||||
|
@ -268,29 +322,11 @@ namespace mdfinder.Localization {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Filters.
|
||||
/// Looks up a localized string similar to Files.
|
||||
/// </summary>
|
||||
public static string FilterBarLabel {
|
||||
public static string FilesLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterBarLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Duplicates.
|
||||
/// </summary>
|
||||
public static string FilterDuplicatesLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterDuplicatesLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show All.
|
||||
/// </summary>
|
||||
public static string FilterShowAllLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterShowAllLabel", resourceCulture);
|
||||
return ResourceManager.GetString("FilesLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,6 +366,15 @@ namespace mdfinder.Localization {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No Preview Available.
|
||||
/// </summary>
|
||||
public static string NoPreviewLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("NoPreviewLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OK.
|
||||
/// </summary>
|
||||
|
@ -384,6 +429,15 @@ namespace mdfinder.Localization {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Potential Space Savings.
|
||||
/// </summary>
|
||||
public static string PotentialSpaceSavingsLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("PotentialSpaceSavingsLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Provider Location.
|
||||
/// </summary>
|
||||
|
@ -429,6 +483,15 @@ namespace mdfinder.Localization {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Scan Location.
|
||||
/// </summary>
|
||||
public static string ScanningLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ScanningLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Scan the selected path.
|
||||
/// </summary>
|
||||
|
@ -438,6 +501,24 @@ namespace mdfinder.Localization {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Size:.
|
||||
/// </summary>
|
||||
public static string SizeLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("SizeLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Size On Disk:.
|
||||
/// </summary>
|
||||
public static string SizeOnDiskLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("SizeOnDiskLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Skip Empty Files.
|
||||
/// </summary>
|
||||
|
|
|
@ -132,8 +132,17 @@
|
|||
<data name="AboutWindowTitle" xml:space="preserve">
|
||||
<value>About mdfinder</value>
|
||||
</data>
|
||||
<data name="ActionArchiveLabel" xml:space="preserve">
|
||||
<value>Archive Remaining Files</value>
|
||||
</data>
|
||||
<data name="ActionBarLabel" xml:space="preserve">
|
||||
<value>Actions</value>
|
||||
<value>Duplicate Actions</value>
|
||||
</data>
|
||||
<data name="ActionLargestLabel" xml:space="preserve">
|
||||
<value>Keep Largest</value>
|
||||
</data>
|
||||
<data name="ActionSmallestLabel" xml:space="preserve">
|
||||
<value>Keep Smallest</value>
|
||||
</data>
|
||||
<data name="BehindVersionQuestion" xml:space="preserve">
|
||||
<value>Would you like to visit mdfinder to update to the latest version?</value>
|
||||
|
@ -186,14 +195,8 @@
|
|||
<data name="FileMenu" xml:space="preserve">
|
||||
<value>File</value>
|
||||
</data>
|
||||
<data name="FilterBarLabel" xml:space="preserve">
|
||||
<value>Filters</value>
|
||||
</data>
|
||||
<data name="FilterDuplicatesLabel" xml:space="preserve">
|
||||
<value>Duplicates</value>
|
||||
</data>
|
||||
<data name="FilterShowAllLabel" xml:space="preserve">
|
||||
<value>Show All</value>
|
||||
<data name="FilesLabel" xml:space="preserve">
|
||||
<value>Files</value>
|
||||
</data>
|
||||
<data name="HelpMenu" xml:space="preserve">
|
||||
<value>Help</value>
|
||||
|
@ -240,9 +243,15 @@
|
|||
<data name="ScanLocationLabel" xml:space="preserve">
|
||||
<value>Location to Scan</value>
|
||||
</data>
|
||||
<data name="ScanningLabel" xml:space="preserve">
|
||||
<value>Scan Location</value>
|
||||
</data>
|
||||
<data name="ScanTooltip" xml:space="preserve">
|
||||
<value>Scan the selected path</value>
|
||||
</data>
|
||||
<data name="SizeLabel" xml:space="preserve">
|
||||
<value>Size:</value>
|
||||
</data>
|
||||
<data name="SkipEmptyFilesLabel" xml:space="preserve">
|
||||
<value>Skip Empty Files</value>
|
||||
</data>
|
||||
|
@ -255,4 +264,22 @@
|
|||
<data name="VersionLabel" xml:space="preserve">
|
||||
<value>Version</value>
|
||||
</data>
|
||||
<data name="ActionSelectedLabel" xml:space="preserve">
|
||||
<value>KeepSelected</value>
|
||||
</data>
|
||||
<data name="ArchiveFolderLabel" xml:space="preserve">
|
||||
<value>Archive Folder</value>
|
||||
</data>
|
||||
<data name="ColumnHeaderKeep" xml:space="preserve">
|
||||
<value>Keep File</value>
|
||||
</data>
|
||||
<data name="NoPreviewLabel" xml:space="preserve">
|
||||
<value>No Preview Available</value>
|
||||
</data>
|
||||
<data name="PotentialSpaceSavingsLabel" xml:space="preserve">
|
||||
<value>Potential Space Savings</value>
|
||||
</data>
|
||||
<data name="SizeOnDiskLabel" xml:space="preserve">
|
||||
<value>Size On Disk:</value>
|
||||
</data>
|
||||
</root>
|
|
@ -3,10 +3,11 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:gu="https://github.com/JohanLarsson/Gu.Wpf.Media"
|
||||
xmlns:mdfinder="clr-namespace:mdfinder"
|
||||
xmlns:loc="clr-namespace:mdfinder.Localization"
|
||||
mc:Ignorable="d"
|
||||
Title="{x:Static loc:Localization.Title}" Height="450" Width="800">
|
||||
Title="{x:Static loc:Localization.Title}" Height="520.293" Width="814.505">
|
||||
<Window.Resources>
|
||||
<mdfinder:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||
<mdfinder:BoolVisibilityConverter x:Key="BoolVisibilityConverter" />
|
||||
|
@ -33,7 +34,7 @@
|
|||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Name="menuAbout" Header="{x:Static loc:Localization.AboutMenu}" Click="MenuAbout_Click">
|
||||
|
||||
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
@ -62,7 +63,7 @@
|
|||
<Button Grid.Column="2" MinWidth="25" Name="btnFilePicker" Click="btnFilePicker_Click">...</Button>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="1" Header="{x:Static loc:Localization.ActionBarLabel}">
|
||||
<GroupBox Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="1" Header="{x:Static loc:Localization.ScanningLabel}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
|
@ -85,80 +86,168 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" Header="{x:Static loc:Localization.FilterBarLabel}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Row="0" Grid.Column="0" Name="btnFilterShowAll" ToolTip="{x:Static loc:Localization.ScanTooltip}" IsEnabled="{Binding Scanner.IsScanning, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InverseBoolConverter}}" Click="BtnFilterShowAll_Click" >
|
||||
<GroupBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" Header="{x:Static loc:Localization.ActionBarLabel}">
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="1">
|
||||
<Button Grid.Row="0" Grid.Column="1" Name="btnKeepLargest" ToolTip="{x:Static loc:Localization.ScanTooltip}" IsEnabled="{Binding Scanner.IsScanning, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InverseBoolConverter}}" Tag="largest" Click="PerformDuplicateAction_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource ShowAllIcon}" />
|
||||
<Label Content="{x:Static loc:Localization.FilterShowAllLabel}" />
|
||||
<ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource LargeFileIcon}" />
|
||||
<Label Content="{x:Static loc:Localization.ActionLargestLabel}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="0" Grid.Column="1" Name="btnFilterDuplicates" ToolTip="{x:Static loc:Localization.ScanTooltip}" IsEnabled="{Binding Scanner.IsScanning, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InverseBoolConverter}}" Click="BtnFilterDuplicates_Click" >
|
||||
<Button Grid.Row="0" Grid.Column="1" Name="btnKeepSmallest" ToolTip="{x:Static loc:Localization.ScanTooltip}" IsEnabled="{Binding Scanner.IsScanning, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InverseBoolConverter}}" Tag="smallest" Click="PerformDuplicateAction_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource DuplicateIcon}" />
|
||||
<Label Content="{x:Static loc:Localization.FilterDuplicatesLabel}" />
|
||||
<ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource SmallFileIcon}" />
|
||||
<Label Content="{x:Static loc:Localization.ActionSmallestLabel}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Button Grid.Row="0" Grid.Column="1" Name="btnKeepSelected" ToolTip="{x:Static loc:Localization.ScanTooltip}" IsEnabled="{Binding Scanner.IsScanning, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InverseBoolConverter}}" Tag="selected" Click="PerformDuplicateAction_Click">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource SelectedFileIcon}" />
|
||||
<Label Content="{x:Static loc:Localization.ActionSelectedLabel}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<CheckBox Name="checkboxArchiveRemainingFiles" VerticalAlignment="Center" IsChecked="True" Content="{x:Static loc:Localization.ActionArchiveLabel}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<DataGrid x:Name="datagridFileRecords" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Initialized="DatagridFileRecords_Initialized" IsReadOnly="True" AutoGenerateColumns="False">
|
||||
<DataGrid.Resources>
|
||||
<Style TargetType="Hyperlink">
|
||||
<EventSetter Event="Click" Handler="Hyperlink_Click"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type DataGridCell}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="DataGridCell.IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="Black" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridHyperlinkColumn Header="{x:Static loc:Localization.ColumnHeadingPath}" Binding="{Binding Path}" ContentBinding="{Binding Path, Converter={StaticResource URIConverter}}" Width="Auto" IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="{x:Static loc:Localization.ColumnHeadingSize}" Binding="{Binding Size, Converter={StaticResource SizeConverter}}" />
|
||||
<DataGridTextColumn Header="{x:Static loc:Localization.ColumnHeadingHash}" Binding="{Binding Hash}" />
|
||||
<DataGridTextColumn Header="{x:Static loc:Localization.ColumnHeadingHashProvider}" Binding="{Binding HashProvider}" />
|
||||
</DataGrid.Columns>
|
||||
<DataGrid.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Path=Hash}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</GroupStyle.HeaderTemplate>
|
||||
<GroupStyle.ContainerStyle>
|
||||
<Style TargetType="{x:Type GroupItem}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type GroupItem}">
|
||||
<Expander IsExpanded="True">
|
||||
<Expander.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Path=Name}" />
|
||||
<TextBlock Text="{Binding Path=ItemCount}"/>
|
||||
<TextBlock Text="{x:Static loc:Localization.ItemsLabel}"/>
|
||||
</StackPanel>
|
||||
</Expander.Header>
|
||||
<ItemsPresenter />
|
||||
</Expander>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</GroupStyle.ContainerStyle>
|
||||
</GroupStyle>
|
||||
</DataGrid.GroupStyle>
|
||||
</DataGrid>
|
||||
<ListBox x:Name="listBoxDupes" Grid.Row="2" Grid.Column="0" Initialized="ListBoxDupes_Initialized" ItemsSource="{Binding ScanResults.DuplicateFiles, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}}" SelectionChanged="ListBoxDupes_SelectionChanged">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label Content="{Binding Count}" />
|
||||
<Label Content="{x:Static loc:Localization.FilesLabel}" />
|
||||
<Label Content="|" />
|
||||
<Label Content="{x:Static loc:Localization.SizeLabel}" />
|
||||
<Label Content="{Binding TotalSize, Converter={StaticResource SizeConverter}}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<Grid x:Name="gridDuplicates" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding SelectedItem, ElementName=listBoxDupe}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label Content="{x:Static loc:Localization.SizeOnDiskLabel}" />
|
||||
<Label Content="{Binding ScanResults.SelectedDuplicateFileGroup.TotalSize, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, Converter={StaticResource SizeConverter}}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label Content="{x:Static loc:Localization.PotentialSpaceSavingsLabel}" />
|
||||
<Label Content="{Binding ScanResults.SelectedDuplicateFileGroup.PotentialSizeSaving, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, Converter={StaticResource SizeConverter}}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<DataGrid Name="datagridFileList" Grid.Row="1" Grid.Column="0" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" ItemsSource="{Binding ScanResults.SelectedDuplicateFileGroup.FileRecords, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}}" SelectionChanged="DatagridFileList_SelectionChanged">
|
||||
<DataGrid.Resources>
|
||||
<Style TargetType="Hyperlink">
|
||||
<EventSetter Event="Click" Handler="Hyperlink_Click"/>
|
||||
<Setter Property="Foreground" Value="DarkGreen"></Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource= {RelativeSource AncestorType={x:Type DataGridRow}}}" Value="True">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock>
|
||||
<Hyperlink Click="Hyperlink_Click" NavigateUri="{Binding Path}">
|
||||
<ContentControl Grid.Column="0" Template="{StaticResource OpenFileIcon}" Width="16" Margin="0,0,4,0" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn IsReadOnly="True" Header="{x:Static loc:Localization.ColumnHeadingPath}" Binding="{Binding Path, Converter={StaticResource URIConverter}}" />
|
||||
<DataGridTextColumn IsReadOnly="True" Header="{x:Static loc:Localization.ColumnHeadingSize}" Binding="{Binding Size, Converter={StaticResource SizeConverter}}"/>
|
||||
<DataGridTemplateColumn Header="{x:Static loc:Localization.ColumnHeaderKeep}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox IsChecked="{Binding Keep, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<Grid Name="gridPreview" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
|
||||
<Grid Name="mediaPreviewContainer" Visibility="Hidden">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<gu:MediaElementWrapper Name="mediaPreview" Grid.Row="0" Stretch="Uniform" />
|
||||
<Grid Name="mediaBarPreview" Grid.Row="1" VerticalAlignment="Bottom" Background="#66666666">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Slider x:Name="ProgressSlider" Grid.Row="0" gu:Drag.PauseWhileDragging="{Binding ElementName=mediaPreview}" Maximum="{Binding ElementName=mediaPreview, Path=Length, Converter={x:Static gu:NullableTimeSpanToSecondsConverter.Default}}" Minimum="0" Style="{StaticResource {x:Static gu:Styles.ProgressSliderStyleKey}}" Value="{Binding ElementName=mediaPreview, Path=Position, Converter={x:Static gu:NullableTimeSpanToSecondsConverter.Default}}" />
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.Resources>
|
||||
<Style BasedOn="{StaticResource {x:Static gu:Styles.PlayerButtonBaseStyleKey}}" TargetType="{x:Type Button}" />
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ToggleButton x:Name="PlayPauseButton" Grid.Column="0" IsChecked="{Binding ElementName=mediaPreview, Path=IsPlaying}" IsEnabled="{Binding ElementName=mediaPreview, Path=HasMedia}">
|
||||
<ToggleButton.Style>
|
||||
<Style BasedOn="{StaticResource {x:Static gu:Styles.PlayerButtonBaseStyleKey}}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="gu:Icon.Geometry" Value="{StaticResource {x:Static gu:Geometries.PauseGeometryKey}}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsChecked" Value="False">
|
||||
<Setter Property="gu:Icon.Geometry" Value="{StaticResource {x:Static gu:Geometries.PlayGeometryKey}}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ToggleButton.Style>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton x:Name="ToggleMutedButton" Grid.Column="1" IsChecked="{Binding ElementName=mediaPreview, Path=IsMuted}" IsEnabled="{Binding ElementName=mediaPreview, Path=HasMedia}">
|
||||
<ToggleButton.Style>
|
||||
<Style BasedOn="{StaticResource {x:Static gu:Styles.PlayerButtonBaseStyleKey}}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="gu:Icon.Geometry" Value="{StaticResource {x:Static gu:Geometries.UnMuteGeometryKey}}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsChecked" Value="False">
|
||||
<Setter Property="gu:Icon.Geometry" Value="{StaticResource {x:Static gu:Geometries.MuteGeometryKey}}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ToggleButton.Style>
|
||||
</ToggleButton>
|
||||
|
||||
<TextBlock x:Name="ProgressTextBlock" Grid.Column="2" VerticalAlignment="Center" Foreground="{Binding ElementName=ToggleMutedButton, Path=Foreground}" Opacity="{Binding ElementName=ToggleMutedButton, Path=Opacity}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0} / {1}">
|
||||
<Binding Converter="{x:Static gu:TimeSpanToStringConverter.Default}" ElementName="MediaElement" Path="Position" />
|
||||
<Binding Converter="{x:Static gu:TimeSpanToStringConverter.Default}" ElementName="MediaElement" Path="Length" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Image Name="imagePreview" Visibility="Hidden" />
|
||||
<TextBlock Name="textPreview" Visibility="Hidden" />
|
||||
<StackPanel Name="stackNoPreview" Orientation="Vertical" VerticalAlignment="Center">
|
||||
<ContentControl Grid.Column="0" Template="{StaticResource NoPreviewIcon}" Width="64" Margin="0,0,4,0" />
|
||||
<Label HorizontalAlignment="Center" Content="{x:Static loc:Localization.NoPreviewLabel}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Label x:Name="txtProgressLabel" Grid.Row="3" Grid.ColumnSpan="4" Panel.ZIndex="1" />
|
||||
<ProgressBar Grid.Row="3" Grid.ColumnSpan="4" Name="progressBar" Minimum="0" Height="16"/>
|
||||
</Grid>
|
||||
|
|
|
@ -18,6 +18,7 @@ using mdfinder.hashprovider;
|
|||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace mdfinder
|
||||
{
|
||||
|
@ -26,6 +27,16 @@ namespace mdfinder
|
|||
/// </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>
|
||||
|
@ -44,6 +55,10 @@ namespace mdfinder
|
|||
/// <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
|
||||
|
||||
#region Constructors
|
||||
|
@ -55,6 +70,7 @@ namespace mdfinder
|
|||
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) =>
|
||||
|
@ -87,17 +103,52 @@ namespace mdfinder
|
|||
/// </returns>
|
||||
private IEnumerable<IHashProvider> GetProviderPlugins()
|
||||
{
|
||||
var directory = new DirectoryInfo(Properties.Settings.Default.ProviderFolder);
|
||||
foreach (var pluginFile in directory.GetFiles("*.dll"))
|
||||
if (!string.IsNullOrWhiteSpace(Properties.Settings.Default.ProviderFolder) && Directory.Exists(Properties.Settings.Default.ProviderFolder))
|
||||
{
|
||||
var assembly = Assembly.LoadFrom(pluginFile.FullName);
|
||||
foreach (var type in assembly.GetTypes().Where(t => t.GetInterface("IHashProvider") != null))
|
||||
var directory = new DirectoryInfo(Properties.Settings.Default.ProviderFolder);
|
||||
foreach (var pluginFile in directory.GetFiles("*.dll"))
|
||||
{
|
||||
yield return Activator.CreateInstance(type) as IHashProvider;
|
||||
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.imagePreview.Source = null;
|
||||
this.textPreview.Text = string.Empty;
|
||||
|
||||
this.mediaPreviewContainer.Visibility = Visibility.Hidden;
|
||||
this.imagePreview.Visibility = Visibility.Hidden;
|
||||
this.textPreview.Visibility = Visibility.Hidden;
|
||||
this.stackNoPreview.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
@ -117,49 +168,27 @@ namespace mdfinder
|
|||
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(() => datagridFileRecords.ItemsSource = this.Database.GetFileRecords());
|
||||
this.Dispatcher.Invoke(() => progressBar.Value = 0);
|
||||
this.Dispatcher.Invoke(() => SetDuplicateFileCollection(GetDuplicateFiles()));
|
||||
}).Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Event handler. Called by DatagridFileRecords for initialized events. </summary>
|
||||
/// <param name="sender"> Source of the event. </param>
|
||||
/// <param name="e"> Event information. </param>
|
||||
private void DatagridFileRecords_Initialized(object sender, EventArgs e)
|
||||
{
|
||||
this.datagridFileRecords.ItemsSource = this.Database.GetFileRecords();
|
||||
}
|
||||
|
||||
/// <summary> Event handler. Called by BtnFilterDuplicates for click events. </summary>
|
||||
/// <param name="sender"> Source of the event. </param>
|
||||
/// <param name="e"> Routed event information. </param>
|
||||
private void BtnFilterDuplicates_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.datagridFileRecords.ItemsSource = new ListCollectionView(this.Database.GetFileRecords().GroupBy(fr => fr.Hash).Where(g => g.Count() > 1).SelectMany(g => g).ToList());
|
||||
((ListCollectionView)this.datagridFileRecords.ItemsSource).GroupDescriptions.Add(new PropertyGroupDescription("Hash"));
|
||||
}
|
||||
|
||||
/// <summary> Event handler. Called by BtnFilterShowAll for click events. </summary>
|
||||
/// <param name="sender"> Source of the event. </param>
|
||||
/// <param name="e"> Routed event information. </param>
|
||||
private void BtnFilterShowAll_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.datagridFileRecords.ItemsSource = this.Database.GetFileRecords();
|
||||
}
|
||||
|
||||
/// <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();
|
||||
this.datagridFileRecords.ItemsSource = Enumerable.Empty<FileRecord>();
|
||||
ResetMediaPreview();
|
||||
SetDuplicateFileCollection(Enumerable.Empty<DuplicateFileGroup>());
|
||||
}
|
||||
|
||||
/// <summary> Event handler. Called by Hyperlink for click events. </summary>
|
||||
|
@ -183,14 +212,153 @@ namespace mdfinder
|
|||
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();
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <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.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 actionableFiles = Enumerable.Empty<FileRecord>();
|
||||
var archive = this.checkboxArchiveRemainingFiles.IsChecked ?? false;
|
||||
|
||||
ResetMediaPreview();
|
||||
|
||||
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)
|
||||
{
|
||||
zipFile = ZipFile.Open(System.IO.Path.Combine(Properties.Settings.Default.ArchiveFolder, duplicateFileGroup.Hash + ".zip"), ZipArchiveMode.Update);
|
||||
}
|
||||
|
||||
foreach (var file in actionableFiles)
|
||||
{
|
||||
if(archive && zipFile != null)
|
||||
{
|
||||
zipFile.CreateEntryFromFile(file.Path.LocalPath, System.IO.Path.GetFileName(file.Path.LocalPath));
|
||||
}
|
||||
|
||||
File.Delete(file.Path.LocalPath);
|
||||
|
||||
this.Database.RemoveFileRecord(file.Id);
|
||||
}
|
||||
|
||||
if(zipFile != null)
|
||||
{
|
||||
zipFile.Dispose();
|
||||
}
|
||||
|
||||
SetDuplicateFileCollection(GetDuplicateFiles());
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var extension = System.IO.Path.GetExtension(fileRecord.Path.LocalPath).ToUpper();
|
||||
|
||||
if (MEDIA_EXTENTIONS.Contains(extension))
|
||||
{
|
||||
this.mediaPreview.Source = fileRecord.Path;
|
||||
this.imagePreview.Source = null;
|
||||
this.textPreview.Text = string.Empty;
|
||||
|
||||
this.mediaPreviewContainer.Visibility = Visibility.Visible;
|
||||
this.imagePreview.Visibility = Visibility.Hidden;
|
||||
this.textPreview.Visibility = Visibility.Hidden;
|
||||
this.stackNoPreview.Visibility = Visibility.Hidden;
|
||||
}
|
||||
else if (IMAGE_EXTENTIONS.Contains(extension))
|
||||
{
|
||||
this.mediaPreview.Source = null;
|
||||
this.imagePreview.Source = new BitmapImage(fileRecord.Path);
|
||||
this.textPreview.Text = string.Empty;
|
||||
|
||||
this.mediaPreviewContainer.Visibility = Visibility.Hidden;
|
||||
this.imagePreview.Visibility = Visibility.Visible;
|
||||
this.textPreview.Visibility = Visibility.Hidden;
|
||||
this.stackNoPreview.Visibility = Visibility.Hidden;
|
||||
}
|
||||
else if (TEXT_EXTENTIONS.Contains(extension))
|
||||
{
|
||||
this.mediaPreview.Source = null;
|
||||
this.imagePreview.Source = null;
|
||||
this.textPreview.Text = File.ReadAllText(fileRecord.Path.LocalPath);
|
||||
|
||||
this.mediaPreviewContainer.Visibility = Visibility.Hidden;
|
||||
this.imagePreview.Visibility = Visibility.Hidden;
|
||||
this.textPreview.Visibility = Visibility.Visible;
|
||||
this.stackNoPreview.Visibility = Visibility.Hidden;
|
||||
}
|
||||
else //Can't preivew.
|
||||
{
|
||||
ResetMediaPreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -26,16 +26,29 @@
|
|||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="77.126" />
|
||||
<ColumnDefinition Width="25.874"/>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentControl Grid.Column="0" Template="{StaticResource FolderIcon}" Width="16" Margin="0,0,4,0" />
|
||||
<Label Grid.Column="1" Content="{x:Static loc:Localization.ProviderFolderLabel}" Grid.ColumnSpan="2" />
|
||||
<TextBox x:Name="txtProviderLocation" Grid.Column="3" VerticalAlignment="Center" Text="{Binding Source={x:Static settings:Settings.Default}, Path=ProviderFolder, Mode=TwoWay}" Margin="0,4" />
|
||||
<TextBox x:Name="txtProviderLocation" Grid.Column="3" VerticalAlignment="Center" Text="{Binding Source={x:Static settings:Settings.Default}, Path=ProviderFolder, Mode=OneWay}" Margin="0,4" />
|
||||
<Button x:Name="btnProviderLocationDirectory" Grid.Column="4" MinWidth="25" Content="..." Click="BtnProviderLocationDirectory_Click" />
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentControl Grid.Column="0" Template="{StaticResource FolderIcon}" Width="16" Margin="0,0,4,0" />
|
||||
<Label Grid.Column="1" Content="{x:Static loc:Localization.ArchiveFolderLabel}" Grid.ColumnSpan="2" />
|
||||
<TextBox x:Name="txtArchiveLocation" Grid.Column="3" VerticalAlignment="Center" Text="{Binding Source={x:Static settings:Settings.Default}, Path=ArchiveFolder, Mode=OneWay}" Margin="0,4" />
|
||||
<Button x:Name="btnArchiveLocationDirectory" Grid.Column="4" MinWidth="25" Content="..." Click="BtnArchiveLocationDirectory_Click" />
|
||||
</Grid>
|
||||
<Separator Margin="0,10" />
|
||||
<Button Name="btnSave" MaxWidth="45" Content="{x:Static loc:Localization.SaveLabel}" Click="BtnSave_Click" />
|
||||
</StackPanel>
|
||||
|
|
|
@ -63,5 +63,18 @@ namespace mdfinder
|
|||
Properties.Settings.Default.ProviderFolder = fbd.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Event handler. Called by BtnArchiveLocationDirectory for click events. </summary>
|
||||
/// <param name="sender"> Source of the event. </param>
|
||||
/// <param name="e"> Routed event information. </param>
|
||||
private void BtnArchiveLocationDirectory_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var fbd = new FolderBrowserDialog();
|
||||
|
||||
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
||||
{
|
||||
Properties.Settings.Default.ArchiveFolder = fbd.SelectedPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
mdfinder/Properties/Settings.Designer.cs
generated
12
mdfinder/Properties/Settings.Designer.cs
generated
|
@ -70,5 +70,17 @@ namespace mdfinder.Properties {
|
|||
this["Language"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string ArchiveFolder {
|
||||
get {
|
||||
return ((string)(this["ArchiveFolder"]));
|
||||
}
|
||||
set {
|
||||
this["ArchiveFolder"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,5 +14,8 @@
|
|||
<Setting Name="Language" Type="System.Globalization.CultureInfo" Scope="User">
|
||||
<Value Profile="(Default)">en-US</Value>
|
||||
</Setting>
|
||||
<Setting Name="ArchiveFolder" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
65
mdfinder/ScanResults.cs
Normal file
65
mdfinder/ScanResults.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mdfinder
|
||||
{
|
||||
/// <summary> A scan results model providing bindable properties. </summary>
|
||||
public class ScanResults : PropertyChangedAlerter
|
||||
{
|
||||
#region Members
|
||||
|
||||
/// <summary> The duplicate files. </summary>
|
||||
private IEnumerable<DuplicateFileGroup> duplicateFiles;
|
||||
/// <summary> The selected duplicate file group. </summary>
|
||||
private DuplicateFileGroup selectedDuplicateFileGroup;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary> Gets or sets the duplicate files. </summary>
|
||||
/// <value> The duplicate files. </value>
|
||||
public IEnumerable<DuplicateFileGroup> DuplicateFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.duplicateFiles;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.duplicateFiles = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets or sets the selected duplicate file group. </summary>
|
||||
/// <value> The selected duplicate file group. </value>
|
||||
public DuplicateFileGroup SelectedDuplicateFileGroup
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.selectedDuplicateFileGroup;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.selectedDuplicateFileGroup = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ScanResults()
|
||||
{
|
||||
this.duplicateFiles = Enumerable.Empty<DuplicateFileGroup>();
|
||||
this.selectedDuplicateFileGroup = new DuplicateFileGroup(Enumerable.Empty<FileRecord>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -76,8 +76,15 @@ namespace mdfinder
|
|||
|
||||
public void Scan(string path)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
this.IsScanning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.Processed = 0;
|
||||
this.Total = 0;
|
||||
this.IsScanning = true;
|
||||
|
||||
var scanPath = new DirectoryInfo(path);
|
||||
if (scanPath.Exists)
|
||||
|
@ -85,6 +92,8 @@ namespace mdfinder
|
|||
Discover(scanPath);
|
||||
Scan(scanPath);
|
||||
}
|
||||
|
||||
this.IsScanning = false;
|
||||
}
|
||||
|
||||
private void Discover(DirectoryInfo directory)
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
<Reference Include="csmic, Version=1.1.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\csmic.1.1.4\lib\net40\csmic.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Gu.Wpf.Media, Version=0.5.0.2, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Gu.Wpf.Media.0.5.0.2\lib\net45\Gu.Wpf.Media.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="LiteDB, Version=4.1.4.0, Culture=neutral, PublicKeyToken=4ee40123013c9f27, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\LiteDB.4.1.4\lib\net40\LiteDB.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -49,6 +52,8 @@
|
|||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
|
@ -73,9 +78,11 @@
|
|||
<Compile Include="AboutWindow.xaml.cs">
|
||||
<DependentUpon>AboutWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="DuplicateFileGroup.cs" />
|
||||
<Compile Include="OptionsWindow.xaml.cs">
|
||||
<DependentUpon>OptionsWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ScanResults.cs" />
|
||||
<Compile Include="SizeConverter.cs" />
|
||||
<Compile Include="URIConverter.cs" />
|
||||
<Page Include="AboutWindow.xaml">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="csmic" version="1.1.4" targetFramework="net461" />
|
||||
<package id="Gu.Wpf.Media" version="0.5.0.2" targetFramework="net461" />
|
||||
<package id="LiteDB" version="4.1.4" targetFramework="net461" />
|
||||
<package id="Octokit" version="0.32.0" targetFramework="net461" />
|
||||
</packages>
|
Loading…
Reference in a new issue