initial work on hashes, view models and xml parsing
This commit is contained in:
parent
a51aa6958b
commit
bda455b7e3
92
AssetEntry.cs
Normal file
92
AssetEntry.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
public class AssetEntry : IEquatable<AssetEntry>
|
||||
{
|
||||
public string Type { get; }
|
||||
public string Name { get; }
|
||||
public IEnumerable<string> DisplayLabels { get; }
|
||||
public uint Hash => SageHash.CalculateLowercaseHash(Name);
|
||||
|
||||
public AssetEntry(XElement element)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(element)} is null");
|
||||
}
|
||||
|
||||
if (element.Name.Namespace != ModXml.EalaAsset)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
Type = element.Name.LocalName;
|
||||
var id = element.Attribute("id")?.Value;
|
||||
if (id == null)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
Name = id;
|
||||
|
||||
var labels = from name in element.Elements(ModXml.EalaAsset + "DisplayName")
|
||||
select name.Value;
|
||||
var transformLabels = from name in element.Elements(ModXml.EalaAsset + "DisplayNameTransformed")
|
||||
select name.Value;
|
||||
DisplayLabels = labels.Concat(transformLabels).ToArray();
|
||||
}
|
||||
|
||||
public bool Equals(AssetEntry entry)
|
||||
{
|
||||
return this == entry;
|
||||
}
|
||||
|
||||
// override object.Equals
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
//
|
||||
// See the full list of guidelines at
|
||||
// http://go.microsoft.com/fwlink/?LinkID=85237
|
||||
// and also the guidance for operator== at
|
||||
// http://go.microsoft.com/fwlink/?LinkId=85238
|
||||
//
|
||||
|
||||
if (!(obj is AssetEntry entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this == entry;
|
||||
}
|
||||
|
||||
public static bool operator ==(AssetEntry a, AssetEntry b)
|
||||
{
|
||||
if (a is null || b is null)
|
||||
{
|
||||
return a is null == b is null;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return a.Type == b.Type && a.Hash == b.Hash;
|
||||
}
|
||||
|
||||
public static bool operator !=(AssetEntry a, AssetEntry b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
// override object.GetHashCode
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Type, Hash);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,13 +8,16 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
<UserSecretsId>bf77c300-44f6-46ea-be94-f50d6993b55b</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Mvp.Xml" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="130" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0">
|
||||
<VirtualizingStackPanel Grid.Row="0">
|
||||
<Grid Height="25">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@ -31,7 +31,7 @@
|
||||
HintText="Select some big file"
|
||||
Text="{Binding Text}"
|
||||
Collection="{Binding Items}"
|
||||
TextChanged="OnTextChanged"
|
||||
TextChanged="OnMainInputTextChanged"
|
||||
/>
|
||||
</Grid>
|
||||
<Button x:Name="button" Content="浏览文件"
|
||||
@ -52,19 +52,22 @@
|
||||
Margin="0,10,0,0"
|
||||
Height="25" VerticalAlignment="Top"
|
||||
/>
|
||||
<DataGrid x:Name="dataGrid">
|
||||
<DataGrid
|
||||
x:Name="DataGrid"
|
||||
AutoGenerateColumns="False"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
EnableRowVirtualization="True"
|
||||
VirtualizingPanel.VirtualizationMode="Recycling"
|
||||
>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Asset ID" Width="200"/>
|
||||
<DataGridTextColumn Header="哈希" Width="*"/>
|
||||
<DataGridTextColumn Header="Asset ID" Width="200" Binding="{Binding AssetId}"/>
|
||||
<DataGridTextColumn Header="哈希" Width="*" Binding="{Binding Hash}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
</VirtualizingStackPanel>
|
||||
<TextBlock x:Name="textBlock" Grid.Row="1" TextWrapping="Wrap">
|
||||
本工具基于 <l:ShellLink NavigateUri="https://github.com/Qibbi">Qibbi</l:ShellLink> 提供的 TechnologyAssembler 制作<LineBreak />
|
||||
此外使用了 <l:ShellLink NavigateUri="https://github.com/vain0x">vain0x</l:ShellLink> 的
|
||||
<l:ShellLink NavigateUri="https://github.com/vain0x/DotNetKit.Wpf.AutoCompleteComboBox">DotNetKit.Wpf.AutoCompleteComboBox</l:ShellLink>
|
||||
组合框控件<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>
|
||||
从而能在 .NET Standard 2.0 上使用 C# 8.0 的末尾索引操作符<LineBreak />
|
||||
<LineBreak />
|
||||
|
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@ -23,85 +20,97 @@ namespace HashCalculator.GUI
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly FileSystemSuggestions _suggestions = new FileSystemSuggestions();
|
||||
private readonly ViewModel _model = new ViewModel();
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = _model;
|
||||
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 OnTextChanged(object sender, TextChangedEventArgs e)
|
||||
private void OnMainInputTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
/*var mainInput = _viewModel.MainInput;
|
||||
if (sender is InputBar)
|
||||
{
|
||||
var inputValue = _model.Text;
|
||||
var inputValue = mainInput.Text;
|
||||
if(inputValue != null)
|
||||
{
|
||||
_model.Items = _suggestions.ProvideFileSystemSuggestions(inputValue)
|
||||
mainInput.Items = _suggestions.ProvideFileSystemSuggestions(inputValue)
|
||||
.Prepend(new InputEntry(InputEntryType.Text, inputValue, inputValue));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private void OnBigInputTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
/*var mainInput = _viewModel.MainInput;
|
||||
if (sender is InputBar)
|
||||
{
|
||||
var inputValue = mainInput.Text;
|
||||
if (inputValue != null)
|
||||
{
|
||||
mainInput.Items = _suggestions.ProvideFileSystemSuggestions(inputValue)
|
||||
.Prepend(new InputEntry(InputEntryType.Text, inputValue, inputValue));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private void OnAssetInputTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
/*var mainInput = _viewModel.MainInput;
|
||||
if (sender is InputBar)
|
||||
{
|
||||
var inputValue = mainInput.Text;
|
||||
if (inputValue != null)
|
||||
{
|
||||
mainInput.Items = _suggestions.ProvideFileSystemSuggestions(inputValue)
|
||||
.Prepend(new InputEntry(InputEntryType.Text, inputValue, inputValue));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
internal class ViewModel : INotifyPropertyChanged
|
||||
{
|
||||
#region INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void Notify(string name)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Updating {name}");
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
|
||||
private 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
|
||||
|
||||
private IEnumerable<InputEntry>? _items;
|
||||
public IEnumerable<InputEntry>? Items
|
||||
{
|
||||
get => _items;
|
||||
set => SetField(ref _items, value);
|
||||
}
|
||||
|
||||
private InputEntry? selectedItem;
|
||||
public InputEntry? SelectedItem
|
||||
{
|
||||
get { return selectedItem; }
|
||||
set { SetField(ref selectedItem, value); }
|
||||
}
|
||||
|
||||
private string? selectedValue;
|
||||
public string? SelectedValue
|
||||
{
|
||||
get { return selectedValue; }
|
||||
set { SetField(ref selectedValue, value); }
|
||||
}
|
||||
|
||||
private string? _text;
|
||||
public string? Text
|
||||
{
|
||||
get { return _text; }
|
||||
set { SetField(ref _text, value); }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
271
ModXml.cs
Normal file
271
ModXml.cs
Normal file
@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Win32;
|
||||
using Mvp.Xml.XInclude;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
public class ModXml
|
||||
{
|
||||
protected enum DocumentOption
|
||||
{
|
||||
Normal,
|
||||
IsEntryPoint
|
||||
}
|
||||
|
||||
const string RegistryPath = @"Microsoft\Windows\CurrentVersion\Uninstall\{F6A3F605-7B10-4939-8D3D-4594332C1649}";
|
||||
public static readonly IEnumerable<string> NotSupportedAttributes = new[]
|
||||
{
|
||||
"fragid", "set-xml-id", "encoding", "accept", "accept-language"
|
||||
};
|
||||
public static XNamespace XInclude { get; } = "http://www.w3.org/2001/XInclude";
|
||||
public static XNamespace EalaAsset { get; } = "uri:ea.com:eala:asset";
|
||||
public bool SdkNotFound { get; }
|
||||
public IReadOnlyCollection<string> Errors => _errors;
|
||||
private readonly ConcurrentDictionary<string, byte> _processed = new ConcurrentDictionary<string, byte>();
|
||||
private readonly ConcurrentDictionary<AssetEntry, byte> _assets = new ConcurrentDictionary<AssetEntry, byte>();
|
||||
private readonly ConcurrentQueue<string> _errors = new ConcurrentQueue<string>();
|
||||
private readonly ModUriResolver _uriResolver;
|
||||
private readonly string _xmlFullPath;
|
||||
|
||||
public ModXml(string xmlPath)
|
||||
{
|
||||
var baseDirectory = new DirectoryInfo(FindBaseDirectory(xmlPath));
|
||||
var allMods = baseDirectory.Parent;
|
||||
if (!allMods.Name.Equals("Mods", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new DirectoryNotFoundException();
|
||||
}
|
||||
var allModsParent = allMods.Parent;
|
||||
using var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
|
||||
if (!(hklm.GetValue(RegistryPath) is string sdkRootPath))
|
||||
{
|
||||
SdkNotFound = true;
|
||||
sdkRootPath = allModsParent.FullName;
|
||||
}
|
||||
|
||||
IReadOnlyCollection<string> GetPaths(string name) => new string[]
|
||||
{
|
||||
sdkRootPath,
|
||||
allModsParent.FullName,
|
||||
Path.Combine(baseDirectory.FullName, name),
|
||||
Path.Combine(sdkRootPath, "Mods"),
|
||||
allMods.FullName,
|
||||
Path.Combine(sdkRootPath, name.Equals("Data", StringComparison.OrdinalIgnoreCase) ? "SageXml" : name)
|
||||
};
|
||||
|
||||
_uriResolver = new ModUriResolver(new Dictionary<string, IReadOnlyCollection<string>>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "Art", GetPaths("Art") },
|
||||
{ "Audio", GetPaths("Audio") },
|
||||
{ "Data", GetPaths("Data") },
|
||||
});
|
||||
|
||||
_xmlFullPath = Path.GetFullPath(xmlPath);
|
||||
}
|
||||
|
||||
public Task<ICollection<AssetEntry>> ProcessDocument()
|
||||
{
|
||||
if (_processed.Any() || _assets.Any() || _errors.Any())
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var includes = ProcessDocumentInternal(_xmlFullPath).AsParallel();
|
||||
while (includes.Any())
|
||||
{
|
||||
includes = from include in includes
|
||||
from newInclude in ProcessDocumentInternal(include)
|
||||
select newInclude;
|
||||
}
|
||||
|
||||
return _assets.Keys;
|
||||
});
|
||||
}
|
||||
|
||||
private string[] ProcessDocumentInternal(string fullPath)
|
||||
{
|
||||
if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var document = GetFile(fullPath);
|
||||
if (document.Root.Name != EalaAsset + "AssetDeclaration" && document.Root.Name != "AssetDeclaration")
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
var items = from element in document.Root.Elements()
|
||||
where element.Attribute("id") != null
|
||||
select new AssetEntry(element);
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!_assets.TryAdd(item, default))
|
||||
{
|
||||
_errors.Enqueue($"Attempted to add item `{item.Type}:{item.Name}` multiple times");
|
||||
}
|
||||
}
|
||||
|
||||
var includes = from include in document.Root.Elements(EalaAsset + "Includes").Elements()
|
||||
let includePath = GetIncludePath(include, fullPath)
|
||||
where includePath != null
|
||||
select includePath;
|
||||
return includes.ToArray();
|
||||
}
|
||||
|
||||
private string? GetIncludePath(XElement include, string includerPath)
|
||||
{
|
||||
if (include.Name != EalaAsset + "Include")
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
if (include.Attribute("type")?.Value == "reference")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var source = include.Attribute("source")?.Value;
|
||||
if (source == null)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
string? includedSource;
|
||||
try
|
||||
{
|
||||
includedSource = _uriResolver.ResolveUri(new Uri(includerPath), source).LocalPath;
|
||||
}
|
||||
catch (FileNotFoundException error)
|
||||
{
|
||||
_errors.Enqueue(error.Message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return includedSource;
|
||||
}
|
||||
|
||||
private XDocument GetFile(string normalizedPath)
|
||||
{
|
||||
using var reader = new XIncludingReader(normalizedPath, _uriResolver)
|
||||
{
|
||||
// I dont know why but looks like it need to be set again
|
||||
// Otherwise it wont work
|
||||
XmlResolver = _uriResolver
|
||||
};
|
||||
reader.MoveToContent();
|
||||
return XDocument.Load(reader);
|
||||
}
|
||||
|
||||
private static string FindBaseDirectory(string currentPath)
|
||||
{
|
||||
var file = new FileInfo(currentPath);
|
||||
if (File.Exists(Path.Combine(file.DirectoryName, "Data", "mod.xml")))
|
||||
{
|
||||
return file.DirectoryName;
|
||||
}
|
||||
return FindBaseDirectory(file.DirectoryName);
|
||||
}
|
||||
}
|
||||
|
||||
public class ModUriResolver : XmlResolver
|
||||
{
|
||||
protected IReadOnlyDictionary<string, IReadOnlyCollection<string>> Paths { get; }
|
||||
|
||||
public ModUriResolver(IReadOnlyDictionary<string, IReadOnlyCollection<string>> paths)
|
||||
{
|
||||
Paths = paths;
|
||||
}
|
||||
|
||||
public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
|
||||
{
|
||||
if (absoluteUri == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(absoluteUri)} is null");
|
||||
}
|
||||
if (ofObjectToReturn != null && ofObjectToReturn != typeof(Stream))
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
return File.OpenRead(absoluteUri.LocalPath);
|
||||
}
|
||||
|
||||
[SuppressMessage("Globalization", "CA1303:请不要将文本作为本地化参数传递", Justification = "<挂起>")]
|
||||
public override Uri ResolveUri(Uri baseUri, string relativeUri)
|
||||
{
|
||||
if (baseUri == null)
|
||||
{
|
||||
if (!Path.IsPathRooted(relativeUri))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(baseUri)} is null and {nameof(relativeUri)} is not rooted");
|
||||
}
|
||||
return new Uri(relativeUri);
|
||||
}
|
||||
if (relativeUri == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(relativeUri)} is null");
|
||||
}
|
||||
return new Uri(Path.GetFullPath(ResolvePath(relativeUri, Path.GetDirectoryName(baseUri.LocalPath))));
|
||||
}
|
||||
|
||||
public override Task<object> GetEntityAsync(Uri absoluteUri, string role, Type ofObjectToReturn)
|
||||
{
|
||||
return base.GetEntityAsync(absoluteUri, role, ofObjectToReturn);
|
||||
}
|
||||
|
||||
public override bool SupportsType(Uri absoluteUri, Type type)
|
||||
{
|
||||
return base.SupportsType(absoluteUri, type);
|
||||
}
|
||||
|
||||
[SuppressMessage("Globalization", "CA1303:请不要将文本作为本地化参数传递", Justification = "<挂起>")]
|
||||
private string ResolvePath(string href, string currentDirectory)
|
||||
{
|
||||
var splitted = href.Split(new[] { ':' }, 2);
|
||||
if (splitted.Length > 1)
|
||||
{
|
||||
var prefix = splitted[0];
|
||||
var path = splitted[1];
|
||||
if (Paths.TryGetValue(prefix, out var collection))
|
||||
{
|
||||
foreach (var directory in collection)
|
||||
{
|
||||
if (prefix.Equals("Art", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var artPath = Path.Combine(directory, path.Substring(0, 2), path);
|
||||
if (File.Exists(artPath))
|
||||
{
|
||||
return artPath;
|
||||
}
|
||||
}
|
||||
|
||||
var realPath = Path.Combine(directory, path);
|
||||
if (File.Exists(realPath))
|
||||
{
|
||||
return realPath;
|
||||
}
|
||||
}
|
||||
throw new FileNotFoundException("Cannot find the file specified", href);
|
||||
}
|
||||
throw new FileNotFoundException($"Unknown prefix: {prefix}", href);
|
||||
}
|
||||
|
||||
var relativePath = Path.Combine(currentDirectory, href);
|
||||
if (!File.Exists(relativePath))
|
||||
{
|
||||
throw new FileNotFoundException("Cannot find the file specified", href);
|
||||
}
|
||||
return relativePath;
|
||||
}
|
||||
}
|
||||
}
|
33
Model.cs
Normal file
33
Model.cs
Normal file
@ -0,0 +1,33 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
49
SageHash.cs
Normal file
49
SageHash.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using TechnologyAssembler.Core.Hashing;
|
||||
using System.Xml;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
public static class SageHash
|
||||
{
|
||||
public static uint CalculateBinaryHash(byte[] content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(content)} is null");
|
||||
}
|
||||
return FastHash.GetHashCode(content);
|
||||
}
|
||||
|
||||
public static uint CalculateBinaryHash(string content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(content)} is null");
|
||||
}
|
||||
return CalculateBinaryHash(Encoding.ASCII.GetBytes(content));
|
||||
}
|
||||
|
||||
[SuppressMessage("Globalization", "CA1308:将字符串规范化为大写", Justification = "<挂起>")]
|
||||
public static uint CalculateLowercaseHash(string content)
|
||||
{
|
||||
if(content == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(content)} is null");
|
||||
}
|
||||
return CalculateBinaryHash(content.ToLowerInvariant());
|
||||
}
|
||||
|
||||
public static uint CalculateFileHash(string path)
|
||||
{
|
||||
if(path == null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(path)} is null");
|
||||
}
|
||||
return CalculateBinaryHash(File.ReadAllBytes(path));
|
||||
}
|
||||
}
|
||||
}
|
83
ViewModel.cs
Normal file
83
ViewModel.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
internal class ViewModel : NotifyPropertyChanged
|
||||
{
|
||||
public InputBarViewModel MainInput { get; } = new InputBarViewModel();
|
||||
public InputBarViewModel BigEntryInput { get; } = new InputBarViewModel();
|
||||
public InputBarViewModel AssetIdInput { get; } = new InputBarViewModel();
|
||||
public Model Model { get; }
|
||||
|
||||
public ViewModel()
|
||||
{
|
||||
Model = new Model(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal class InputBarViewModel : NotifyPropertyChanged
|
||||
{
|
||||
private IEnumerable<InputEntry>? _items;
|
||||
public IEnumerable<InputEntry>? Items
|
||||
{
|
||||
get => _items;
|
||||
set => SetField(ref _items, value);
|
||||
}
|
||||
|
||||
private InputEntry? selectedItem;
|
||||
public InputEntry? SelectedItem
|
||||
{
|
||||
get { return selectedItem; }
|
||||
set { SetField(ref selectedItem, value); }
|
||||
}
|
||||
|
||||
private string? selectedValue;
|
||||
public string? SelectedValue
|
||||
{
|
||||
get { return selectedValue; }
|
||||
set { SetField(ref selectedValue, value); }
|
||||
}
|
||||
|
||||
private string? _text;
|
||||
public string? Text
|
||||
{
|
||||
get { return _text; }
|
||||
set { SetField(ref _text, value); }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user