Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show file hashes in properties like HashTab. #3722 #4300

Closed
wants to merge 12 commits into from
8 changes: 8 additions & 0 deletions Files/Files.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
<Compile Include="Helpers\WallpaperHelpers.cs" />
<Compile Include="Helpers\WidgetsHelpers.cs" />
<Compile Include="Helpers\Win32Helpers.cs" />
<Compile Include="Interacts\Crc32.cs" />
<Compile Include="Interacts\ItemManipulationModel.cs" />
<Compile Include="UserControls\RestartControl.xaml.cs">
<DependentUpon>RestartControl.xaml</DependentUpon>
Expand Down Expand Up @@ -484,6 +485,9 @@
<Compile Include="Views\LayoutModes\ColumnViewBrowser.xaml.cs">
<DependentUpon>ColumnViewBrowser.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Pages\PropertiesHashes.xaml.cs">
<DependentUpon>PropertiesHashes.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Pages\PropertiesLibrary.xaml.cs">
<DependentUpon>PropertiesLibrary.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -948,6 +952,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Pages\PropertiesHashes.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\Pages\PropertiesLibrary.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
Expand Down
62 changes: 62 additions & 0 deletions Files/Interacts/Crc32.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Files.Interacts
{
// https://rosettacode.org/wiki/CRC-32#C.23
public class Crc32
{
#region Constants
/// <summary>
/// Generator polynomial (modulo 2) for the reversed CRC32 algorithm.
/// </summary>
private const UInt32 s_generator = 0xEDB88320;
#endregion

#region Constructors
/// <summary>
/// Creates a new instance of the Crc32 class.
/// </summary>
public Crc32()
{
// Constructs the checksum lookup table. Used to optimize the checksum.
m_checksumTable = Enumerable.Range(0, 256).Select(i =>
{
var tableEntry = (uint)i;
for (var j = 0; j < 8; ++j)
{
tableEntry = ((tableEntry & 1) != 0)
? (s_generator ^ (tableEntry >> 1))
: (tableEntry >> 1);
}
return tableEntry;
}).ToArray();
}
#endregion

#region Methods
/// <summary>
/// Calculates the checksum of the byte stream.
/// </summary>
/// <param name="byteStream">The byte stream to calculate the checksum for.</param>
/// <returns>A 32-bit reversed checksum.</returns>
public UInt32 Get<T>(IEnumerable<T> byteStream)
{
// Initialize checksumRegister to 0xFFFFFFFF and calculate the checksum.
return ~byteStream.Aggregate(0xFFFFFFFF, (checksumRegister, currentByte) =>
(m_checksumTable[(checksumRegister & 0xFF) ^ Convert.ToByte(currentByte)] ^ (checksumRegister >> 8)));
}
#endregion

#region Fields
/// <summary>
/// Contains a cache of calculated checksum chunks.
/// </summary>
private readonly UInt32[] m_checksumTable;

#endregion
}
}
144 changes: 135 additions & 9 deletions Files/ViewModels/Properties/FileProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Files.Extensions;
using Files.Filesystem;
using Files.Helpers;
using Files.Interacts;
using Microsoft.Toolkit.Mvvm.Input;
using Microsoft.Toolkit.Uwp;
using Microsoft.UI.Xaml.Controls;
Expand Down Expand Up @@ -143,17 +144,29 @@ public override async void GetSpecialProperties()
GetOtherProperties(file.Properties);

// Get file MD5 hash
var hashAlgTypeName = HashAlgorithmNames.Md5;
ViewModel.ItemMD5HashProgressVisibility = Visibility.Visible;
ViewModel.ItemMD5HashVisibility = Visibility.Visible;

// Get file SHA1 hash
ViewModel.ItemSHA1HashProgressVisibility = Visibility.Visible;
ViewModel.ItemSHA1HashVisibility = Visibility.Visible;

// Get file CRC32 hash
ViewModel.ItemCRC32HashProgressVisibility = Visibility.Visible;
ViewModel.ItemCRC32HashVisibility = Visibility.Visible;

try
{
ViewModel.ItemMD5Hash = await GetHashForFileAsync(Item, hashAlgTypeName, TokenSource.Token, ProgressBar, AppInstance);
ViewModel.ItemMD5Hash = await GetHashForFileAsync(file, HashAlgorithmNames.Md5, TokenSource.Token, ProgressBar, AppInstance);
ViewModel.ItemSHA1Hash = await GetHashForFileAsync(file, HashAlgorithmNames.Sha1, TokenSource.Token, ProgressBar, AppInstance);
ViewModel.ItemCRC32Hash = await GetCRC32HashForFileAsync(file, ProgressBar);
}
catch (Exception ex)
{
NLog.LogManager.GetCurrentClassLogger().Warn(ex, ex.Message);
ViewModel.ItemMD5HashCalcError = true;
ViewModel.ItemSHA1HashCalcError = true;
ViewModel.ItemCRC32HashCalcError = true;
}
}

Expand Down Expand Up @@ -181,6 +194,88 @@ public async void GetSystemFileProperties()
ViewModel.FileProperties = new ObservableCollection<FileProperty>(list.Where(i => i.Value != null));
}

