1
0
Fork 0
mirror of https://github.com/wagesj45/mdfinder.git synced 2024-12-22 00:12:42 -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:
Jordan Wages 2019-02-28 01:17:38 -06:00
parent be05120bdb
commit 1de70724b0
17 changed files with 885 additions and 145 deletions

View file

@ -36,6 +36,9 @@
<setting name="Language" serializeAs="String"> <setting name="Language" serializeAs="String">
<value>en-US</value> <value>en-US</value>
</setting> </setting>
<setting name="ArchiveFolder" serializeAs="String">
<value />
</setting>
</mdfinder.Properties.Settings> </mdfinder.Properties.Settings>
</userSettings> </userSettings>
</configuration> </configuration>

View file

@ -63,8 +63,15 @@ namespace mdfinder
/// <param name="hashProvider"> The hash provider. </param> /// <param name="hashProvider"> The hash provider. </param>
public void InsertFileRecord(string path, long size, string hash, string hashProvider) 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 }; var fileRecord = new FileRecord(path, size, hash, hashProvider);
this.FileRecordCollection.Insert(fileRecord); 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> /// <summary> Gets the file records in this collection. </summary>

View 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
}
}
}

View file

@ -6,29 +6,149 @@ using System.Threading.Tasks;
namespace mdfinder 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 #region Properties
/// <summary> Gets or sets the identifier. </summary> /// <summary> Gets or sets the identifier. </summary>
/// <value> The identifier. </value> /// <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> /// <summary> Gets or sets the full pathname of the file. </summary>
/// <value> The full pathname of the file. </value> /// <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> /// <summary> Gets or sets the size. </summary>
/// <value> The size. </value> /// <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> /// <summary> Gets or sets the hash. </summary>
/// <value> The hash. </value> /// <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> /// <summary> Gets or sets the hash provider. </summary>
/// <value> The hash provider. </value> /// <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 #endregion
} }

View file

@ -170,4 +170,49 @@
</Path> </Path>
</Viewbox> </Viewbox>
</ControlTemplate> </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> </ResourceDictionary>

View file

