HashCalculator.GUI/ViewModel.cs
2020-03-28 23:16:45 +01:00

348 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Win32;
using TechnologyAssembler.Core.IO;
namespace HashCalculator.GUI
{
internal abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
protected void Notify(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
protected void SetField<X>(ref X field, X value, [CallerMemberName] string? propertyName = null)
{
if (propertyName == null)
{
throw new InvalidOperationException();
}
if (EqualityComparer<X>.Default.Equals(field, value))
{
return;
}
field = value;
Notify(propertyName);
}
#endregion
}
[SuppressMessage("Microsoft.Performance", "CA1812")]
internal class ViewModel : NotifyPropertyChanged
{
public MainInputViewModel MainInput { get; } = new MainInputViewModel();
public BigInputViewModel BigEntryInput { get; } = new BigInputViewModel();
public InputBarViewModel AssetIdInput { get; } = new InputBarViewModel();
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()
{
}
[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
{
private IEnumerable<InputEntry>? _items;
public virtual IEnumerable<InputEntry>? Items
{
get => _items;
set => SetField(ref _items, value);
}
private InputEntry? _selectedItem;
public virtual InputEntry? SelectedItem
{
get => _selectedItem;
set => SetField(ref _selectedItem, value);
}
private string? _text;
public virtual string? Text
{
get => _text;
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;
}
}
}