Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d8b8cf8
Feature
Lamparter Feb 1, 2025
0324ab5
Add ability to go through all hashes automatically
Lamparter Feb 1, 2025
483b9bd
Add compare file button
Lamparter Feb 16, 2025
3743513
Requested changes
Lamparter Mar 2, 2025
410eb39
Hide info bar when no hash is provided
Lamparter Mar 3, 2025
dc1133f
Add back the BOM
Lamparter Mar 3, 2025
6c37d74
MVVM
Lamparter Mar 3, 2025
bd70048
Refactor file picker method
Lamparter Mar 3, 2025
72ee848
Replace SHA384 with MD5 for file comparison algorithm
Lamparter Mar 4, 2025
19b6072
Squash previous commits (to revert)
Lamparter Mar 16, 2025
3a76b4b
Revert "Squash previous commits (to revert)"
Lamparter Mar 16, 2025
dac83a1
MVVM
Lamparter Mar 16, 2025
d983c40
Genius fix
Lamparter Mar 17, 2025
43ad7d7
Add back `Command="{x:Bind HashesViewModel.ToggleIsEnabledCommand}"`
Lamparter Mar 17, 2025
8c1a0cb
Discard changes to src/Files.App/Views/Properties/HashesPage.xaml.cs
Lamparter Mar 17, 2025
01c7107
Hide info bar when text box is cleared
Lamparter Mar 17, 2025
8b19089
Convert bindings back to OneWay
Lamparter Mar 17, 2025
3f46f8e
Improved spacing
yaira2 Mar 17, 2025
c092439
Convert back to TwoWay binding
Lamparter Mar 18, 2025
a12ab88
Remove unnecessary grid
Lamparter Mar 18, 2025
659015d
Remove `VerticalAlignment="Stretch"`
Lamparter Mar 18, 2025
07bbccb
Remove XAML behaviours command
Lamparter Mar 18, 2025
eb3712a
SHA384 -> MD5
Lamparter Mar 18, 2025
bcea388
Convert ItemsSource to OneWay binding
Lamparter Mar 18, 2025
9b82e7d
Fix info bar appearing in file comparison
Lamparter Mar 18, 2025
40a5e2e
Leftover changes
Lamparter Mar 18, 2025
926d757
Apply suggestions from code review
Lamparter Mar 18, 2025
0dbc80b
Fix for the compare file feature
Lamparter Mar 19, 2025
404684e
Apply suggestions from code review
Lamparter Mar 19, 2025
ffbf07d
Update src/Files.App/ViewModels/Properties/HashesViewModel.cs
yaira2 Mar 19, 2025
90b8806
Update src/Files.App/ViewModels/Properties/HashesViewModel.cs
yaira2 Mar 19, 2025
94a9608
Update HashesViewModel.cs
yaira2 Mar 19, 2025
16789ff
Remove SelectAlgorithm entry from Resources.resw
Lamparter Mar 20, 2025
9c23ebc
Update HashesViewModel.cs
yaira2 Mar 20, 2025
cd4024e
Simplify code
yaira2 Mar 20, 2025
28375a3
Use properties window hwnd
yaira2 Mar 20, 2025
4b369cd
Apply suggestions from code review
yaira2 Mar 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/Files.App/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -4113,6 +4113,18 @@
<value>Add to shelf</value>
<comment>Tooltip that displays when dragging items to the Shelf Pane</comment>
</data>
<data name="EnterHashToCompare" xml:space="preserve">
<value>Enter a hash to compare</value>
<comment>Placeholder that appears in the compare hash text box</comment>
</data>
<data name="HashesMatch" xml:space="preserve">
<value>Matches {0}</value>
<comment>Appears when two compared hashes match, e.g. "Matches SHA256"</comment>
</data>
<data name="HashesDoNotMatch" xml:space="preserve">
<value>No matches found</value>
<comment>Appears when two compared hashes don't match</comment>
</data>
<data name="PathOrAlias" xml:space="preserve">
<value>Path or alias</value>
</data>
Expand Down Expand Up @@ -4156,4 +4168,8 @@
<value>Cannot clone repo</value>
<comment>Cannot clone repo dialog title</comment>
</data>
<data name="CompareFile" xml:space="preserve">
<value>Compare a file</value>
<comment>Button that appears in file hash properties that allows the user to compare two files</comment>
</data>
</root>
98 changes: 93 additions & 5 deletions src/Files.App/ViewModels/Properties/HashesViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
// Copyright (c) Files Community
// Copyright (c) Files Community
// Licensed under the MIT License.