@ -106,7 +106,16 @@ namespace mdfinder.Localization {
} }
/// <summary> /// <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> /// </summary>
public static string ActionBarLabel { public static string ActionBarLabel {
get { 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> /// <summary>
/// Looks up a localized string similar to Would you like to visit mdfinder to update to the latest version?. /// Looks up a localized string similar to Would you like to visit mdfinder to update to the latest version?.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Hash. /// Looks up a localized string similar to Hash.
/// </summary> /// </summary>
@ -268,29 +322,11 @@ namespace mdfinder.Localization {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Filters. /// Looks up a localized string similar to Files.
/// </summary> /// </summary>
public static string FilterBarLabel { public static string FilesLabel {
get { get {
return ResourceManager.GetString("FilterBarLabel", resourceCulture); return ResourceManager.GetString("FilesLabel", 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);
} }
} }
@ -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> /// <summary>
/// Looks up a localized string similar to OK. /// Looks up a localized string similar to OK.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Provider Location. /// Looks up a localized string similar to Provider Location.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Scan the selected path. /// Looks up a localized string similar to Scan the selected path.
/// </summary> /// </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> /// <summary>
/// Looks up a localized string similar to Skip Empty Files. /// Looks up a localized string similar to Skip Empty Files.
/// </summary> /// </summary>

View file

@ -132,8 +132,17 @@
<data name="AboutWindowTitle" xml:space="preserve"> <data name="AboutWindowTitle" xml:space="preserve">
<value>About mdfinder</value> <value>About mdfinder</value>
</data> </data>
<data name="ActionArchiveLabel" xml:space="preserve">
<value>Archive Remaining Files</value>
</data>
<data name="ActionBarLabel" xml:space="preserve"> <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>
<data name="BehindVersionQuestion" xml:space="preserve"> <data name="BehindVersionQuestion" xml:space="preserve">
<value>Would you like to visit mdfinder to update to the latest version?</value> <value>Would you like to visit mdfinder to update to the latest version?</value>
@ -186,14 +195,8 @@
<data name="FileMenu" xml:space="preserve"> <data name="FileMenu" xml:space="preserve">
<value>File</value> <value>File</value>
</data> </data>
<data name="FilterBarLabel" xml:space="preserve"> <data name="FilesLabel" xml:space="preserve">
<value>Filters</value> <value>Files</value>
</data>
<data name="FilterDuplicatesLabel" xml:space="preserve">
<value>Duplicates</value>
</data>
<data name="FilterShowAllLabel" xml:space="preserve">
<value>Show All</value>
</data> </data>
<data name="HelpMenu" xml:space="preserve"> <data name="HelpMenu" xml:space="preserve">
<value>Help</value> <value>Help</value>
@ -240,9 +243,15 @@
<data name="ScanLocationLabel" xml:space="preserve"> <data name="ScanLocationLabel" xml:space="preserve">
<value>Location to Scan</value> <value>Location to Scan</value>
</data> </data>
<data name="ScanningLabel" xml:space="preserve">
<value>Scan Location</value>
</data>
<data name="ScanTooltip" xml:space="preserve"> <data name="ScanTooltip" xml:space="preserve">
<value>Scan the selected path</value> <value>Scan the selected path</value>
</data> </data>
<data name="SizeLabel" xml:space="preserve">
<value>Size:</value>
</data>
<data name="SkipEmptyFilesLabel" xml:space="preserve"> <data name="SkipEmptyFilesLabel" xml:space="preserve">
<value>Skip Empty Files</value> <value>Skip Empty Files</value>
</data> </data>
@ -255,4 +264,22 @@
<data name="VersionLabel" xml:space="preserve"> <data name="VersionLabel" xml:space="preserve">
<value>Version</value> <value>Version</value>
</data> </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> </root>

View file

@ -3,10 +3,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:gu="https://github.com/JohanLarsson/Gu.Wpf.Media"
xmlns:mdfinder="clr-namespace:mdfinder" xmlns:mdfinder="clr-namespace:mdfinder"
xmlns:loc="clr-namespace:mdfinder.Localization" xmlns:loc="clr-namespace:mdfinder.Localization"
mc:Ignorable="d" 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> <Window.Resources>
<mdfinder:InverseBoolConverter x:Key="InverseBoolConverter" /> <mdfinder:InverseBoolConverter x:Key="InverseBoolConverter" />
<mdfinder:BoolVisibilityConverter x:Key="BoolVisibilityConverter" /> <mdfinder:BoolVisibilityConverter x:Key="BoolVisibilityConverter" />
@ -62,7 +63,7 @@
<Button Grid.Column="2" MinWidth="25" Name="btnFilePicker" Click="btnFilePicker_Click">...</Button> <Button Grid.Column="2" MinWidth="25" Name="btnFilePicker" Click="btnFilePicker_Click">...</Button>
</Grid> </Grid>
</GroupBox> </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>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="32" /> <RowDefinition Height="32" />
@ -85,80 +86,168 @@
</Button> </Button>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" Header="{x:Static loc:Localization.FilterBarLabel}"> <GroupBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" Header="{x:Static loc:Localization.ActionBarLabel}">
<Grid> <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="1">
<Grid.RowDefinitions> <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">
<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" >
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource ShowAllIcon}" /> <ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource LargeFileIcon}" />
<Label Content="{x:Static loc:Localization.FilterShowAllLabel}" /> <Label Content="{x:Static loc:Localization.ActionLargestLabel}" />
</StackPanel> </StackPanel>
</Button> </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"> <StackPanel Orientation="Horizontal">
<ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource DuplicateIcon}" /> <ContentControl MaxWidth="16" HorizontalAlignment="Center" Template="{StaticResource SmallFileIcon}" />
<Label Content="{x:Static loc:Localization.FilterDuplicatesLabel}" /> <Label Content="{x:Static loc:Localization.ActionSmallestLabel}" />
</StackPanel> </StackPanel>
</Button> </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> </GroupBox>
<DataGrid x:Name="datagridFileRecords" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Initialized="DatagridFileRecords_Initialized" IsReadOnly="True" AutoGenerateColumns="False"> <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">
<DataGrid.Resources> <ListBox.ItemTemplate>
<Style TargetType="Hyperlink"> <DataTemplate>
<EventSetter Event="Click" Handler="Hyperlink_Click"/> <StackPanel Orientation="Horizontal">
</Style> <Label Content="{Binding Count}" />
<Style TargetType="{x:Type DataGridCell}"> <Label Content="{x:Static loc:Localization.FilesLabel}" />
<Style.Triggers> <Label Content="|" />
<Trigger Property="DataGridCell.IsSelected" Value="True"> <Label Content="{x:Static loc:Localization.SizeLabel}" />
<Setter Property="Background" Value="Black" /> <Label Content="{Binding TotalSize, Converter={StaticResource SizeConverter}}" />
</Trigger> </StackPanel>
</Style.Triggers> </DataTemplate>
</Style> </ListBox.ItemTemplate>
</DataGrid.Resources> </ListBox>
<DataGrid.Columns> <Grid x:Name="gridDuplicates" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding SelectedItem, ElementName=listBoxDupe}">
<DataGridHyperlinkColumn Header="{x:Static loc:Localization.ColumnHeadingPath}" Binding="{Binding Path}" ContentBinding="{Binding Path, Converter={StaticResource URIConverter}}" Width="Auto" IsReadOnly="True" /> <Grid.RowDefinitions>
<DataGridTextColumn Header="{x:Static loc:Localization.ColumnHeadingSize}" Binding="{Binding Size, Converter={StaticResource SizeConverter}}" /> <RowDefinition Height="Auto" />
<DataGridTextColumn Header="{x:Static loc:Localization.ColumnHeadingHash}" Binding="{Binding Hash}" /> <RowDefinition Height="*" />
<DataGridTextColumn Header="{x:Static loc:Localization.ColumnHeadingHashProvider}" Binding="{Binding HashProvider}" /> </Grid.RowDefinitions>
</DataGrid.Columns> <Grid.ColumnDefinitions>
<DataGrid.GroupStyle> <ColumnDefinition Width="*"/>
<GroupStyle> <ColumnDefinition Width="*"/>
<GroupStyle.HeaderTemplate> </Grid.ColumnDefinitions>
<DataTemplate> <StackPanel Orientation="Vertical">
<StackPanel> <StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Hash}" /> <Label Content="{x:Static loc:Localization.SizeOnDiskLabel}" />
</StackPanel> <Label Content="{Binding ScanResults.SelectedDuplicateFileGroup.TotalSize, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, Converter={StaticResource SizeConverter}}" />
</DataTemplate> </StackPanel>
</GroupStyle.HeaderTemplate> <StackPanel Orientation="Horizontal">
<GroupStyle.ContainerStyle> <Label Content="{x:Static loc:Localization.PotentialSpaceSavingsLabel}" />
<Style TargetType="{x:Type GroupItem}"> <Label Content="{Binding ScanResults.SelectedDuplicateFileGroup.PotentialSizeSaving, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mdfinder:MainWindow}}, Converter={StaticResource SizeConverter}}" />
<Setter Property="Template"> </StackPanel>
<Setter.Value> </StackPanel>
<ControlTemplate TargetType="{x:Type GroupItem}"> <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">
<Expander IsExpanded="True"> <DataGrid.Resources>
<Expander.Header> <Style TargetType="Hyperlink">
<StackPanel Orientation="Horizontal"> <EventSetter Event="Click" Handler="Hyperlink_Click"/>
<TextBlock Text="{Binding Path=Name}" /> <Setter Property="Foreground" Value="DarkGreen"></Setter>
<TextBlock Text="{Binding Path=ItemCount}"/> <Style.Triggers>
<TextBlock Text="{x:Static loc:Localization.ItemsLabel}"/> <DataTrigger Binding="{Binding IsSelected, RelativeSource= {RelativeSource AncestorType={x:Type DataGridRow}}}" Value="True">
</StackPanel> <Setter Property="Foreground" Value="White" />
</Expander.Header> </DataTrigger>
<ItemsPresenter /> </Style.Triggers>
</Expander> </Style>
</ControlTemplate> </DataGrid.Resources>
</Setter.Value> <DataGrid.Columns>
</Setter> <DataGridTemplateColumn>
</Style> <DataGridTemplateColumn.CellTemplate>
</GroupStyle.ContainerStyle> <DataTemplate>
</GroupStyle> <TextBlock>
</DataGrid.GroupStyle> <Hyperlink Click="Hyperlink_Click" NavigateUri="{Binding Path}">
</DataGrid> <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" /> <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"/> <ProgressBar Grid.Row="3" Grid.ColumnSpan="4" Name="progressBar" Minimum="0" Height="16"/>
</Grid> </Grid>

