Hash functions

This commit is contained in:
2022-02-16 21:03:34 +01:00
parent 27a974e3e2
commit d1ceb95884
18 changed files with 240 additions and 39 deletions

View File

@@ -8,12 +8,14 @@ namespace FileTime.App.Core.Command
ChangeTimelineMode, ChangeTimelineMode,
CloseTab, CloseTab,
Copy, Copy,
CopyHash,
CopyPath, CopyPath,
CreateContainer, CreateContainer,
CreateElement, CreateElement,
Cut, Cut,
Edit, Edit,
EnterRapidTravel, EnterRapidTravel,
GetHash,
GoToHome, GoToHome,
GoToPath, GoToPath,
GoToProvider, GoToProvider,

View File

@@ -24,7 +24,7 @@ namespace FileTime.ConsoleUI.App.UI
foreach (var input in fields) foreach (var input in fields)
{ {
_application.MoveToIOLine(); _application.MoveToIOLine();
_coloredConsoleRenderer.Write(input.Text + ": "); _coloredConsoleRenderer.Write(input.Label + ": ");
results.Add(await _consoleReader.ReadText(placeHolder: input.InputType == InputType.Password ? '*' : null)); results.Add(await _consoleReader.ReadText(placeHolder: input.InputType == InputType.Password ? '*' : null));
} }

View File

@@ -2,15 +2,38 @@ namespace FileTime.Core.Interactions
{ {
public class InputElement public class InputElement
{ {
public string Text { get; } public string Label { get; }
public InputType InputType { get; } public InputType InputType { get; }
public string? DefaultValue { get; } public string? DefaultValue { get; }
public List<object>? Options { get; }
public InputElement(string text, InputType inputType, string? defaultValue = null) protected InputElement(string text, InputType inputType, string? defaultValue = null)
{ {
Text = text; Label = text;
InputType = inputType; InputType = inputType;
DefaultValue = defaultValue; DefaultValue = defaultValue;
} }
protected InputElement(string text, InputType inputType, List<object> defaultValue)
{
Label = text;
InputType = inputType;
Options = defaultValue;
}
public static InputElement ForText(string label, string? defaultValue = null)
{
return new InputElement(label, InputType.Text, defaultValue);
}
public static InputElement ForPassword(string label, string? defaultValue = null)
{
return new InputElement(label, InputType.Password, defaultValue);
}
public static InputElement ForOptions(string label, List<object> defaultValue)
{
return new InputElement(label, InputType.Options, defaultValue);
}
} }
} }

View File

@@ -2,8 +2,8 @@ namespace FileTime.Core.Interactions
{ {
public enum InputType public enum InputType
{ {
Text, Options,
Password, Password,
Bool Text,
} }
} }

View File

@@ -0,0 +1,61 @@
using System.Threading.Tasks;
namespace FileTime.Core.Providers
{
public class ContentProviderStream : Stream
{
private readonly IContentReader? _contentReader;
private readonly IContentWriter? _contentWriter;
public override bool CanRead => _contentReader == null;
public override bool CanSeek => false;
public override bool CanWrite => _contentWriter == null;
public override long Length => throw new NotImplementedException();
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public ContentProviderStream(IContentReader contentReader)
{
_contentReader = contentReader;
}
public ContentProviderStream(IContentWriter contentWriter)
{
_contentWriter = contentWriter;
}
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (_contentReader == null) throw new IOException("This stream is not readable");
var dataTask = Task.Run(async () => await _contentReader.ReadBytesAsync(count, offset));
dataTask.Wait();
var data = dataTask.Result;
if (data.Length > count) throw new Exception("More bytes has been read than requested");
Array.Copy(data, buffer, data.Length);
return data.Length;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
}
}

View File

@@ -4,6 +4,6 @@ namespace FileTime.Core.Providers
{ {
int PreferredBufferSize { get; } int PreferredBufferSize { get; }
Task<byte[]> ReadBytesAsync(int bufferSize); Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null);
} }
} }

View File

@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using FileTime.Core.Command; using FileTime.Core.Command;
namespace FileTime.Core.Timeline namespace FileTime.Core.Timeline
@@ -12,7 +13,7 @@ namespace FileTime.Core.Timeline
public CommandTimeState(ICommand command, PointInTime? startTime) public CommandTimeState(ICommand command, PointInTime? startTime)
{ {
Command = command; Command = command;
UpdateState(startTime).Wait(); Task.Run(async () => await UpdateState(startTime)).Wait();
} }
public async Task UpdateState(PointInTime? startPoint) public async Task UpdateState(PointInTime? startPoint)

View File

