1- // Copyright (c) Files Community
1+ // Copyright (c) Files Community
22// Licensed under the MIT License.
33
44using Files . Shared . Helpers ;
5+ using Microsoft . UI . Windowing ;
6+ using Microsoft . UI . Xaml . Controls ;
57using System . IO ;
8+ using System . Security . Cryptography ;
69using System . Windows . Input ;
710
811namespace Files . App . ViewModels . Properties
912{
1013 public sealed partial class HashesViewModel : ObservableObject , IDisposable
1114 {
15+ private ICommonDialogService CommonDialogService { get ; } = Ioc . Default . GetRequiredService < ICommonDialogService > ( ) ;
1216 private IUserSettingsService UserSettingsService { get ; } = Ioc . Default . GetService < IUserSettingsService > ( ) ! ;
1317
18+ private readonly AppWindow _appWindow ;
19+
1420 private HashInfoItem _selectedItem ;
1521 public HashInfoItem SelectedItem
1622 {
@@ -23,16 +29,48 @@ public HashInfoItem SelectedItem
2329 public Dictionary < string , bool > ShowHashes { get ; private set ; }
2430
2531 public ICommand ToggleIsEnabledCommand { get ; private set ; }
32+ public ICommand CompareFileCommand { get ; private set ; }
2633
2734 private ListedItem _item ;
2835
2936 private CancellationTokenSource _cancellationTokenSource ;
3037
31- public HashesViewModel ( ListedItem item )
38+ private string _hashInput ;
39+ public string HashInput
40+ {
41+ get => _hashInput ;
42+ set
43+ {
44+ SetProperty ( ref _hashInput , value ) ;
45+
46+ OnHashInputTextChanged ( ) ;
47+ OnPropertyChanged ( nameof ( IsInfoBarOpen ) ) ;
48+ }
49+ }
50+
51+ private InfoBarSeverity _infoBarSeverity ;
52+ public InfoBarSeverity InfoBarSeverity
53+ {
54+ get => _infoBarSeverity ;
55+ set => SetProperty ( ref _infoBarSeverity , value ) ;
56+ }
57+
58+ private string _infoBarTitle ;
59+ public string InfoBarTitle
60+ {
61+ get => _infoBarTitle ;
62+ set => SetProperty ( ref _infoBarTitle , value ) ;
63+ }
64+
65+ public bool IsInfoBarOpen
66+ => ! string . IsNullOrEmpty ( HashInput ) ;
67+
68+ public HashesViewModel ( ListedItem item , AppWindow appWindow )
3269 {
3370 ToggleIsEnabledCommand = new RelayCommand < string > ( ToggleIsEnabled ) ;
3471
3572 _item = item ;
73+ _appWindow = appWindow ;
3674 _cancellationTokenSource = new ( ) ;
3775
3876 Hashes =
@@ -55,6 +93,8 @@ public HashesViewModel(ListedItem item)
5593 ShowHashes . TryAdd ( "SHA512" , false ) ;
5694
5795 Hashes . Where ( x => ShowHashes [ x . Algorithm ] ) . ForEach ( x => ToggleIsEnabledCommand . Execute ( x . Algorithm ) ) ;
96+
97+ CompareFileCommand = new RelayCommand ( async ( ) => await OnCompareFileAsync ( ) ) ;
5898 }
5999
60100 private void ToggleIsEnabled ( string ? algorithm )
@@ -71,7 +111,7 @@ private void ToggleIsEnabled(string? algorithm)
71111 // Don't calculate hashes for online files
72112 if ( _item . SyncStatusUI . SyncStatus is CloudDriveSyncStatus . FileOnline or CloudDriveSyncStatus . FolderOnline )
73113 {
74- hashInfoItem . HashValue = " CalculationOnlineFileHashError" . GetLocalizedResource ( ) ;
114+ hashInfoItem . HashValue = Strings . CalculationOnlineFileHashError . GetLocalizedResource ( ) ;
75115 return ;
76116 }
77117
@@ -106,11 +146,11 @@ private void ToggleIsEnabled(string? algorithm)
106146 catch ( IOException )
107147 {
108148 // File is currently open
109- hashInfoItem . HashValue = " CalculationErrorFileIsOpen" . GetLocalizedResource ( ) ;
149+ hashInfoItem . HashValue = Strings . CalculationErrorFileIsOpen . GetLocalizedResource ( ) ;
110150 }
111151 catch ( Exception )
112152 {
113- hashInfoItem . HashValue = " CalculationError" . GetLocalizedResource ( ) ;
153+ hashInfoItem . HashValue = Strings . CalculationError . GetLocalizedResource ( ) ;
114154 }
115155 finally
116156 {
@@ -120,6 +160,51 @@ private void ToggleIsEnabled(string? algorithm)
120160 }
121161 }
122162
163+ public string FindMatchingAlgorithm ( string hash )
164+ {
165+ if ( string . IsNullOrEmpty ( hash ) )
166+ return string . Empty ;
167+
168+ return Hashes . FirstOrDefault ( h => h . HashValue ? . Equals ( hash , StringComparison . OrdinalIgnoreCase ) == true ) ? . Algorithm ?? string . Empty ;
169+ }
170+
171+ public async Task < string > CalculateFileHashAsync ( string filePath )
172+ {
173+ using var stream = File . OpenRead ( filePath ) ;
174+ using var md5 = MD5 . Create ( ) ;
175+ var hash = await Task . Run ( ( ) => md5 . ComputeHash ( stream ) ) ;
176+ return BitConverter . ToString ( hash ) . Replace ( "-" , "" ) . ToLowerInvariant ( ) ;
177+ }
178+
179+ private void OnHashInputTextChanged ( )
180+ {
181+ string matchingAlgorithm = FindMatchingAlgorithm ( HashInput ) ;
182+
183+ InfoBarSeverity = string . IsNullOrEmpty ( matchingAlgorithm )
184+ ? InfoBarSeverity . Error
185+ : InfoBarSeverity . Success ;
186+
187+ InfoBarTitle = string . IsNullOrEmpty ( matchingAlgorithm )
188+ ? Strings . HashesDoNotMatch . GetLocalizedResource ( )
189+ : string . Format ( Strings . HashesMatch . GetLocalizedResource ( ) , matchingAlgorithm ) ;
190+ }
191+
192+ private async Task OnCompareFileAsync ( )
193+ {
194+ var hWnd = Microsoft . UI . Win32Interop . GetWindowFromWindowId ( _appWindow . Id ) ;
195+
196+ var result = CommonDialogService . Open_FileOpenDialog (
197+ hWnd ,
198+ false ,
199+ [ ] ,
200+ Environment . SpecialFolder . Desktop ,
201+ out var filePath ) ;
202+
203+ HashInput = result && filePath != null
204+ ? await CalculateFileHashAsync ( filePath )
205+ : string . Empty ;
206+ }
207+
123208 public void Dispose ( )
124209 {
125210 _cancellationTokenSource . Cancel ( ) ;
0 commit comments