View file

@ -18,6 +18,7 @@ using mdfinder.hashprovider;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.IO; using System.IO;
using System.IO.Compression;
namespace mdfinder namespace mdfinder
{ {
@ -26,6 +27,16 @@ namespace mdfinder
/// </summary> /// </summary>
public partial class MainWindow : Window 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 #region Properties
/// <summary> Gets or sets the database. </summary> /// <summary> Gets or sets the database. </summary>
@ -44,6 +55,10 @@ namespace mdfinder
/// <value> The default provider. </value> /// <value> The default provider. </value>
public IHashProvider DefaultProvider { get; set; } public IHashProvider DefaultProvider { get; set; }
/// <summary> Gets or sets the scan results. </summary>
/// <value> The scan results. </value>
public ScanResults ScanResults { get; set; }
#endregion #endregion
#region Constructors #region Constructors
@ -55,6 +70,7 @@ namespace mdfinder
this.Scanner = new Scanner(); this.Scanner = new Scanner();
this.DefaultProvider = new MD5HashProvider(); this.DefaultProvider = new MD5HashProvider();
this.HashProviders = GetProviderPlugins(); this.HashProviders = GetProviderPlugins();
this.ScanResults = new ScanResults();
this.Scanner.DirectoryFound += (sender, args) => Dispatcher.Invoke(() => txtProgressLabel.Content = args.Directory.Name); this.Scanner.DirectoryFound += (sender, args) => Dispatcher.Invoke(() => txtProgressLabel.Content = args.Directory.Name);
this.Scanner.FilesFound += (sender, args) => this.Scanner.FilesFound += (sender, args) =>
@ -87,17 +103,52 @@ namespace mdfinder
/// </returns> /// </returns>
private IEnumerable<IHashProvider> GetProviderPlugins() private IEnumerable<IHashProvider> GetProviderPlugins()
{ {
var directory = new DirectoryInfo(Properties.Settings.Default.ProviderFolder); if (!string.IsNullOrWhiteSpace(Properties.Settings.Default.ProviderFolder) && Directory.Exists(Properties.Settings.Default.ProviderFolder))
foreach (var pluginFile in directory.GetFiles("*.dll"))
{ {
var assembly = Assembly.LoadFrom(pluginFile.FullName); var directory = new DirectoryInfo(Properties.Settings.Default.ProviderFolder);
foreach (var type in assembly.GetTypes().Where(t => t.GetInterface("IHashProvider") != null)) 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> /// <summary> Event handler. Called by btnFilePicker for click events. </summary>
/// <param name="sender"> Source of the event. </param> /// <param name="sender"> Source of the event. </param>
/// <param name="e"> Routed event information. </param> /// <param name="e"> Routed event information. </param>
@ -117,49 +168,27 @@ namespace mdfinder
private void btnScan_Click(object sender, RoutedEventArgs e) private void btnScan_Click(object sender, RoutedEventArgs e)
{ {
var location = txtScanLocation.Text; var location = txtScanLocation.Text;
ResetMediaPreview();
if (!this.Scanner.IsScanning) if (!this.Scanner.IsScanning)
{ {
new Thread(() => new Thread(() =>
{ {
this.Scanner.Scan(location); this.Scanner.Scan(location);
this.Dispatcher.Invoke(() => txtProgressLabel.Content = string.Empty); 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(); }).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> /// <summary> Event handler. Called by BtnClear for click events. </summary>
/// <param name="sender"> Source of the event. </param> /// <param name="sender"> Source of the event. </param>
/// <param name="e"> Routed event information. </param> /// <param name="e"> Routed event information. </param>
private void BtnClear_Click(object sender, RoutedEventArgs e) private void BtnClear_Click(object sender, RoutedEventArgs e)
{ {
this.Database.Clear(); this.Database.Clear();
this.datagridFileRecords.ItemsSource = Enumerable.Empty<FileRecord>(); ResetMediaPreview();
SetDuplicateFileCollection(Enumerable.Empty<DuplicateFileGroup>());
} }
/// <summary> Event handler. Called by Hyperlink for click events. </summary> /// <summary> Event handler. Called by Hyperlink for click events. </summary>
@ -183,14 +212,153 @@ namespace mdfinder
this.HashProviders = GetProviderPlugins(); 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) private void MenuAbout_Click(object sender, RoutedEventArgs e)
{ {
var aboutWindow = new AboutWindow(); var aboutWindow = new AboutWindow();
aboutWindow.Show(); 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
} }

View file

@ -26,16 +26,29 @@
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="77.126" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="25.874"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" Template="{StaticResource FolderIcon}" Width="16" Margin="0,0,4,0" /> <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" /> <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" /> <Button x:Name="btnProviderLocationDirectory" Grid.Column="4" MinWidth="25" Content="..." Click="BtnProviderLocationDirectory_Click" />
</Grid> </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" /> <Separator Margin="0,10" />
<Button Name="btnSave" MaxWidth="45" Content="{x:Static loc:Localization.SaveLabel}" Click="BtnSave_Click" /> <Button Name="btnSave" MaxWidth="45" Content="{x:Static loc:Localization.SaveLabel}" Click="BtnSave_Click" />
</StackPanel> </StackPanel>

View file

@ -63,5 +63,18 @@ namespace mdfinder
Properties.Settings.Default.ProviderFolder = fbd.SelectedPath; 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;
}
}
} }
} }

View file

@ -70,5 +70,17 @@ namespace mdfinder.Properties {
this["Language"] = value; 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;
}
}
} }
} }