@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -136,7 +137,7 @@ namespace FileTime.Core.Timeline
if (arg is CommandTimeState commandToRun2) if (arg is CommandTimeState commandToRun2)
{ {
commandToRun = commandToRun2; commandToRun = commandToRun2;
_commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait(); Task.Run(async () => await _commandExecutor.ExecuteCommandAsync(commandToRun.Command, this)).Wait();
} }
} }
catch (Exception e) catch (Exception e)
@@ -150,7 +151,7 @@ namespace FileTime.Core.Timeline
} }
finally finally
{ {
DisposeCommandThread(Thread.CurrentThread, commandToRun).Wait(); Task.Run(async () => await DisposeCommandThread(Thread.CurrentThread, commandToRun)).Wait();
} }
} }
@@ -238,6 +239,6 @@ namespace FileTime.Core.Timeline
} }
} }
private void RunWithLock(Action action) => RunWithLockAsync(action).Wait(); private void RunWithLock(Action action) => Task.Run(async () => await RunWithLockAsync(action)).Wait();
} }
} }

View File

@@ -202,5 +202,25 @@
<Style Selector="Border.SelectedTimelineCommand"> <Style Selector="Border.SelectedTimelineCommand">
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}"/>
</Style> </Style>
<Style Selector="ListBox.RadioButtonListBox">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="ListBox.RadioButtonListBox ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border>
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
VerticalAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"
Foreground="{DynamicResource ForegroundBrush}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@@ -55,7 +55,7 @@ namespace FileTime.Avalonia.Application
{*/ {*/
/*var task = SetSelectedItemAsync(value, true); /*var task = SetSelectedItemAsync(value, true);
Task.WaitAll(new Task[] { task }, 100);*/ Task.WaitAll(new Task[] { task }, 100);*/
SetSelectedItemAsync(value, true); Task.Run(async () => await SetSelectedItemAsync(value, true)).Wait();
/*} /*}
catch catch
{ {

View File

@@ -46,6 +46,7 @@ namespace FileTime.Avalonia.Configuration
new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }), new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }),
new CommandBindingConfiguration(Commands.CloseTab, Key.Q), new CommandBindingConfiguration(Commands.CloseTab, Key.Q),
new CommandBindingConfiguration(Commands.Copy, new[] { Key.Y, Key.Y }), new CommandBindingConfiguration(Commands.Copy, new[] { Key.Y, Key.Y }),
new CommandBindingConfiguration(Commands.CopyHash, new[] { Key.C, Key.H }),
new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }), new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }),
new CommandBindingConfiguration(Commands.CreateContainer, Key.F7), new CommandBindingConfiguration(Commands.CreateContainer, Key.F7),
new CommandBindingConfiguration(Commands.CreateContainer, new[] { Key.C, Key.C }), new CommandBindingConfiguration(Commands.CreateContainer, new[] { Key.C, Key.C }),

View File

@@ -8,6 +8,8 @@ namespace FileTime.Avalonia.Misc
public string Value { get; set; } public string Value { get; set; }
public object? Option { get; set; }
public char? PasswordChar { get; set; } public char? PasswordChar { get; set; }
public InputElementWrapper(InputElement inputElement, string? defaultValue = null) public InputElementWrapper(InputElement inputElement, string? defaultValue = null)

View File

@@ -0,0 +1,10 @@
namespace FileTime.Avalonia.Models
{
public enum HashFunction
{
MD5,
SHA256,
SHA384,
SHA512,
}
}

View File

@@ -2,12 +2,14 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks; using System.Threading.Tasks;
using FileTime.App.Core.Clipboard; using FileTime.App.Core.Clipboard;
using FileTime.App.Core.Command; using FileTime.App.Core.Command;
using FileTime.Avalonia.Application; using FileTime.Avalonia.Application;
using FileTime.Avalonia.IconProviders; using FileTime.Avalonia.IconProviders;
using FileTime.Avalonia.Misc; using FileTime.Avalonia.Misc;
using FileTime.Avalonia.Models;
using FileTime.Avalonia.ViewModels; using FileTime.Avalonia.ViewModels;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Components; using FileTime.Core.Components;
@@ -65,6 +67,7 @@ namespace FileTime.Avalonia.Services
{Commands.ChangeTimelineMode, ChangeTimelineMode}, {Commands.ChangeTimelineMode, ChangeTimelineMode},
{Commands.CloseTab, CloseTab}, {Commands.CloseTab, CloseTab},
{Commands.Copy, Copy}, {Commands.Copy, Copy},
{Commands.CopyHash, CopyHash},
{Commands.CopyPath, CopyPath}, {Commands.CopyPath, CopyPath},
{Commands.CreateContainer, CreateContainer}, {Commands.CreateContainer, CreateContainer},
{Commands.CreateElement, CreateElement}, {Commands.CreateElement, CreateElement},
@@ -282,7 +285,7 @@ namespace FileTime.Avalonia.Services
} }
}; };
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Container name", InputType.Text) }, handler); _dialogService.ReadInputs(new List<InputElement>() { InputElement.ForText("Container name") }, handler);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -305,7 +308,7 @@ namespace FileTime.Avalonia.Services
} }
}; };
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Element name", InputType.Text) }, handler); _dialogService.ReadInputs(new List<InputElement>() { InputElement.ForText("Element name") }, handler);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -487,7 +490,7 @@ namespace FileTime.Avalonia.Services
await AddCommand(renameCommand); await AddCommand(renameCommand);
}; };
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("New name", InputType.Text, selectedItem.Name) }, handler); _dialogService.ReadInputs(new List<InputElement>() { InputElement.ForText("New name", selectedItem.Name) }, handler);
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -543,7 +546,7 @@ namespace FileTime.Avalonia.Services
} }
}; };
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Path", InputType.Text) }, handler); _dialogService.ReadInputs(new List<InputElement>() { InputElement.ForText("Path") }, handler);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -580,9 +583,17 @@ namespace FileTime.Avalonia.Services
var currentContainer = _appState.SelectedTab.CurrentLocation.Container; var currentContainer = _appState.SelectedTab.CurrentLocation.Container;
var textToCopy = currentContainer.NativePath; var textToCopy = currentContainer.NativePath;
if (textToCopy != null && global::Avalonia.Application.Current?.Clipboard is global::Avalonia.Input.Platform.IClipboard clipboard) if (textToCopy != null)
{ {
await clipboard.SetTextAsync(textToCopy); await CopyToClipboard(textToCopy);
}
}
private static async Task CopyToClipboard(string text)
{
if (global::Avalonia.Application.Current?.Clipboard is global::Avalonia.Input.Platform.IClipboard clipboard)
{
await clipboard.SetTextAsync(text);
} }
} }
@@ -633,7 +644,7 @@ namespace FileTime.Avalonia.Services
return Task.CompletedTask; return Task.CompletedTask;
}; };
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Command", InputType.Text) }, handler); _dialogService.ReadInputs(new List<InputElement>() { InputElement.ForText("Command") }, handler);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -840,5 +851,40 @@ namespace FileTime.Avalonia.Services
//TODO: else //TODO: else
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task CopyHash()
{
var handler = async (List<InputElementWrapper> inputs) =>
{
var hashFunction = (HashFunction?)inputs[0].Option;
if (hashFunction != null && _appState.SelectedTab.SelectedItem?.Item is IElement element && element.Provider.SupportsContentStreams)
{
using var stream = new ContentProviderStream(await element.GetContentReaderAsync());
string? hashString = null;
using HashAlgorithm hashFunc = hashFunction switch
{
HashFunction.MD5 => MD5.Create(),
HashFunction.SHA256 => SHA256.Create(),
HashFunction.SHA384 => SHA384.Create(),
HashFunction.SHA512 => SHA512.Create(),
_ => throw new NotImplementedException()
};
var hash = hashFunc.ComputeHash(stream);
hashString = string.Concat(hash.Select(b => b.ToString("X2")));
_dialogService.ShowToastMessage($"Hash copied ({hashString})");
await CopyToClipboard(hashString);
}
};
_dialogService.ReadInputs(new List<InputElement>()
{
InputElement.ForOptions("Hash function", Enum.GetValues<HashFunction>().Cast<object>().ToList())
}, handler);
return Task.CompletedTask;
}
} }
} }

View File

@@ -5,6 +5,7 @@
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:vm="using:FileTime.Avalonia.ViewModels" xmlns:vm="using:FileTime.Avalonia.ViewModels"
xmlns:interactions="using:FileTime.Core.Interactions"
xmlns:local="using:FileTime.Avalonia.Views" xmlns:local="using:FileTime.Avalonia.Views"
xmlns:models="using:FileTime.Avalonia.Models" xmlns:models="using:FileTime.Avalonia.Models"
Title="FileTime" Title="FileTime"
@@ -470,20 +471,35 @@
Items="{Binding AppState.Inputs}"> Items="{Binding AppState.Inputs}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid MinWidth="500" ColumnDefinitions="250,*" Margin="10,5"> <Grid MinWidth="500" ColumnDefinitions="250,*" Margin="10,5" x:Name="ItemRoot">
<TextBlock <TextBlock
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Top"
Text="{Binding InputElement.Text}" /> Text="{Binding InputElement.Label}" />
<Grid Grid.Column="1">
<TextBox <TextBox
PasswordChar="{Binding PasswordChar}" IsVisible="{Binding InputElement.InputType, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static interactions:InputType.Text}}"
AttachedToVisualTree="InputText_AttachedToVisualTree" AttachedToVisualTree="InputText_AttachedToVisualTree"
IsTabStop="True"
Grid.Column="1"
GotFocus="InputText_GotFocus" GotFocus="InputText_GotFocus"
LostFocus="InputText_LostFocus" LostFocus="InputText_LostFocus"
KeyDown="InputText_KeyDown" KeyDown="InputText_KeyDown"
VerticalAlignment="Top"
Text="{Binding Value, Mode=TwoWay}" /> Text="{Binding Value, Mode=TwoWay}" />
<TextBox
IsVisible="{Binding InputElement.InputType, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static interactions:InputType.Password}}"
PasswordChar="{Binding PasswordChar}"
AttachedToVisualTree="InputText_AttachedToVisualTree"
GotFocus="InputText_GotFocus"
LostFocus="InputText_LostFocus"
KeyDown="InputText_KeyDown"
VerticalAlignment="Top"
Text="{Binding Value, Mode=TwoWay}" />
<ListBox
Classes="RadioButtonListBox"
IsVisible="{Binding InputElement.InputType, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static interactions:InputType.Options}}"
Items="{Binding InputElement.Options}"
SelectedItem="{Binding Option}"/>
</Grid>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View File

@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using FileTime.Core.Providers; using FileTime.Core.Providers;
namespace FileTime.Providers.Local namespace FileTime.Providers.Local
@@ -9,6 +10,7 @@ namespace FileTime.Providers.Local
private bool disposed; private bool disposed;
public int PreferredBufferSize => 1024 * 1024; public int PreferredBufferSize => 1024 * 1024;
private long? _bytesRead;
public LocalContentReader(FileStream readerStream) public LocalContentReader(FileStream readerStream)
{ {
@@ -16,12 +18,28 @@ namespace FileTime.Providers.Local
_binaryReader = new BinaryReader(_readerStream); _binaryReader = new BinaryReader(_readerStream);
} }
public Task<byte[]> ReadBytesAsync(int bufferSize) public Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null)
{ {
var max = bufferSize > 0 && bufferSize < PreferredBufferSize ? bufferSize : PreferredBufferSize; var max = bufferSize > 0 && bufferSize < PreferredBufferSize ? bufferSize : PreferredBufferSize;
if (offset != null)
{
if (_bytesRead == null) _bytesRead = 0;
var buffer = new byte[max];
var bytesRead = _binaryReader.Read(buffer, offset.Value, max);
_bytesRead += bytesRead;
if (buffer.Length != bytesRead)
{
Array.Resize(ref buffer, bytesRead);
}
return Task.FromResult(buffer);
}
else
{
return Task.FromResult(_binaryReader.ReadBytes(max)); return Task.FromResult(_binaryReader.ReadBytes(max));
} }
}
~LocalContentReader() ~LocalContentReader()
{ {

View File

@@ -21,11 +21,11 @@ namespace FileTime.Providers.Smb
_client = client; _client = client;
} }
public Task<byte[]> ReadBytesAsync(int bufferSize) public Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null)
{ {
var max = bufferSize > 0 && bufferSize < (int)_client.MaxReadSize ? bufferSize : (int)_client.MaxReadSize; var max = bufferSize > 0 && bufferSize < (int)_client.MaxReadSize ? bufferSize : (int)_client.MaxReadSize;
var status = _smbFileStore.ReadFile(out byte[] data, _fileHandle, _bytesRead, max); var status = _smbFileStore.ReadFile(out byte[] data, _fileHandle, offset ?? _bytesRead, max);
if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE) if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE)
{ {
throw new Exception("Failed to read from file"); throw new Exception("Failed to read from file");

View File

@@ -195,8 +195,8 @@ namespace FileTime.Providers.Smb
var inputs = await _inputInterface.ReadInputs( var inputs = await _inputInterface.ReadInputs(
new InputElement[] new InputElement[]
{ {
new InputElement($"Username for '{Name}'", InputType.Text, Username ?? ""), InputElement.ForText($"Username for '{Name}'", Username ?? ""),
new InputElement($"Password for '{Name}'", InputType.Password, Password ?? "") InputElement.ForPassword($"Password for '{Name}'", Password ?? "")
}); });
Username = inputs[0]; Username = inputs[0];