From 1b158c3336a4de6ac4e176f5974b918df98a031f Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 18 Oct 2020 20:18:41 +0200 Subject: [PATCH] Folder structure for packages. --- ILSpy/ILSpy.csproj | 1 + ILSpy/LoadedPackage.cs | 114 +++++++++++++++++++---- ILSpy/TreeNodes/AssemblyTreeNode.cs | 20 +--- ILSpy/TreeNodes/PackageFolderTreeNode.cs | 80 ++++++++++++++++ 4 files changed, 177 insertions(+), 38 deletions(-) create mode 100644 ILSpy/TreeNodes/PackageFolderTreeNode.cs diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 4a9cb4e16..58e365395 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -213,6 +213,7 @@ + diff --git a/ILSpy/LoadedPackage.cs b/ILSpy/LoadedPackage.cs index 4dfa9aeda..077a6b432 100644 --- a/ILSpy/LoadedPackage.cs +++ b/ILSpy/LoadedPackage.cs @@ -20,6 +20,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; +using System.Linq; +using System.Reflection; using ICSharpCode.Decompiler.Metadata; @@ -37,22 +39,80 @@ namespace ICSharpCode.ILSpy public PackageKind Kind { get; } - public List Entries { get; } = new List(); + /// + /// List of all entries, including those in sub-directories within the package. + /// + public IReadOnlyList Entries { get; } - public static LoadedPackage FromZipFile(string file) + internal IReadOnlyList TopLevelEntries { get; } + internal IReadOnlyList TopLevelFolders { get; } + + public LoadedPackage(PackageKind kind, IEnumerable entries) { - using (var archive = ZipFile.OpenRead(file)) + this.Kind = kind; + this.Entries = entries.ToArray(); + var topLevelEntries = new List(); + var folders = new Dictionary(); + var rootFolder = new PackageFolder(""); + folders.Add("", rootFolder); + foreach (var entry in this.Entries) { - LoadedPackage result = new LoadedPackage(); - foreach (var entry in archive.Entries) - { - result.Entries.Add(new ZipFileEntry(file, entry)); - } + var (dirname, filename) = SplitName(entry.Name); + GetFolder(dirname).Entries.Add(new FolderEntry(filename, entry)); + } + this.TopLevelEntries = rootFolder.Entries; + this.TopLevelFolders = rootFolder.Folders; + + static (string, string) SplitName(string filename) + { + int pos = filename.LastIndexOfAny(new char[] { '/', '\\' }); + if (pos == -1) + return ("", filename); // file in root + else + return (filename.Substring(0, pos), filename.Substring(pos + 1)); + } + + PackageFolder GetFolder(string name) + { + if (folders.TryGetValue(name, out var result)) + return result; + var (dirname, basename) = SplitName(name); + PackageFolder parent = GetFolder(dirname); + result = new PackageFolder(basename); + parent.Folders.Add(result); + folders.Add(name, result); return result; } } - class ZipFileEntry : PackageEntry + public static LoadedPackage FromZipFile(string file) + { + using var archive = ZipFile.OpenRead(file); + return new LoadedPackage(PackageKind.Zip, + archive.Entries.Select(entry => new ZipFileEntry(file, entry))); + } + + /// + /// Entry inside a package folder. Effectively renames the entry. + /// + sealed class FolderEntry : PackageEntry + { + readonly PackageEntry originalEntry; + public override string Name { get; } + + public FolderEntry(string name, PackageEntry originalEntry) + { + this.Name = name; + this.originalEntry = originalEntry; + } + + public override ManifestResourceAttributes Attributes => originalEntry.Attributes; + public override string FullName => originalEntry.FullName; + public override ResourceType ResourceType => originalEntry.ResourceType; + public override Stream TryOpenStream() => originalEntry.TryOpenStream(); + } + + sealed class ZipFileEntry : PackageEntry { readonly string zipFile; public override string Name { get; } @@ -67,19 +127,17 @@ namespace ICSharpCode.ILSpy public override Stream TryOpenStream() { Debug.WriteLine("Decompress " + Name); - using (var archive = ZipFile.OpenRead(zipFile)) + using var archive = ZipFile.OpenRead(zipFile); + var entry = archive.GetEntry(Name); + if (entry == null) + return null; + var memoryStream = new MemoryStream(); + using (var s = entry.Open()) { - var entry = archive.GetEntry(Name); - if (entry == null) - return null; - var memoryStream = new MemoryStream(); - using (var s = entry.Open()) - { - s.CopyTo(memoryStream); - } - memoryStream.Position = 0; - return memoryStream; + s.CopyTo(memoryStream); } + memoryStream.Position = 0; + return memoryStream; } } } @@ -96,4 +154,20 @@ namespace ICSharpCode.ILSpy /// public abstract string FullName { get; } } + + class PackageFolder + { + /// + /// Gets the short name of the folder. + /// + public string Name { get; } + + public PackageFolder(string name) + { + this.Name = name; + } + + public List Folders { get; } = new List(); + public List Entries { get; } = new List(); + } } diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index 10e891ab1..1000f10b0 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; @@ -176,7 +175,8 @@ namespace ICSharpCode.ILSpy.TreeNodes } else if (loadResult.Package != null) { - LoadChildrenForPackage(loadResult.Package); + var package = loadResult.Package; + this.Children.AddRange(PackageFolderTreeNode.LoadChildrenForFolder(package.TopLevelFolders, package.TopLevelEntries)); } } @@ -216,22 +216,6 @@ namespace ICSharpCode.ILSpy.TreeNodes } } - private void LoadChildrenForPackage(LoadedPackage package) - { - foreach (var entry in package.Entries.OrderBy(e => e.Name)) - { - if (entry.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || entry.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) - { - var stream = Task.Run(entry.TryOpenStream); - this.Children.Add(new AssemblyTreeNode(new LoadedAssembly(AssemblyList, entry.FullName, stream), entry.Name)); - continue; - } - this.Children.Add(ResourceTreeNode.Create(entry)); - } - } - - public override bool CanExpandRecursively => true; - /// /// Finds the node for a top-level type. /// diff --git a/ILSpy/TreeNodes/PackageFolderTreeNode.cs b/ILSpy/TreeNodes/PackageFolderTreeNode.cs new file mode 100644 index 000000000..5014ebad3 --- /dev/null +++ b/ILSpy/TreeNodes/PackageFolderTreeNode.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Linq; + +using ICSharpCode.Decompiler; +using ICSharpCode.TreeView; + +namespace ICSharpCode.ILSpy.TreeNodes +{ + /// + /// Lists the embedded resources in an assembly. + /// + sealed class PackageFolderTreeNode : ILSpyTreeNode + { + readonly PackageFolder folder; + + public PackageFolderTreeNode(PackageFolder folder, string text = null) + { + this.folder = folder; + this.Text = text ?? folder.Name; + this.LazyLoading = true; + } + + public override object Text { get; } + + public override object Icon => Images.FolderClosed; + + public override object ExpandedIcon => Images.FolderOpen; + + protected override void LoadChildren() + { + this.Children.AddRange(LoadChildrenForFolder(folder.Folders, folder.Entries)); + } + + internal static IEnumerable LoadChildrenForFolder(IReadOnlyList folders, IReadOnlyList entries) + { + if (folders.Count == 1 && entries.Count == 0) + { + + } + foreach (var folder in folders.OrderBy(f => f.Name)) + { + string newName = folder.Name; + var subfolder = folder; + while (subfolder.Folders.Count == 1 && subfolder.Entries.Count == 0) + { + // special case: a folder that only contains a single sub-folder + subfolder = subfolder.Folders[0]; + newName = $"{newName}/{subfolder.Name}"; + } + yield return new PackageFolderTreeNode(subfolder, newName); + } + foreach (var entry in entries.OrderBy(e => e.Name)) + { + yield return ResourceTreeNode.Create(entry); + } + } + + public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) + { + } + } +}