using Files.Shared.Helpers;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml.Controls;
using System.IO;
using System.Security.Cryptography;
using System.Windows.Input;

namespace Files.App.ViewModels.Properties
{
public sealed partial class HashesViewModel : ObservableObject, IDisposable
{
private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService<ICommonDialogService>();
private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetService<IUserSettingsService>()!;

private readonly AppWindow _appWindow;

private HashInfoItem _selectedItem;
public HashInfoItem SelectedItem
{
Expand All @@ -23,16 +29,48 @@ public HashInfoItem SelectedItem
public Dictionary<string, bool> ShowHashes { get; private set; }

public ICommand ToggleIsEnabledCommand { get; private set; }
public ICommand CompareFileCommand { get; private set; }

private ListedItem _item;

private CancellationTokenSource _cancellationTokenSource;

public HashesViewModel(ListedItem item)
private string _hashInput;
public string HashInput
{
get => _hashInput;
set
{
SetProperty(ref _hashInput, value);

OnHashInputTextChanged();
OnPropertyChanged(nameof(IsInfoBarOpen));
}
}

private InfoBarSeverity _infoBarSeverity;
public InfoBarSeverity InfoBarSeverity
{
get => _infoBarSeverity;
set => SetProperty(ref _infoBarSeverity, value);
}

private string _infoBarTitle;
public string InfoBarTitle
{
get => _infoBarTitle;
set => SetProperty(ref _infoBarTitle, value);
}

public bool IsInfoBarOpen
=> !string.IsNullOrEmpty(HashInput);

public HashesViewModel(ListedItem item, AppWindow appWindow)
{
ToggleIsEnabledCommand = new RelayCommand<string>(ToggleIsEnabled);

_item = item;
_appWindow = appWindow;
_cancellationTokenSource = new();

Hashes =
Expand All @@ -55,6 +93,8 @@ public HashesViewModel(ListedItem item)
ShowHashes.TryAdd("SHA512", false);

Hashes.Where(x => ShowHashes[x.Algorithm]).ForEach(x => ToggleIsEnabledCommand.Execute(x.Algorithm));

CompareFileCommand = new RelayCommand(async () => await OnCompareFileAsync());
}

private void ToggleIsEnabled(string? algorithm)
Expand All @@ -71,7 +111,7 @@ private void ToggleIsEnabled(string? algorithm)
// Don't calculate hashes for online files
if (_item.SyncStatusUI.SyncStatus is CloudDriveSyncStatus.FileOnline or CloudDriveSyncStatus.FolderOnline)
{
hashInfoItem.HashValue = "CalculationOnlineFileHashError".GetLocalizedResource();
hashInfoItem.HashValue = Strings.CalculationOnlineFileHashError.GetLocalizedResource();
return;
}

Expand Down Expand Up @@ -106,11 +146,11 @@ private void ToggleIsEnabled(string? algorithm)
catch (IOException)
{
// File is currently open
hashInfoItem.HashValue = "CalculationErrorFileIsOpen".GetLocalizedResource();
hashInfoItem.HashValue = Strings.CalculationErrorFileIsOpen.GetLocalizedResource();
}
catch (Exception)
{
hashInfoItem.HashValue = "CalculationError".GetLocalizedResource();
hashInfoItem.HashValue = Strings.CalculationError.GetLocalizedResource();
}
finally
{
Expand All @@ -120,6 +160,54 @@ private void ToggleIsEnabled(string? algorithm)
}
}

public string FindMatchingAlgorithm(string hash)
{
if (string.IsNullOrEmpty(hash))
return string.Empty;

return Hashes
.FirstOrDefault(h => h.HashValue?.Equals(hash, StringComparison.OrdinalIgnoreCase) == true)
?.Algorithm ?? string.Empty;
}

public async Task<string> CalculateFileHashAsync(string filePath)
{
using var stream = File.OpenRead(filePath);
using var md5 = MD5.Create();
var hash = await Task.Run(() => md5.ComputeHash(stream));
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}

private void OnHashInputTextChanged()
{
string matchingAlgorithm = FindMatchingAlgorithm(HashInput);

InfoBarSeverity = string.IsNullOrEmpty(matchingAlgorithm)
? InfoBarSeverity.Error
: InfoBarSeverity.Success;

InfoBarTitle = string.IsNullOrEmpty(matchingAlgorithm)
? Strings.HashesDoNotMatch.GetLocalizedResource()
: string.Format(Strings.HashesMatch.GetLocalizedResource(), matchingAlgorithm);
}

private async Task OnCompareFileAsync()
{
var hWnd = Microsoft.UI.Win32Interop.GetWindowFromWindowId(_appWindow.Id);

var result = CommonDialogService.Open_FileOpenDialog(
hWnd,
false,
[],
Environment.SpecialFolder.Desktop,
out var filePath
);

HashInput = result && filePath != null
? await CalculateFileHashAsync(filePath)
: string.Empty;
}

public void Dispose()
{
_cancellationTokenSource.Cancel();
Expand Down
54 changes: 50 additions & 4 deletions src/Files.App/Views/Properties/HashesPage.xaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- Copyright (c) Files Community. Licensed under the MIT License. -->
<!-- Copyright (c) Files Community. Licensed under the MIT License. -->
<vm:BasePropertiesPage
x:Class="Files.App.Views.Properties.HashesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Expand All @@ -25,20 +25,66 @@
</vm:BasePropertiesPage.Resources>

<Grid x:Name="RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- Hash Comparison Section -->
<Grid
x:Name="HashesListGrid"
Margin="12"
x:Name="HashComparisonGrid"
Margin="12,12,12,4"
Padding="12"
VerticalAlignment="Top"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
ColumnSpacing="8"
CornerRadius="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<!-- Hash Input -->
<TextBox
x:Name="HashInputTextBox"
Grid.Column="0"
PlaceholderText="{helpers:ResourceString Name=EnterHashToCompare}"
Text="{x:Bind HashesViewModel.HashInput, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

<!-- Compare File Button -->
<Button
x:Name="CompareFileButton"
Grid.Column="1"
Command="{x:Bind HashesViewModel.CompareFileCommand}"
Content="{helpers:ResourceString Name=CompareFile}" />
</Grid>

<InfoBar
x:Name="HashMatchInfoBar"
Grid.Row="1"
Margin="12,4,12,4"
x:Load="{x:Bind HashesViewModel.IsInfoBarOpen, Mode=OneWay}"
IsClosable="False"
IsOpen="True"
Message="{x:Bind HashesViewModel.InfoBarTitle, Mode=OneWay}"
Severity="{x:Bind HashesViewModel.InfoBarSeverity, Mode=OneWay}" />

<Grid
x:Name="HashesListGrid"
Grid.Row="2"
Margin="12,4,12,12"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="4">

<!-- Hashes ListView -->
<ListView
x:Name="HashesListView"
ItemsSource="{x:Bind HashesViewModel.Hashes, Mode=TwoWay}"
ItemsSource="{x:Bind HashesViewModel.Hashes, Mode=OneWay}"
SelectedItem="{x:Bind HashesViewModel.SelectedItem, Mode=TwoWay}">

<!-- Header -->
Expand Down
12 changes: 5 additions & 7 deletions src/Files.App/Views/Properties/HashesPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
// Copyright (c) Files Community
// Copyright (c) Files Community
// Licensed under the MIT License.

using Files.App.Data.Parameters;
using Files.App.Utils;
using Files.App.ViewModels.Properties;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Navigation;
using System.Threading.Tasks;

namespace Files.App.Views.Properties
{
Expand All @@ -25,9 +22,10 @@ public HashesPage()

protected override void OnNavigatedTo(NavigationEventArgs e)
{
var np = (PropertiesPageNavigationParameter)e.Parameter;
if (np.Parameter is ListedItem listedItem)
HashesViewModel = new(listedItem);
var parameter = (PropertiesPageNavigationParameter)e.Parameter;

if (parameter.Parameter is ListedItem listedItem)
HashesViewModel = new(listedItem, parameter.Window.AppWindow);

base.OnNavigatedTo(e);
}
Expand Down
Loading