public async Task<string> GenerateMD5Compare(StorageFile file)
{
string hash;
// Get file MD5 hash
try
{
TokenSource.Cancel();
hash = await GetHashForFileAsync(file, HashAlgorithmNames.Md5, TokenSource.Token, ProgressBar, AppInstance);
}
catch (Exception ex)
{
NLog.LogManager.GetCurrentClassLogger().Error(ex, ex.Message);
hash = string.Empty;
}

return hash;
}

public async Task<string> GenerateSHA1Compare(StorageFile file)
{
string hash;
// Get file SHA1 hash
try
{
TokenSource.Cancel();
hash = await GetHashForFileAsync(file, HashAlgorithmNames.Sha1, TokenSource.Token, ProgressBar, AppInstance);
}
catch (Exception ex)
{
NLog.LogManager.GetCurrentClassLogger().Error(ex, ex.Message);
hash = string.Empty;
}

return hash;
}

public async Task<string> GenerateCRC32Compare(StorageFile file)
{
string hash;
// Get file CRC32 hash
try
{
TokenSource.Cancel();
hash = await GetCRC32HashForFileAsync(file, ProgressBar);
}
catch (Exception ex)
{
NLog.LogManager.GetCurrentClassLogger().Error(ex, ex.Message);
hash = string.Empty;
}

return hash;
}

public async Task GetSystemFileHashes(string hashType, StorageFile file)
{
if (file == null)
{
// Could not access file, can't show any other property
ViewModel.ItemMD5Hash = ViewModel.ItemSHA1Hash = ViewModel.ItemCRC32Hash = string.Empty;
ViewModel.ItemCompareHashProgressVisibility = Visibility.Collapsed;
}
else
{
ViewModel.ItemCompareHashProgressVisibility = Visibility.Visible;
ViewModel.ItemCompareHashVisibility = Visibility.Visible;

if (hashType.Equals(HashAlgorithmNames.Md5))
{
ViewModel.ItemCompareHash = await GenerateMD5Compare(file);
}
else if (hashType.Equals(HashAlgorithmNames.Sha1))
{
ViewModel.ItemCompareHash = await GenerateSHA1Compare(file);
}
else
{
ViewModel.ItemCompareHash = await GenerateCRC32Compare(file);
}
}
}

public static async Task<string> GetAddressFromCoordinatesAsync(double? Lat, double? Lon)
{
if (!Lat.HasValue || !Lon.HasValue)
Expand Down Expand Up @@ -341,16 +436,15 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode
}
}

private async Task<string> GetHashForFileAsync(ListedItem fileItem, string nameOfAlg, CancellationToken token, ProgressBar progress, IShellPage associatedInstance)
private async Task<string> GetHashForFileAsync(StorageFile itemFromPath, string nameOfAlg, CancellationToken token, ProgressBar progress, IShellPage associatedInstance)
{
HashAlgorithmProvider algorithmProvider = HashAlgorithmProvider.OpenAlgorithm(nameOfAlg);
StorageFile file = await StorageItemHelpers.ToStorageItem<StorageFile>((fileItem as ShortcutItem)?.TargetPath ?? fileItem.ItemPath, associatedInstance);
if (file == null)
if (itemFromPath == null)
{
return "";
}

Stream stream = await FilesystemTasks.Wrap(() => file.OpenStreamForReadAsync());
Stream stream = await FilesystemTasks.Wrap(() => itemFromPath.OpenStreamForReadAsync());
if (stream == null)
{
return "";
Expand All @@ -371,7 +465,7 @@ private async Task<string> GetHashForFileAsync(ListedItem fileItem, string nameO

Windows.Storage.Streams.Buffer buffer = new Windows.Storage.Streams.Buffer(capacity);
var hash = algorithmProvider.CreateHash();
while (!token.IsCancellationRequested)
while (token.IsCancellationRequested)
{
await inputStream.ReadAsync(buffer, capacity, InputStreamOptions.None);
if (buffer.Length > 0)
Expand All @@ -389,11 +483,43 @@ private async Task<string> GetHashForFileAsync(ListedItem fileItem, string nameO
}
inputStream.Dispose();
stream.Dispose();
if (token.IsCancellationRequested)

return CryptographicBuffer.EncodeToHexString(hash.GetValueAndReset()).ToLower();
}

public async Task<string> GetCRC32HashForFileAsync(StorageFile itemFromPath, Microsoft.UI.Xaml.Controls.ProgressBar progress)
{
if (itemFromPath == null)
{
return "";
}
return CryptographicBuffer.EncodeToHexString(hash.GetValueAndReset()).ToLower();

Stream stream = await FilesystemTasks.Wrap(() => itemFromPath.OpenStreamForReadAsync());
if (stream == null)
{
return "";
}

byte[] fileBytes = await GetBytesAsync(itemFromPath);
var crc32 = new Crc32();

return crc32.Get(fileBytes).ToString("X");
}

public static async Task<byte[]> GetBytesAsync(StorageFile file)
{
byte[] fileBytes = null;
if (file == null) return null;
using (var stream = await file.OpenReadAsync())
{
fileBytes = new byte[stream.Size];
using (var reader = new DataReader(stream))
{
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(fileBytes);
}
}
return fileBytes;
}
}
}
Loading