348 lines
12 KiB
C#
348 lines
12 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|