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(ref X field, X value, [CallerMemberName] string? propertyName = null) { if (propertyName == null) { throw new InvalidOperationException(); } if (EqualityComparer.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? _items; public virtual IEnumerable? 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 BrowseCommand { get; } public Command SelectCommand { get; } public MainInputViewModel() { Items = Enumerable.Empty(); BrowseCommand = new Command(window => { var dialog = new OpenFileDialog { Multiselect = false, ValidateNames = true }; if (dialog.ShowDialog(window) == true) { Text = dialog.FileName; } return Task.CompletedTask; }); SelectCommand = new Command(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? _manifests; public IEnumerable? 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 SelectCommand { get; } public BigInputViewModel() { SelectCommand = new Command(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(); 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; } } }