View file

@ -14,5 +14,8 @@
<Setting Name="Language" Type="System.Globalization.CultureInfo" Scope="User"> <Setting Name="Language" Type="System.Globalization.CultureInfo" Scope="User">
<Value Profile="(Default)">en-US</Value> <Value Profile="(Default)">en-US</Value>
</Setting> </Setting>
<Setting Name="ArchiveFolder" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

65
mdfinder/ScanResults.cs Normal file
View 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
}
}

View file

@ -76,8 +76,15 @@ namespace mdfinder
public void Scan(string path) public void Scan(string path)
{ {
if(string.IsNullOrWhiteSpace(path))
{
this.IsScanning = false;
return;
}
this.Processed = 0; this.Processed = 0;
this.Total = 0; this.Total = 0;
this.IsScanning = true;
var scanPath = new DirectoryInfo(path); var scanPath = new DirectoryInfo(path);
if (scanPath.Exists) if (scanPath.Exists)
@ -85,6 +92,8 @@ namespace mdfinder
Discover(scanPath); Discover(scanPath);
Scan(scanPath); Scan(scanPath);
} }
this.IsScanning = false;
} }
private void Discover(DirectoryInfo directory) private void Discover(DirectoryInfo directory)

View file

@ -40,6 +40,9 @@
<Reference Include="csmic, Version=1.1.4.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="csmic, Version=1.1.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\csmic.1.1.4\lib\net40\csmic.dll</HintPath> <HintPath>..\packages\csmic.1.1.4\lib\net40\csmic.dll</HintPath>
</Reference> </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"> <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> <HintPath>..\packages\LiteDB.4.1.4\lib\net40\LiteDB.dll</HintPath>
</Reference> </Reference>
@ -49,6 +52,8 @@
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Runtime.Serialization" /> <Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Security" /> <Reference Include="System.Security" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
@ -73,9 +78,11 @@
<Compile Include="AboutWindow.xaml.cs"> <Compile Include="AboutWindow.xaml.cs">
<DependentUpon>AboutWindow.xaml</DependentUpon> <DependentUpon>AboutWindow.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="DuplicateFileGroup.cs" />
<Compile Include="OptionsWindow.xaml.cs"> <Compile Include="OptionsWindow.xaml.cs">
<DependentUpon>OptionsWindow.xaml</DependentUpon> <DependentUpon>OptionsWindow.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="ScanResults.cs" />
<Compile Include="SizeConverter.cs" /> <Compile Include="SizeConverter.cs" />
<Compile Include="URIConverter.cs" /> <Compile Include="URIConverter.cs" />
<Page Include="AboutWindow.xaml"> <Page Include="AboutWindow.xaml">

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="csmic" version="1.1.4" targetFramework="net461" /> <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="LiteDB" version="4.1.4" targetFramework="net461" />
<package id="Octokit" version="0.32.0" targetFramework="net461" /> <package id="Octokit" version="0.32.0" targetFramework="net461" />
</packages> </packages>