viewmodels, uis, ecc.
This commit is contained in:
parent
bda455b7e3
commit
1d706b1adc
@ -12,6 +12,9 @@ namespace HashCalculator.GUI
|
|||||||
public IEnumerable<string> DisplayLabels { get; }
|
public IEnumerable<string> DisplayLabels { get; }
|
||||||
public uint Hash => SageHash.CalculateLowercaseHash(Name);
|
public uint Hash => SageHash.CalculateLowercaseHash(Name);
|
||||||
|
|
||||||
|
public string IdString => $"{Type}:{Name}";
|
||||||
|
public string HashString => $"{Hash:0X} ({Hash})";
|
||||||
|
|
||||||
public AssetEntry(XElement element)
|
public AssetEntry(XElement element)
|
||||||
{
|
{
|
||||||
if (element == null)
|
if (element == null)
|
||||||
@ -26,12 +29,7 @@ namespace HashCalculator.GUI
|
|||||||
|
|
||||||
Type = element.Name.LocalName;
|
Type = element.Name.LocalName;
|
||||||
var id = element.Attribute("id")?.Value;
|
var id = element.Attribute("id")?.Value;
|
||||||
if (id == null)
|
Name = id ?? throw new NotSupportedException();
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
Name = id;
|
|
||||||
|
|
||||||
var labels = from name in element.Elements(ModXml.EalaAsset + "DisplayName")
|
var labels = from name in element.Elements(ModXml.EalaAsset + "DisplayName")
|
||||||
select name.Value;
|
select name.Value;
|
||||||
|
131
Command.cs
Normal file
131
Command.cs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI
|
||||||
|
{
|
||||||
|
internal class Command<T> : ICommand
|
||||||
|
{
|
||||||
|
private readonly Func<T, Task> _action;
|
||||||
|
private readonly Action<Exception> _onAsyncException;
|
||||||
|
private bool _canExecute = true;
|
||||||
|
|
||||||
|
public bool CanExecuteValue
|
||||||
|
{
|
||||||
|
get => _canExecute;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_canExecute = value;
|
||||||
|
CanExecuteChanged?.Invoke(this, new EventArgs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler? CanExecuteChanged;
|
||||||
|
|
||||||
|
public Command(Func<T, Task> action, Action<Exception>? onAsyncException = null)
|
||||||
|
{
|
||||||
|
_action = action;
|
||||||
|
_onAsyncException = onAsyncException ?? (exception =>
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Unhandled async exception in {nameof(Command<T>)}: {exception}");
|
||||||
|
Application.Current.Shutdown(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanExecute(object parameter)
|
||||||
|
{
|
||||||
|
return _canExecute;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Globalization", "CA1303:请不要将文本作为本地化参数传递", Justification = "<挂起>")]
|
||||||
|
public void Execute(object parameter)
|
||||||
|
{
|
||||||
|
if (!_canExecute)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!(parameter is T typed))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"{nameof(parameter)} wrong type");
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecuteTaskInternal(ExecuteTask(typed));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ExecuteTask(T parameter) => _action(parameter);
|
||||||
|
|
||||||
|
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||||
|
private async void ExecuteTaskInternal(Task task)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await task.ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_onAsyncException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Command : ICommand
|
||||||
|
{
|
||||||
|
private readonly Func<Task> _action;
|
||||||
|
private readonly Action<Exception> _onAsyncException;
|
||||||
|
private bool _canExecute = true;
|
||||||
|
|
||||||
|
public bool CanExecuteValue
|
||||||
|
{
|
||||||
|
get => _canExecute;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_canExecute = value;
|
||||||
|
CanExecuteChanged?.Invoke(this, new EventArgs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler? CanExecuteChanged;
|
||||||
|
|
||||||
|
public Command(Func<Task> action, Action<Exception>? onAsyncException = null)
|
||||||
|
{
|
||||||
|
_action = action;
|
||||||
|
_onAsyncException = onAsyncException ?? (exception =>
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Unhandled async exception in {nameof(Command)}: {exception}");
|
||||||
|
Application.Current.Shutdown(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanExecute(object parameter)
|
||||||
|
{
|
||||||
|
return _canExecute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(object parameter)
|
||||||
|
{
|
||||||
|
if (!_canExecute)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecuteTaskInternal(ExecuteTask());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ExecuteTask() => _action();
|
||||||
|
|
||||||
|
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||||
|
private async void ExecuteTaskInternal(Task task)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await task.ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_onAsyncException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Converters/BooleanInvertConverter.cs
Normal file
22
Converters/BooleanInvertConverter.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI.Converters
|
||||||
|
{
|
||||||
|
[ValueConversion(typeof(bool), typeof(bool))]
|
||||||
|
internal class BooleanInvertConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool original = (bool)value;
|
||||||
|
return !original;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool original = (bool)value;
|
||||||
|
return !original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Converters/NullToBooleanConverter.cs
Normal file
28
Converters/NullToBooleanConverter.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI.Converters
|
||||||
|
{
|
||||||
|
[ValueConversion(typeof(object), typeof(bool))]
|
||||||
|
public class NullToBooleanConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if(value is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(value is string s)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(s);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
Converters/ValidInputEntryTypeToBooleanConverter.cs
Normal file
21
Converters/ValidInputEntryTypeToBooleanConverter.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI.Converters
|
||||||
|
{
|
||||||
|
[ValueConversion(typeof(InputEntry), typeof(bool))]
|
||||||
|
internal class ValidInputEntryTypeToBooleanConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
var entry = value as InputEntry;
|
||||||
|
return entry?.IsValid == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows;
|
using System.Linq;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
namespace HashCalculator.GUI.Converters
|
||||||
{
|
{
|
||||||
[ValueConversion(typeof(string), typeof(Visibility))]
|
internal class ValueConverterAggregate : List<IValueConverter>, IValueConverter
|
||||||
public class NullToVisibilityConverter : IValueConverter
|
|
||||||
{
|
{
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
var text = value as string;
|
return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));
|
||||||
return string.IsNullOrEmpty(text) ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
50
CopyableBox.xaml
Normal file
50
CopyableBox.xaml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="HashCalculator.GUI.CopyableBox"
|
||||||
|
x:Name="Self"
|
||||||
|
x:ClassModifier="internal"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:HashCalculator.GUI"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="CopyableBox" Height="350" Width="600"
|
||||||
|
Closing="ClosingHandler"
|
||||||
|
>
|
||||||
|
<Window.DataContext>
|
||||||
|
<local:CopyableBoxViewModel />
|
||||||
|
</Window.DataContext>
|
||||||
|
<Grid Margin="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="60"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Button
|
||||||
|
Content="复制所有内容"
|
||||||
|
Command="{Binding CopyCommand}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="10,0,0,10"
|
||||||
|
Width="120"
|
||||||
|
Height="30"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
Content="{Binding CloseButtonText}"
|
||||||
|
Command="{Binding CloseCommand}"
|
||||||
|
CommandParameter="{Binding ElementName=Self}"
|
||||||
|
Margin="0,0,10,10"
|
||||||
|
Grid.Row="1"
|
||||||
|
Width="100"
|
||||||
|
Height="30"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
/>
|
||||||
|
<TextBox
|
||||||
|
Grid.Row="0"
|
||||||
|
Text="{Binding Text, Mode=OneWay}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
166
CopyableBox.xaml.cs
Normal file
166
CopyableBox.xaml.cs
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using TechnologyAssembler.Core.Extensions;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CopyableBox.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
|
internal partial class CopyableBox : Window, IDisposable
|
||||||
|
{
|
||||||
|
private CopyableBoxViewModel ViewModel => (CopyableBoxViewModel)DataContext;
|
||||||
|
|
||||||
|
public static void ShowDialog(Func<CancellationToken, Task<string>> action)
|
||||||
|
{
|
||||||
|
using var box = new CopyableBox();
|
||||||
|
box.ViewModel.Initialize(action, box.Dispatcher);
|
||||||
|
box.ShowDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CopyableBox()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
ViewModel.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClosingHandler(object sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel.ReadyToClose)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Cancel = true;
|
||||||
|
if (ViewModel.CloseCommand.CanExecuteValue)
|
||||||
|
{
|
||||||
|
ViewModel.CloseCommand.Execute(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
|
internal class CopyableBoxViewModel : NotifyPropertyChanged, IDisposable
|
||||||
|
{
|
||||||
|
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
private Task? _task;
|
||||||
|
|
||||||
|
public const string InitialMessage = "正在加载…… 关闭窗口就可以取消加载(";
|
||||||
|
|
||||||
|
private string _text = InitialMessage;
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => _text;
|
||||||
|
private set => SetField(ref _text, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CloseButtonText => CloseCommand.CanExecuteValue ? "关闭窗口" : "正在关闭…";
|
||||||
|
private bool _readyToClose = false;
|
||||||
|
public bool ReadyToClose
|
||||||
|
{
|
||||||
|
get => _readyToClose;
|
||||||
|
private set => SetField(ref _readyToClose, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command CopyCommand { get; }
|
||||||
|
public Command<CopyableBox> CloseCommand { get; }
|
||||||
|
|
||||||
|
public CopyableBoxViewModel()
|
||||||
|
{
|
||||||
|
CopyCommand = new Command(() =>
|
||||||
|
{
|
||||||
|
Clipboard.SetText(Text);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
CloseCommand = new Command<CopyableBox>(async window =>
|
||||||
|
{
|
||||||
|
CloseCommand.CanExecuteValue = false;
|
||||||
|
Notify(nameof(CloseButtonText));
|
||||||
|
|
||||||
|
if (_task != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Cancel();
|
||||||
|
await _task.ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadyToClose = true;
|
||||||
|
window.Close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Cancel();
|
||||||
|
_cancellationTokenSource.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(Func<CancellationToken, Task<string>> action, Dispatcher dispatcher)
|
||||||
|
{
|
||||||
|
_task = InitializationTask(action, dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitializationTask(Func<CancellationToken, Task<string>> action, Dispatcher dispatcher)
|
||||||
|
{
|
||||||
|
var utcBegin = DateTimeOffset.UtcNow;
|
||||||
|
var timer = new DispatcherTimer(TimeSpan.FromMilliseconds(200), DispatcherPriority.Normal, (s, e) =>
|
||||||
|
{
|
||||||
|
var timeElapsed = DateTimeOffset.UtcNow - utcBegin;
|
||||||
|
if (((DispatcherTimer)s).IsEnabled && timeElapsed.TotalSeconds > 1)
|
||||||
|
{
|
||||||
|
Text = Text = $"{InitialMessage}\r\n目前耗时{timeElapsed},稍微再等一下吧233";
|
||||||
|
}
|
||||||
|
}, dispatcher);
|
||||||
|
timer.Start();
|
||||||
|
|
||||||
|
var token = _cancellationTokenSource.Token;
|
||||||
|
async Task<string> Action()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await action(token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return "操作已被取消";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
timer.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text = await dispatcher.Invoke(Action).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,11 @@ namespace HashCalculator.GUI
|
|||||||
|
|
||||||
path = Environment.ExpandEnvironmentVariables(path);
|
path = Environment.ExpandEnvironmentVariables(path);
|
||||||
|
|
||||||
|
if (path.IndexOfAny(Path.GetInvalidPathChars()) != -1)
|
||||||
|
{
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
|
||||||
_search.Update(path);
|
_search.Update(path);
|
||||||
|
|
||||||
if (!_search.IsValidPath)
|
if (!_search.IsValidPath)
|
||||||
@ -37,7 +42,7 @@ namespace HashCalculator.GUI
|
|||||||
return empty;
|
return empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentFiles = empty;
|
var currentFiles = new List<InputEntry>();
|
||||||
string? fileName;
|
string? fileName;
|
||||||
string? currentFullPath;
|
string? currentFullPath;
|
||||||
try
|
try
|
||||||
@ -46,13 +51,12 @@ namespace HashCalculator.GUI
|
|||||||
fileName = Path.GetFileName(path);
|
fileName = Path.GetFileName(path);
|
||||||
if (File.Exists(currentFullPath))
|
if (File.Exists(currentFullPath))
|
||||||
{
|
{
|
||||||
|
|
||||||
var type = CheckExtension(currentFullPath);
|
var type = CheckExtension(currentFullPath);
|
||||||
if (type.HasValue)
|
if(type is InputEntryType entryType)
|
||||||
{
|
{
|
||||||
currentFiles.Append(new InputEntry(type.Value, path, currentFullPath));
|
currentFiles.Add(new InputEntry(entryType, path, currentFullPath));
|
||||||
}
|
}
|
||||||
currentFiles.Append(new InputEntry(InputEntryType.BinaryFile, path, currentFullPath));
|
currentFiles.Add(new InputEntry(InputEntryType.BinaryFile, path, currentFullPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -60,34 +64,31 @@ namespace HashCalculator.GUI
|
|||||||
return empty;
|
return empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var directories = from directory in _search.AllDirectories
|
var alternatives = empty;
|
||||||
where directory.Name.StartsWith(fileName, StringComparison.OrdinalIgnoreCase)
|
|
||||||
select new InputEntry(InputEntryType.Path, _search.GetInputStyleName(directory), directory.FullName);
|
|
||||||
|
|
||||||
var otherFiles = from file in _search.AllFiles
|
|
||||||
where file.Name.StartsWith(fileName, StringComparison.OrdinalIgnoreCase)
|
|
||||||
where file.FullName != currentFullPath
|
|
||||||
select file;
|
|
||||||
|
|
||||||
var supportedFiles = empty;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
supportedFiles = from file in otherFiles
|
var directories = from directory in _search.AllDirectories
|
||||||
let type = CheckExtension(file.Extension)
|
where directory.Name.StartsWith(fileName, StringComparison.OrdinalIgnoreCase)
|
||||||
where type.HasValue
|
select new InputEntry(InputEntryType.Path, _search.GetInputStyleName(directory), directory.FullName);
|
||||||
select new InputEntry(type.Value, _search.GetInputStyleName(file), file.FullName);
|
|
||||||
|
var otherFiles = from file in _search.AllFiles
|
||||||
|
where file.Name.StartsWith(fileName, StringComparison.OrdinalIgnoreCase)
|
||||||
|
where file.FullName != currentFullPath
|
||||||
|
select file;
|
||||||
|
|
||||||
|
var supportedFiles = from file in otherFiles
|
||||||
|
let type = CheckExtension(file.Extension)
|
||||||
|
where type.HasValue
|
||||||
|
select new InputEntry(type.Value, _search.GetInputStyleName(file), file.FullName);
|
||||||
|
|
||||||
|
var binaryFiles = from file in otherFiles
|
||||||
|
select new InputEntry(InputEntryType.BinaryFile, _search.GetInputStyleName(file), file.FullName);
|
||||||
|
|
||||||
|
alternatives = supportedFiles.Concat(directories).Concat(binaryFiles).ToArray();
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
var binaryFiles = empty;
|
return currentFiles.Concat(alternatives);
|
||||||
try
|
|
||||||
{
|
|
||||||
binaryFiles = from file in otherFiles
|
|
||||||
select new InputEntry(InputEntryType.BinaryFile, _search.GetInputStyleName(file), file.FullName);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
return currentFiles.Concat(supportedFiles).Concat(directories).Concat(binaryFiles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InputEntryType? CheckExtension(string path)
|
private static InputEntryType? CheckExtension(string path)
|
||||||
@ -130,6 +131,12 @@ namespace HashCalculator.GUI
|
|||||||
string? newBaseDirectory = null;
|
string? newBaseDirectory = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (Path.GetFileName(path).IndexOfAny(Path.GetInvalidFileNameChars()) != -1)
|
||||||
|
{
|
||||||
|
_inputBaseDirectory = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
newBaseDirectory = Path.GetDirectoryName(path);
|
newBaseDirectory = Path.GetDirectoryName(path);
|
||||||
var rootDirectory = Path.GetPathRoot(path);
|
var rootDirectory = Path.GetPathRoot(path);
|
||||||
if(string.IsNullOrEmpty(newBaseDirectory) && !string.IsNullOrEmpty(rootDirectory))
|
if(string.IsNullOrEmpty(newBaseDirectory) && !string.IsNullOrEmpty(rootDirectory))
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="System.Windows.Presentation" />
|
||||||
<Reference Include="TechnologyAssembler.Core">
|
<Reference Include="TechnologyAssembler.Core">
|
||||||
<HintPath>TechnologyAssembler.Core.dll</HintPath>
|
<HintPath>TechnologyAssembler.Core.dll</HintPath>
|
||||||
<Private>true</Private>
|
<Private>true</Private>
|
||||||
|
@ -5,13 +5,18 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:HashCalculator.GUI"
|
xmlns:local="clr-namespace:HashCalculator.GUI"
|
||||||
|
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:ClassModifier="internal"
|
x:ClassModifier="internal"
|
||||||
x:Name="_this"
|
x:Name="_this"
|
||||||
d:DesignHeight="100" d:DesignWidth="800"
|
d:DesignHeight="100" d:DesignWidth="800"
|
||||||
>
|
>
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
<c:ValueConverterAggregate x:Key="NullToVisibilityConverter">
|
||||||
|
<c:NullToBooleanConverter />
|
||||||
|
<c:BooleanInvertConverter />
|
||||||
|
<BooleanToVisibilityConverter />
|
||||||
|
</c:ValueConverterAggregate>
|
||||||
<Style
|
<Style
|
||||||
x:Key="BlankListBoxContainerStyle"
|
x:Key="BlankListBoxContainerStyle"
|
||||||
TargetType="{x:Type ListBoxItem}"
|
TargetType="{x:Type ListBoxItem}"
|
||||||
@ -72,7 +77,6 @@
|
|||||||
PreviewKeyDown="OnPreviewKeyDown"
|
PreviewKeyDown="OnPreviewKeyDown"
|
||||||
Padding="2, 0"
|
Padding="2, 0"
|
||||||
VerticalContentAlignment="Center"
|
VerticalContentAlignment="Center"
|
||||||
TextChanged="OnTextChanged"
|
|
||||||
LostFocus="OnLostFocus"
|
LostFocus="OnLostFocus"
|
||||||
/>
|
/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@ -98,14 +102,28 @@
|
|||||||
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="OnSelectionChanged"
|
SelectionChanged="OnSelectionChanged"
|
||||||
|
SelectedItem="{Binding Path=SelectedItem,
|
||||||
|
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
||||||
|
SelectedIndex="{Binding Path=SelectedIndex,
|
||||||
|
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
||||||
VirtualizingPanel.IsVirtualizing="True"
|
VirtualizingPanel.IsVirtualizing="True"
|
||||||
VirtualizingPanel.VirtualizationMode="Recycling"
|
VirtualizingPanel.VirtualizationMode="Recycling"
|
||||||
>
|
>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<TextBlock Text="{Binding Text}" FontWeight="Bold"></TextBlock>
|
<TextBlock Text="{Binding Text}"></TextBlock>
|
||||||
<TextBlock Text="{Binding Type}"></TextBlock>
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
>
|
||||||
|
<StackPanel.Resources>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="Gray" />
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Resources>
|
||||||
|
<TextBlock Text="{Binding Type}"></TextBlock>
|
||||||
|
<TextBlock Text="{Binding Details}" Margin="10,0,0,0"></TextBlock>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
|
@ -82,7 +82,22 @@ namespace HashCalculator.GUI
|
|||||||
set => SetValue(SelectedItemProperty, value);
|
set => SetValue(SelectedItemProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event TextChangedEventHandler? TextChanged;
|
public static readonly DependencyProperty SelectedIndexProperty =
|
||||||
|
DependencyProperty.Register(nameof(SelectedIndex), typeof(int), typeof(InputBar), new FrameworkPropertyMetadata
|
||||||
|
{
|
||||||
|
BindsTwoWayByDefault = true,
|
||||||
|
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
|
||||||
|
CoerceValueCallback = (d, baseObject) =>
|
||||||
|
{
|
||||||
|
var self = (InputBar)d;
|
||||||
|
return self.NormalizeIndex((int)baseObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
public int SelectedIndex
|
||||||
|
{
|
||||||
|
get => (int)GetValue(SelectedIndexProperty);
|
||||||
|
set => SetValue(SelectedIndexProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
public InputBar()
|
public InputBar()
|
||||||
{
|
{
|
||||||
@ -94,12 +109,12 @@ namespace HashCalculator.GUI
|
|||||||
switch (e.Key)
|
switch (e.Key)
|
||||||
{
|
{
|
||||||
case Key.Down:
|
case Key.Down:
|
||||||
ListBox.SelectedIndex = NormalizeIndex(ListBox.SelectedIndex + 1);
|
SelectedIndex += 1;
|
||||||
ListBox.ScrollIntoView(ListBox.SelectedItem);
|
ListBox.ScrollIntoView(ListBox.SelectedItem);
|
||||||
DropDown.IsOpen = true;
|
DropDown.IsOpen = true;
|
||||||
break;
|
break;
|
||||||
case Key.Up:
|
case Key.Up:
|
||||||
ListBox.SelectedIndex = NormalizeIndex(ListBox.SelectedIndex - 1);
|
SelectedIndex -= 1;
|
||||||
ListBox.ScrollIntoView(ListBox.SelectedItem);
|
ListBox.ScrollIntoView(ListBox.SelectedItem);
|
||||||
break;
|
break;
|
||||||
case Key.Escape:
|
case Key.Escape:
|
||||||
@ -114,7 +129,6 @@ namespace HashCalculator.GUI
|
|||||||
|
|
||||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
SelectedItem = (InputEntry)ListBox.SelectedItem;
|
|
||||||
if (SelectedItem != null)
|
if (SelectedItem != null)
|
||||||
{
|
{
|
||||||
Text = SelectedItem.ToString();
|
Text = SelectedItem.ToString();
|
||||||
@ -122,14 +136,6 @@ namespace HashCalculator.GUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTextChanged(object sender, TextChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (Text != SelectedItem?.ToString())
|
|
||||||
{
|
|
||||||
TextChanged?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLostFocus(object sender, RoutedEventArgs e)
|
private void OnLostFocus(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
DropDown.IsOpen = false;
|
DropDown.IsOpen = false;
|
||||||
@ -145,6 +151,5 @@ namespace HashCalculator.GUI
|
|||||||
return Math.Min(Math.Max(rawIndex, -1), Collection.Count() - 1);
|
return Math.Min(Math.Max(rawIndex, -1), Collection.Count() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,34 @@ namespace HashCalculator.GUI
|
|||||||
|
|
||||||
internal sealed class InputEntry
|
internal sealed class InputEntry
|
||||||
{
|
{
|
||||||
|
public static IReadOnlyDictionary<InputEntryType, string> Descriptions = new Dictionary<InputEntryType, string>
|
||||||
|
{
|
||||||
|
{ InputEntryType.BigFile, "可以尝试读取这个 big 文件里的 manifest 文件" },
|
||||||
|
{ InputEntryType.BinaryFile, "可以计算这个文件的哈希值" },
|
||||||
|
{ InputEntryType.ManifestFile, "可以尝试读取这个 manifest 文件,显示各个素材的哈希值" },
|
||||||
|
{ InputEntryType.XmlFile, "可以尝试读取这个XML,显示 XML 里定义的各个素材的哈希值" },
|
||||||
|
{ InputEntryType.Path, string.Empty },
|
||||||
|
};
|
||||||
|
|
||||||
public InputEntryType Type { get; }
|
public InputEntryType Type { get; }
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
public string Text { get; }
|
public string Text { get; }
|
||||||
|
public string Details
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(Type == InputEntryType.Text)
|
||||||
|
{
|
||||||
|
var hash = SageHash.CalculateLowercaseHash(Value);
|
||||||
|
var binaryHash = SageHash.CalculateBinaryHash(Value);
|
||||||
|
return hash == binaryHash
|
||||||
|
? $"这段文字的哈希值:{hash:x8} ({hash})"
|
||||||
|
: $"这段文字的哈希值:{hash:x8};大小写敏感哈希值 {binaryHash:x8}";
|
||||||
|
}
|
||||||
|
return Descriptions[Type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool IsValid => Type != InputEntryType.Text && Type != InputEntryType.Path;
|
||||||
|
|
||||||
public InputEntry(InputEntryType type, string text, string value)
|
public InputEntry(InputEntryType type, string text, string value)
|
||||||
{
|
{
|
||||||
@ -34,6 +59,5 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
return Text;
|
return Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
233
MainWindow.xaml
233
MainWindow.xaml
@ -1,71 +1,228 @@
|
|||||||
<Window x:Class="HashCalculator.GUI.MainWindow"
|
<Window
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
x:Class="HashCalculator.GUI.MainWindow"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
x:Name="Self"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:l="clr-namespace:HashCalculator.GUI"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
mc:Ignorable="d"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
Title="Sage FastHash 哈希计算器" Height="600" Width="600">
|
xmlns:l="clr-namespace:HashCalculator.GUI"
|
||||||
|
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Sage FastHash 哈希计算器" Height="600" Width="600"
|
||||||
|
Background="#202020"
|
||||||
|
Foreground="LightGray"
|
||||||
|
>
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<l:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
<c:ValueConverterAggregate x:Key="ValidInputEntryToVisibilityConverter">
|
||||||
|
<c:ValidInputEntryTypeToBooleanConverter />
|
||||||
|
<BooleanToVisibilityConverter />
|
||||||
|
</c:ValueConverterAggregate>
|
||||||
|
<c:ValueConverterAggregate x:Key="InvalidInputEntryToVisibilityConverter">
|
||||||
|
<c:ValidInputEntryTypeToBooleanConverter />
|
||||||
|
<c:BooleanInvertConverter />
|
||||||
|
<BooleanToVisibilityConverter />
|
||||||
|
</c:ValueConverterAggregate>
|
||||||
|
<Style x:Key="CommonStyle" TargetType="Control">
|
||||||
|
<Setter
|
||||||
|
Property="Foreground"
|
||||||
|
Value="{Binding Path=(TextElement.Foreground),
|
||||||
|
RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}}"
|
||||||
|
/>
|
||||||
|
<Setter
|
||||||
|
Property="Background"
|
||||||
|
Value="{Binding Path=(TextElement.Background),
|
||||||
|
RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}}"
|
||||||
|
/>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type TextBox}"
|
||||||
|
>
|
||||||
|
<Setter Property="Background" Value="#00000000"/>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type Button}"
|
||||||
|
>
|
||||||
|
<Setter Property="Background" Value="#20808080"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Button}">
|
||||||
|
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
|
||||||
|
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="Button.IsDefaulted" Value="True">
|
||||||
|
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" TargetName="border" Value="#80BEE6FD"/>
|
||||||
|
<Setter Property="BorderBrush" TargetName="border" Value="#FF3C7FB1"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter Property="Background" TargetName="border" Value="#80C4E5F6"/>
|
||||||
|
<Setter Property="BorderBrush" TargetName="border" Value="#FF2C628B"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="ToggleButton.IsChecked" Value="True">
|
||||||
|
<Setter Property="Background" TargetName="border" Value="#FFBCDDEE"/>
|
||||||
|
<Setter Property="BorderBrush" TargetName="border" Value="#FF245A83"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Background" TargetName="border" Value="#00000000"/>
|
||||||
|
<Setter Property="BorderBrush" TargetName="border" Value="#FFADB2B5"/>
|
||||||
|
<Setter Property="Foreground" Value="#808080"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type DataGrid}"
|
||||||
|
/>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type DataGridColumnHeader}"
|
||||||
|
>
|
||||||
|
<Setter Property="Padding" Value="10,5" />
|
||||||
|
<Setter Property="MinWidth" Value="0" />
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Background" Value="#343434"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0, 0, 1, 0"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Gray" />
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type ListBox}"
|
||||||
|
>
|
||||||
|
<Setter Property="Background" Value="#181818"/>
|
||||||
|
</Style>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
<Grid Margin="10, 10">
|
<Window.DataContext>
|
||||||
|
<l:ViewModel/>
|
||||||
|
</Window.DataContext>
|
||||||
|
<Grid Margin="10,20,10,10">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
<RowDefinition Height="130" />
|
<RowDefinition Height="130" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<VirtualizingStackPanel Grid.Row="0">
|
<DockPanel Grid.Row="0">
|
||||||
<Grid Height="25">
|
<Grid
|
||||||
|
DockPanel.Dock="Top"
|
||||||
|
Height="25"
|
||||||
|
>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition />
|
<ColumnDefinition />
|
||||||
<ColumnDefinition Width="92"/>
|
<ColumnDefinition Width="92"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Grid>
|
<l:InputBar
|
||||||
<!--<l:InputBox
|
HintText="输入文本,或 big/manifest/xml 文件路径"
|
||||||
HintText="Select some big file"
|
Text="{Binding MainInput.Text}"
|
||||||
Collection="{Binding Items}"
|
Collection="{Binding MainInput.Items}"
|
||||||
TextChanged="OnTextChanged"
|
SelectedItem="{Binding MainInput.SelectedItem}"
|
||||||
>
|
SelectedIndex="{Binding MainInput.SelectedIndex}"
|
||||||
</l:InputBox>-->
|
Grid.Column="0"
|
||||||
<l:InputBar
|
Margin="0,0,10,0"
|
||||||
HintText="Select some big file"
|
/>
|
||||||
Text="{Binding Text}"
|
<Button
|
||||||
Collection="{Binding Items}"
|
Content="浏览文件"
|
||||||
TextChanged="OnMainInputTextChanged"
|
Command="{Binding MainInput.BrowseCommand}"
|
||||||
/>
|
CommandParameter="{Binding ElementName=Self}"
|
||||||
</Grid>
|
Visibility="{Binding MainInput.SelectedItem, Converter={StaticResource InvalidInputEntryToVisibilityConverter}}"
|
||||||
<Button x:Name="button" Content="浏览文件"
|
Grid.Column="1"
|
||||||
Grid.Column="1" Margin="0"
|
Margin="0"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
Content="确认"
|
||||||
|
Command="{Binding MainInput.SelectCommand}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Visibility="{Binding MainInput.SelectedItem, Converter={StaticResource ValidInputEntryToVisibilityConverter}}"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0"
|
||||||
|
Foreground="#20FF30"
|
||||||
|
BorderBrush="#20FF30"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<l:InputBar
|
<Grid
|
||||||
HintText="输入big文件里包含的manifest文件的路径"
|
DockPanel.Dock="Top"
|
||||||
Collection="{Binding Items}"
|
Height="25"
|
||||||
SelectedItem="{Binding SelectedItem}"
|
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
Height="25" VerticalAlignment="Top"
|
Visibility="Visible"
|
||||||
/>
|
>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="92"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<l:InputBar
|
||||||
|
HintText="输入 big 文件里包含的 manifest 文件的路径"
|
||||||
|
Collection="{Binding Items}"
|
||||||
|
SelectedItem="{Binding SelectedItem}"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
Content="确认"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0"
|
||||||
|
Foreground="ForestGreen"
|
||||||
|
BorderBrush="ForestGreen" Template="{DynamicResource ButtonBaseControlTemplate1}"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<DockPanel
|
||||||
|
DockPanel.Dock="Top"
|
||||||
|
Height="25"
|
||||||
|
Margin="0,10"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Content="取消加载"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="92"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Content="加载 csf / mod.str"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="140"
|
||||||
|
Margin="10,0"
|
||||||
|
/>
|
||||||
|
<TextBlock
|
||||||
|
Text="正在加载……"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Grid.Column="0"
|
||||||
|
/>
|
||||||
|
</DockPanel>
|
||||||
<l:InputBar
|
<l:InputBar
|
||||||
HintText="过滤Asset ID(可选)"
|
HintText="过滤Asset ID(可选)"
|
||||||
Collection="{Binding Items}"
|
Collection="{Binding Items}"
|
||||||
SelectedItem="{Binding SelectedItem}"
|
SelectedItem="{Binding SelectedItem}"
|
||||||
Margin="0,10,0,0"
|
DockPanel.Dock="Top"
|
||||||
Height="25" VerticalAlignment="Top"
|
Height="25"
|
||||||
/>
|
/>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
x:Name="DataGrid"
|
x:Name="DataGrid"
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
ScrollViewer.CanContentScroll="True"
|
ScrollViewer.CanContentScroll="True"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
EnableRowVirtualization="True"
|
EnableRowVirtualization="True"
|
||||||
VirtualizingPanel.VirtualizationMode="Recycling"
|
VirtualizingPanel.VirtualizationMode="Recycling"
|
||||||
|
DockPanel.Dock="Top"
|
||||||
>
|
>
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="Asset ID" Width="200" Binding="{Binding AssetId}"/>
|
<DataGridTextColumn Header="Asset ID" Width="200" Binding="{Binding AssetId}"/>
|
||||||
<DataGridTextColumn Header="哈希" Width="*" Binding="{Binding Hash}"/>
|
<DataGridTextColumn Header="哈希" Width="*" Binding="{Binding Hash}"/>
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
</VirtualizingStackPanel>
|
</DockPanel>
|
||||||
<TextBlock x:Name="textBlock" Grid.Row="1" TextWrapping="Wrap">
|
<TextBlock
|
||||||
|
x:Name="textBlock"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0,10,0,0"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
>
|
||||||
本工具基于 <l:ShellLink NavigateUri="https://github.com/Qibbi">Qibbi</l:ShellLink> 提供的 TechnologyAssembler 制作<LineBreak />
|
本工具基于 <l:ShellLink NavigateUri="https://github.com/Qibbi">Qibbi</l:ShellLink> 提供的 TechnologyAssembler 制作<LineBreak />
|
||||||
此外使用了 <l:ShellLink NavigateUri="https://github.com/bgrainger">Bradley Grainger</l:ShellLink> 的
|
此外使用了 <l:ShellLink NavigateUri="https://github.com/bgrainger">Bradley Grainger</l:ShellLink> 的
|
||||||
<l:ShellLink NavigateUri="https://github.com/bgrainger/IndexRange">IndexRange</l:ShellLink>
|
<l:ShellLink NavigateUri="https://github.com/bgrainger/IndexRange">IndexRange</l:ShellLink>
|
||||||
|
@ -20,59 +20,28 @@ namespace HashCalculator.GUI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
internal ViewModel ViewModel => (ViewModel)DataContext;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
var x = new ModXml(@"D:\Users\lanyi\Desktop\RA3Mods\ARSDK2\Mods\Armor Rush\DATA\mod.xml");
|
|
||||||
var xxx = Enumerable.Repeat(new { AssetId = "正在准备", Hash = "正在准备" }, 1);
|
|
||||||
x.ProcessDocument().ContinueWith(async txx =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var xx = await txx.ConfigureAwait(true);
|
|
||||||
if (x.Errors.Any())
|
|
||||||
{
|
|
||||||
MessageBox.Show("Errors: \r\n" + x.Errors.Aggregate((x, y) => $"{x}\r\n{y}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
DataGrid.ItemsSource = from y in xx
|
|
||||||
select new { AssetId = $"{y.Type}:{y.Name}", y.Hash };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
DataGrid.ItemsSource = Enumerable.Repeat(new { AssetId = $"错误 {e}", Hash = e.ToString() }, 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, TaskScheduler.Default);
|
|
||||||
|
|
||||||
/*_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var xxx = Enumerable.Repeat(new { AssetId = "正在准备", Hash = "正在准备" }, 1);
|
|
||||||
await Dispatcher.Invoke(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var xx = await x.ProcessDocument().ConfigureAwait(true);
|
|
||||||
DataGrid.ItemsSource = from y in xx
|
|
||||||
select new { AssetId = $"{y.Type}:{y.Name}", y.Hash };
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DataGrid.ItemsSource = Enumerable.Repeat(new { AssetId = $"错误 {e}", Hash = e.ToString() }, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}).ConfigureAwait(true);
|
|
||||||
});*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMainInputTextChanged(object sender, TextChangedEventArgs e)
|
private void OnMainInputTextChanged(object sender, TextChangedEventArgs e)
|
||||||
{
|
{
|
||||||
|
CopyableBox.ShowDialog(async token =>
|
||||||
|
{
|
||||||
|
var init = DateTimeOffset.UtcNow;
|
||||||
|
var i = 0;
|
||||||
|
while (i < 100)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
MessageBox.Show("Completed!");
|
||||||
|
return $"Completed after exactly {DateTimeOffset.UtcNow - init}";
|
||||||
|
});
|
||||||
/*var mainInput = _viewModel.MainInput;
|
/*var mainInput = _viewModel.MainInput;
|
||||||
if (sender is InputBar)
|
if (sender is InputBar)
|
||||||
{
|
{
|
||||||
|
19
ModManifest.cs
Normal file
19
ModManifest.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using TechnologyAssembler.Core.Assets;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI
|
||||||
|
{
|
||||||
|
public class ModManifest
|
||||||
|
{
|
||||||
|
IReadOnlyCollection<AssetEntry> Entries;
|
||||||
|
|
||||||
|
public ModManifest()
|
||||||
|
{
|
||||||
|
var x = Manifest.Load("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
Model.cs
33
Model.cs
@ -1,33 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
|
||||||
{
|
|
||||||
internal class Model
|
|
||||||
{
|
|
||||||
private readonly ViewModel _viewModel;
|
|
||||||
private readonly FileSystemSuggestions _suggestions = new FileSystemSuggestions();
|
|
||||||
|
|
||||||
public Model(ViewModel viewModel)
|
|
||||||
{
|
|
||||||
_viewModel = viewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateMainInputSuggestions()
|
|
||||||
{
|
|
||||||
var mainInput = _viewModel.MainInput;
|
|
||||||
var inputValue = mainInput.Text;
|
|
||||||
if (inputValue != null)
|
|
||||||
{
|
|
||||||
mainInput.Items = _suggestions.ProvideFileSystemSuggestions(inputValue)
|
|
||||||
.Prepend(new InputEntry(InputEntryType.Text, inputValue, inputValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
302
ViewModel.cs
302
ViewModel.cs
@ -1,10 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using TechnologyAssembler.Core.IO;
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
namespace HashCalculator.GUI
|
||||||
{
|
{
|
||||||
@ -37,47 +43,305 @@ namespace HashCalculator.GUI
|
|||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
internal class ViewModel : NotifyPropertyChanged
|
internal class ViewModel : NotifyPropertyChanged
|
||||||
{
|
{
|
||||||
public InputBarViewModel MainInput { get; } = new InputBarViewModel();
|
public MainInputViewModel MainInput { get; } = new MainInputViewModel();
|
||||||
public InputBarViewModel BigEntryInput { get; } = new InputBarViewModel();
|
public BigInputViewModel BigEntryInput { get; } = new BigInputViewModel();
|
||||||
public InputBarViewModel AssetIdInput { get; } = new InputBarViewModel();
|
public InputBarViewModel AssetIdInput { get; } = new InputBarViewModel();
|
||||||
public Model Model { get; }
|
|
||||||
|
private Visibility _bigInputVisibility = Visibility.Collapsed;
|
||||||
|
public Visibility BigInputVisibility
|
||||||
|
{
|
||||||
|
get => _bigInputVisibility;
|
||||||
|
set => SetField(ref _bigInputVisibility, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputEntry? _mainInputResult; // if not null
|
||||||
|
private string? _bigManifest;
|
||||||
|
|
||||||
public ViewModel()
|
public ViewModel()
|
||||||
{
|
{
|
||||||
Model = new Model(this);
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||||
|
public async Task OnMainInputDecided(InputEntry selected)
|
||||||
|
{
|
||||||
|
BigInputVisibility = Visibility.Collapsed;
|
||||||
|
switch (selected.Type)
|
||||||
|
{
|
||||||
|
case InputEntryType.BinaryFile:
|
||||||
|
CopyableBox.ShowDialog(async cancel =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hash = await Task.Run(() =>
|
||||||
|
{
|
||||||
|
return SageHash.CalculateBinaryHash(File.ReadAllBytes(selected.Value));
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
return $"使用Sage Hash计算出的哈希值:{hash:X8} (十进制 {hash})\r\n"
|
||||||
|
+ "注意这是以大小写敏感模式计算出的哈希值,与素材ID(大小写不敏感)的哈希其实并不完全一样";
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
return exception.ToString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case InputEntryType.BigFile:
|
||||||
|
BigInputVisibility = Visibility.Visible;
|
||||||
|
throw new NotImplementedException();
|
||||||
|
case InputEntryType.ManifestFile:
|
||||||
|
await ProcessManifest(selected).ConfigureAwait(true);
|
||||||
|
break;
|
||||||
|
case InputEntryType.XmlFile:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessManifest(InputEntry entry)
|
||||||
|
{
|
||||||
|
var content = await Task.Run(() =>
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(entry.Value);
|
||||||
|
return new ManifestContent(entry.Text, stream);
|
||||||
|
}).ConfigureAwait(true);
|
||||||
|
await ProcessManifest(content).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessManifest(ManifestContent content)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class InputBarViewModel : NotifyPropertyChanged
|
internal class InputBarViewModel : NotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private IEnumerable<InputEntry>? _items;
|
private IEnumerable<InputEntry>? _items;
|
||||||
public IEnumerable<InputEntry>? Items
|
public virtual IEnumerable<InputEntry>? Items
|
||||||
{
|
{
|
||||||
get => _items;
|
get => _items;
|
||||||
set => SetField(ref _items, value);
|
set => SetField(ref _items, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputEntry? selectedItem;
|
private InputEntry? _selectedItem;
|
||||||
public InputEntry? SelectedItem
|
public virtual InputEntry? SelectedItem
|
||||||
{
|
{
|
||||||
get { return selectedItem; }
|
get => _selectedItem;
|
||||||
set { SetField(ref selectedItem, value); }
|
set => SetField(ref _selectedItem, value);
|
||||||
}
|
|
||||||
|
|
||||||
private string? selectedValue;
|
|
||||||
public string? SelectedValue
|
|
||||||
{
|
|
||||||
get { return selectedValue; }
|
|
||||||
set { SetField(ref selectedValue, value); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? _text;
|
private string? _text;
|
||||||
public string? Text
|
public virtual string? Text
|
||||||
{
|
{
|
||||||
get { return _text; }
|
get => _text;
|
||||||
set { SetField(ref _text, value); }
|
set => SetField(ref _text, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class MainInputViewModel : InputBarViewModel
|
||||||
|
{
|
||||||
|
private readonly FileSystemSuggestions _suggestions = new FileSystemSuggestions();
|
||||||
|
|
||||||
|
private int _selectedIndex;
|
||||||
|
public int SelectedIndex
|
||||||
|
{
|
||||||
|
get => _selectedIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == 0)
|
||||||
|
{
|
||||||
|
if (Items.Count() <= 1)
|
||||||
|
{
|
||||||
|
value = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = _selectedIndex < value ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetField(ref _selectedIndex, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string? Text
|
||||||
|
{
|
||||||
|
get => base.Text;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Text = value;
|
||||||
|
if (value != SelectedItem?.ToString())
|
||||||
|
{
|
||||||
|
UpdateList(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command<MainWindow> BrowseCommand { get; }
|
||||||
|
public Command<ViewModel> SelectCommand { get; }
|
||||||
|
|
||||||
|
public MainInputViewModel()
|
||||||
|
{
|
||||||
|
Items = Enumerable.Empty<InputEntry>();
|
||||||
|
BrowseCommand = new Command<MainWindow>(window =>
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Multiselect = false,
|
||||||
|
ValidateNames = true
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog(window) == true)
|
||||||
|
{
|
||||||
|
Text = dialog.FileName;
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
SelectCommand = new Command<ViewModel>(async viewModel =>
|
||||||
|
{
|
||||||
|
if(SelectedItem?.IsValid != true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BrowseCommand.CanExecuteValue = false;
|
||||||
|
SelectCommand.CanExecuteValue = false;
|
||||||
|
await viewModel.OnMainInputDecided(SelectedItem).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
BrowseCommand.CanExecuteValue = true;
|
||||||
|
SelectCommand.CanExecuteValue = true;
|
||||||
|
SelectedIndex = -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateList(string? path)
|
||||||
|
{
|
||||||
|
bool candidateFound = false;
|
||||||
|
var result = _suggestions.ProvideFileSystemSuggestions(path);
|
||||||
|
InputEntry? first = result.FirstOrDefault();
|
||||||
|
if (first != null)
|
||||||
|
{
|
||||||
|
if (new FileInfo(path!).FullName == new FileInfo(first.Value).FullName)
|
||||||
|
{
|
||||||
|
candidateFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
result = result.Prepend(new InputEntry(InputEntryType.Text, path!, path!));
|
||||||
|
}
|
||||||
|
Items = result;
|
||||||
|
|
||||||
|
if (candidateFound)
|
||||||
|
{
|
||||||
|
SelectedIndex = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Design", "CA1001:具有可释放字段的类型应该是可释放的", Justification = "<挂起>")]
|
||||||
|
internal class BigInputViewModel : NotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private CancellationTokenSource? _currentCancellator = null;
|
||||||
|
|
||||||
|
private IEnumerable<ManifestContent>? _manifests;
|
||||||
|
public IEnumerable<ManifestContent>? Manifests
|
||||||
|
{
|
||||||
|
get => _manifests;
|
||||||
|
set => SetField(ref _manifests, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ManifestContent? _selectedManifest;
|
||||||
|
public ManifestContent? SelectedManifest
|
||||||
|
{
|
||||||
|
get => _selectedManifest;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetField(ref _selectedManifest, value);
|
||||||
|
SelectCommand.CanExecuteValue = value != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command<ViewModel> SelectCommand { get; }
|
||||||
|
|
||||||
|
public BigInputViewModel()
|
||||||
|
{
|
||||||
|
SelectCommand = new Command<ViewModel>(viewModel => viewModel.ProcessManifest(SelectedManifest!));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadBig(string path)
|
||||||
|
{
|
||||||
|
using var cancellator = _currentCancellator = new CancellationTokenSource();
|
||||||
|
var saved = Interlocked.Exchange(ref _currentCancellator, cancellator);
|
||||||
|
saved?.Cancel();
|
||||||
|
|
||||||
|
var token = cancellator.Token;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var big = await Task.Run(() => new BigFile(path), token).ConfigureAwait(true);
|
||||||
|
var manifests = await Task.Run(() =>
|
||||||
|
{
|
||||||
|
var manifests = big.GetFiles(string.Empty, "*.manifest", VirtualSearchOptionType.AllDirectories);
|
||||||
|
var modManifests = from manifest in manifests
|
||||||
|
where FileNameStartsWith(manifest, "mod")
|
||||||
|
select manifest;
|
||||||
|
var globalDataManifests = from manifest in manifests
|
||||||
|
where FileNameStartsWith(manifest, "mapmetadata")
|
||||||
|
select manifest;
|
||||||
|
var firstManifests = modManifests.Concat(globalDataManifests);
|
||||||
|
var otherManifests = from manifest in manifests
|
||||||
|
where !firstManifests.Contains(manifest)
|
||||||
|
select manifest;
|
||||||
|
|
||||||
|
var list = new List<ManifestContent>();
|
||||||
|
foreach (var path in firstManifests.Concat(otherManifests))
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
using var stream = big.OpenStream(path);
|
||||||
|
list.Add(new ManifestContent(path, stream));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}, token).ConfigureAwait(true);
|
||||||
|
|
||||||
|
if (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Manifests = manifests;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
|
||||||
|
Interlocked.CompareExchange(ref _currentCancellator, null, cancellator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool FileNameStartsWith(string path, string what)
|
||||||
|
{
|
||||||
|
return Path.GetFileName(path).StartsWith(what, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ManifestContent
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public byte[] Data { get; }
|
||||||
|
|
||||||
|
public ManifestContent(string name, Stream stream)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
stream.CopyTo(memoryStream);
|
||||||
|
Data = memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user