From 39ab6fb9b4131c7ce511c8733549d0dd1704102e Mon Sep 17 00:00:00 2001 From: Crauzer <0xcrauzer@proton.me> Date: Fri, 28 Jul 2023 01:56:44 +0200 Subject: [PATCH] Use new hashing method and refactor `WadTreeItem` (#211) * Use new hashing method and refactor `WadTreeItem` * aaaa --- .editorconfig | 2 +- Obsidian/Data/Wad/IWadTreeParent.cs | 24 ++++++++++-------- Obsidian/Data/Wad/IWadTreePathable.cs | 2 ++ Obsidian/Data/Wad/WadTreeItemModel.cs | 36 ++++++++++++--------------- Obsidian/Data/Wad/WadTreeModel.cs | 34 +++++++++++++++---------- Obsidian/Obsidian.csproj | 6 ++--- Obsidian/Shared/TreeWadItem.razor.cs | 2 +- Obsidian/Shared/WadExplorer.razor.cs | 10 +++++--- Obsidian/Utils/WadUtils.cs | 4 +-- 9 files changed, 65 insertions(+), 55 deletions(-) diff --git a/.editorconfig b/.editorconfig index b394333..bf324f0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -25,7 +25,7 @@ file_header_template = unset # this. and Me. preferences dotnet_style_qualification_for_event = true:suggestion dotnet_style_qualification_for_field = true -dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_method = true:suggestion dotnet_style_qualification_for_property = true:suggestion # Language keywords vs BCL types preferences diff --git a/Obsidian/Data/Wad/IWadTreeParent.cs b/Obsidian/Data/Wad/IWadTreeParent.cs index b71a0e6..508ebb9 100644 --- a/Obsidian/Data/Wad/IWadTreeParent.cs +++ b/Obsidian/Data/Wad/IWadTreeParent.cs @@ -1,10 +1,11 @@ using LeagueToolkit.Core.Wad; +using LeagueToolkit.Hashing; using System.Text.RegularExpressions; namespace Obsidian.Data.Wad; public interface IWadTreeParent : IWadTreePathable { - Dictionary Items { get; } + List Items { get; } } public static class IWadTreeParentExtensions { @@ -16,18 +17,19 @@ WadChunk chunk ) { // File belongs to this folder if (pathComponents.Count() is 1) { - string name = pathComponents.First(); - parent.Items.Add(name, new WadTreeFileModel(parent, name, wad, chunk)); + parent.Items.Add(new WadTreeFileModel(parent, pathComponents.First(), wad, chunk)); return; } string folderName = pathComponents.First(); + ulong folderNameHash = XxHash64Ext.Hash(folderName); + WadTreeItemModel directory = null; lock (parent) { - directory = parent.Items.GetValueOrDefault(folderName); + directory = parent.Items.FirstOrDefault(item => item.Type is not WadTreeItemType.File && item.NameHash == folderNameHash); if (directory is null) { directory = new(parent, folderName); - parent.Items.Add(directory.Name, directory); + parent.Items.Add(directory); } } @@ -38,7 +40,7 @@ public static IEnumerable TraverseFlattenedItems(this IWadTree if (parent.Items is null) yield break; - foreach (var (_, item) in parent.Items) { + foreach (var item in parent.Items) { yield return item; foreach (WadTreeItemModel itemItem in item.TraverseFlattenedItems()) @@ -52,7 +54,7 @@ this IWadTreeParent parent if (parent.Items is null) yield break; - foreach (var (_, item) in parent.Items) { + foreach (var item in parent.Items) { if (item.IsChecked) yield return item; @@ -69,7 +71,7 @@ public static IEnumerable TraverseFlattenedVisibleItems( if (parent.Items is null) yield break; - foreach (var (_, item) in parent.Items) { + foreach (var item in parent.Items) { if (!string.IsNullOrEmpty(filter)) { // If the current item is a file we check if it matches the filter if (item is WadTreeFileModel && DoesMatchFilter(item, filter, useRegex)) { @@ -78,12 +80,12 @@ public static IEnumerable TraverseFlattenedVisibleItems( } // If the current item is a folder we get filtered items and if there are none we skip - IReadOnlyList filteredItems = item.TraverseFlattenedVisibleItems( + WadTreeItemModel[] filteredItems = item.TraverseFlattenedVisibleItems( filter, useRegex ) - .ToList(); - if (filteredItems.Count is 0) + .ToArray(); + if (filteredItems.Length is 0) continue; // Return parent only if its children are included in the filter diff --git a/Obsidian/Data/Wad/IWadTreePathable.cs b/Obsidian/Data/Wad/IWadTreePathable.cs index e3c8d1a..f57ab49 100644 --- a/Obsidian/Data/Wad/IWadTreePathable.cs +++ b/Obsidian/Data/Wad/IWadTreePathable.cs @@ -6,6 +6,8 @@ public interface IWadTreePathable { string Name { get; } string Path { get; } + ulong NameHash { get; } + ulong PathHash { get; } public bool IsWadArchive { get; } } diff --git a/Obsidian/Data/Wad/WadTreeItemModel.cs b/Obsidian/Data/Wad/WadTreeItemModel.cs index 68025a2..6faafde 100644 --- a/Obsidian/Data/Wad/WadTreeItemModel.cs +++ b/Obsidian/Data/Wad/WadTreeItemModel.cs @@ -1,9 +1,7 @@ -using LeagueToolkit.Core.Wad; +using LeagueToolkit.Hashing; using LeagueToolkit.Utils; using MudBlazor; using System.Diagnostics; -using System.Text.RegularExpressions; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.ProgressBar; using PathIO = System.IO.Path; namespace Obsidian.Data.Wad; @@ -23,8 +21,10 @@ public class WadTreeItemModel public int Depth => this.GetDepth(); public Guid Id { get; } = Guid.NewGuid(); - public string Name { get; set; } + public string Name { get; } public string Path { get; } + public ulong NameHash { get; } + public ulong PathHash { get; } public string Icon => GetIcon(); @@ -33,38 +33,34 @@ public class WadTreeItemModel public bool IsSelected { get; set; } public bool IsChecked { get; set; } public bool IsExpanded { get; set; } + public bool IsWadArchive { get; } - public Dictionary Items { get; protected set; } = new(); - - public bool IsWadArchive { - get { - string extension = PathIO.GetExtension(this.Name); - - return extension.Contains("wad") - || extension.Contains("client") - || extension.Contains("server"); - } - } + public List Items { get; protected set; } = new(); public WadTreeItemModel(IWadTreeParent parent, string name) { this.Parent = parent; - this.Name = name; + this.Name = name; this.Path = parent switch { null or WadTreeModel or { IsWadArchive: true } => name, _ => string.Join('/', parent.Path, name) }; + this.NameHash = XxHash64Ext.Hash(this.Name); + this.PathHash = XxHash64Ext.Hash(this.Path); + + this.IsWadArchive = this.Name.EndsWith(".wad", StringComparison.OrdinalIgnoreCase) + || this.Name.EndsWith(".wad.client", StringComparison.OrdinalIgnoreCase) + || this.Name.EndsWith(".wad.mobile", StringComparison.OrdinalIgnoreCase); } public void SortItems() { if (this.Items is null) return; - this.Items = new(this.Items.OrderBy(x => x.Value)); + this.Items.Sort(); - foreach (var (_, item) in this.Items) { - if (item.Type is WadTreeItemType.Directory) - item.SortItems(); + foreach (WadTreeItemModel item in this.Items.Where(item => item.Type is WadTreeItemType.Directory)) { + item.SortItems(); } } diff --git a/Obsidian/Data/Wad/WadTreeModel.cs b/Obsidian/Data/Wad/WadTreeModel.cs index a0f47ea..072b992 100644 --- a/Obsidian/Data/Wad/WadTreeModel.cs +++ b/Obsidian/Data/Wad/WadTreeModel.cs @@ -1,8 +1,6 @@ using CommunityToolkit.Diagnostics; -using CommunityToolkit.HighPerformance.Helpers; using LeagueToolkit.Core.Wad; using Obsidian.Services; -using Obsidian.Shared; using Serilog; using PathIO = System.IO.Path; @@ -14,8 +12,12 @@ public class WadTreeModel : IWadTreeParent, IDisposable { public IWadTreeParent Parent => null; public int Depth => 0; + public string Name => string.Empty; public string Path => string.Empty; + public ulong NameHash => 0; + public ulong PathHash => 0; + public bool IsWadArchive => false; public bool UseRegexFilter { get; set; } @@ -23,19 +25,18 @@ public class WadTreeModel : IWadTreeParent, IDisposable { public WadFilePreviewType CurrentPreviewType { get; set; } - public Dictionary Items { get; set; } = new(); + public List Items { get; set; } = new(); public IEnumerable CheckedFiles => this.TraverseFlattenedCheckedItems() - .Where(x => x is WadTreeFileModel) - .Select(x => x as WadTreeFileModel); + .OfType(); public WadTreeFileModel SelectedFile => this.SelectedFiles.FirstOrDefault(); public IEnumerable SelectedFiles => this.TraverseFlattenedItems() - .Where(x => x.IsSelected && x is WadTreeFileModel) - .Select(x => x as WadTreeFileModel); + .Where(x => x.IsSelected) + .OfType(); private readonly Dictionary _mountedWadFiles = new(); @@ -69,27 +70,34 @@ private void MountWadFile(string path) { CreateTreeForWadFile(wad, relativeWadPath); } - public void CreateTreeForWadFile(WadFile wad, string wadFilePath) { + public void CreateTreeForWadFile(WadFile wad, string wadFilePath, bool allowDuplicate = false) { IEnumerable wadFilePathComponents = wadFilePath.Split('/'); + IWadTreeParent wadParent = this; + if (allowDuplicate) { + var wadItem = new WadTreeItemModel(this, wadFilePathComponents.First()); + this.Items.Add(wadItem); + wadParent = wadItem; + wadFilePathComponents = wadFilePathComponents.Skip(1); + } + foreach (var (_, chunk) in wad.Chunks) { string path = this.Hashtable.TryGetChunkPath(chunk, out path) switch { true => path, false => HashtableService.GuessChunkPath(chunk, wad), }; - this.AddWadFile(wadFilePathComponents.Concat(path.Split('/')), wad, chunk); + wadParent.AddWadFile(wadFilePathComponents.Concat(path.Split('/')), wad, chunk); } } public void SortItems() { Log.Information($"Sorting wad tree"); - this.Items = new(this.Items.OrderBy(x => x.Value)); + this.Items.Sort(); - foreach (var (_, item) in this.Items) { - if (item.Type is WadTreeItemType.Directory) - item.SortItems(); + foreach (WadTreeItemModel item in this.Items.Where(item => item.Type is WadTreeItemType.Directory)) { + item.SortItems(); } } diff --git a/Obsidian/Obsidian.csproj b/Obsidian/Obsidian.csproj index 788b2e0..c634db5 100644 --- a/Obsidian/Obsidian.csproj +++ b/Obsidian/Obsidian.csproj @@ -12,10 +12,10 @@ - + - - + + diff --git a/Obsidian/Shared/TreeWadItem.razor.cs b/Obsidian/Shared/TreeWadItem.razor.cs index 25b04c1..81495e8 100644 --- a/Obsidian/Shared/TreeWadItem.razor.cs +++ b/Obsidian/Shared/TreeWadItem.razor.cs @@ -152,7 +152,7 @@ private void Save() { } private void Delete() { - this.Item.Parent.Items.Remove(this.Item.Name); + this.Item.Parent.Items.Remove(this.Item); this.Explorer.RefreshState(); } } \ No newline at end of file diff --git a/Obsidian/Shared/WadExplorer.razor.cs b/Obsidian/Shared/WadExplorer.razor.cs index 976ee57..229ed19 100644 --- a/Obsidian/Shared/WadExplorer.razor.cs +++ b/Obsidian/Shared/WadExplorer.razor.cs @@ -91,8 +91,10 @@ await Task.Run(() => { FileStream wadFileStream = File.OpenRead(wadPath); WadFile wad = new(wadFileStream); - this.WadTree.CreateTreeForWadFile(wad, Path.GetFileName(wadPath)); + this.WadTree.CreateTreeForWadFile(wad, Path.GetFileName(wadPath), allowDuplicate: true); } + + this.WadTree.SortItems(); }); this.Snackbar.Add("Successfully opened Wad archives!", Severity.Success); @@ -191,15 +193,15 @@ private void ExtractFiles(IEnumerable fileItems, string extrac ); } - public List GetVisibleItemsForWadTree() { + private ICollection GetVisibleItemsForWadTree() { try { return this.WadTree .TraverseFlattenedVisibleItems(this.WadTree.Filter, this.WadTree.UseRegexFilter) - .ToList(); + .ToArray(); } catch (Exception exception) { SnackbarUtils.ShowSoftError(this.Snackbar, exception); - return new(); + return Array.Empty(); } } diff --git a/Obsidian/Utils/WadUtils.cs b/Obsidian/Utils/WadUtils.cs index f2a0655..6af679f 100644 --- a/Obsidian/Utils/WadUtils.cs +++ b/Obsidian/Utils/WadUtils.cs @@ -1,5 +1,5 @@ using LeagueToolkit.Core.Wad; -using XXHash3NET; +using LeagueToolkit.Hashing; namespace Obsidian.Utils; @@ -27,7 +27,7 @@ public static string CreateChunkFilePath(string saveDirectory, string chunkPath) saveDirectory, string.Format( "{0:x16}{1}", - XXHash64.Compute(chunkPath.ToLower()), + XxHash64Ext.Hash(chunkPath.ToLowerInvariant()), Path.GetExtension(chunkPath) ) );