Browse Source

Fix #3655: ExtractPackageEntryContextMenuEntry

pull/3666/head
Siegfried Pammer 2 months ago
parent
commit
d8c619d2ad
  1. 18
      ICSharpCode.ILSpyX/LoadedPackage.cs
  2. 147
      ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs
  3. 14
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  4. 6
      ILSpy/TreeNodes/PackageFolderTreeNode.cs
  5. 32
      ILSpy/Util/ShellHelper.cs

18
ICSharpCode.ILSpyX/LoadedPackage.cs

@ -143,6 +143,7 @@ namespace ICSharpCode.ILSpyX @@ -143,6 +143,7 @@ namespace ICSharpCode.ILSpyX
{
readonly PackageEntry originalEntry;
public override string Name { get; }
public override string FullName => originalEntry.Name;
public FolderEntry(string name, PackageEntry originalEntry)
{
@ -151,7 +152,7 @@ namespace ICSharpCode.ILSpyX @@ -151,7 +152,7 @@ namespace ICSharpCode.ILSpyX
}
public override ManifestResourceAttributes Attributes => originalEntry.Attributes;
public override string FullName => originalEntry.FullName;
public override string PackageQualifiedFileName => originalEntry.PackageQualifiedFileName;
public override ResourceType ResourceType => originalEntry.ResourceType;
public override Stream? TryOpenStream() => originalEntry.TryOpenStream();
public override long? TryGetLength() => originalEntry.TryGetLength();
@ -161,7 +162,9 @@ namespace ICSharpCode.ILSpyX @@ -161,7 +162,9 @@ namespace ICSharpCode.ILSpyX
{
readonly string zipFile;
public override string Name { get; }
public override string FullName => $"zip://{zipFile};{Name}";
public override string PackageQualifiedFileName => $"zip://{zipFile};{Name}";
public override string FullName => Name;
public ZipFileEntry(string zipFile, ZipArchiveEntry entry)
{
@ -210,7 +213,8 @@ namespace ICSharpCode.ILSpyX @@ -210,7 +213,8 @@ namespace ICSharpCode.ILSpyX
}
public override string Name => entry.RelativePath;
public override string FullName => $"bundle://{bundleFile};{Name}";
public override string FullName => Name;
public override string PackageQualifiedFileName => $"bundle://{bundleFile};{Name}";
public override Stream TryOpenStream()
{
@ -251,7 +255,12 @@ namespace ICSharpCode.ILSpyX @@ -251,7 +255,12 @@ namespace ICSharpCode.ILSpyX
public abstract override string Name { get; }
/// <summary>
/// Gets the full file name for the entry.
/// Gets the full file name including the full file name of the package (prefixed with e.g., bundle:// or zip://).
/// </summary>
public abstract string PackageQualifiedFileName { get; }
/// <summary>
/// Gets the full name of the file name relative to the package root.
/// </summary>
public abstract string FullName { get; }
}
@ -273,6 +282,7 @@ namespace ICSharpCode.ILSpyX @@ -273,6 +282,7 @@ namespace ICSharpCode.ILSpyX
this.Name = name;
}
public PackageFolder? Parent => parent;
public List<PackageFolder> Folders { get; } = new List<PackageFolder>();
public List<PackageEntry> Entries { get; } = new List<PackageEntry>();

147
ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs

@ -27,6 +27,7 @@ using System.Windows; @@ -27,6 +27,7 @@ using System.Windows;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TextView;
@ -92,99 +93,79 @@ namespace ICSharpCode.ILSpy @@ -92,99 +93,79 @@ namespace ICSharpCode.ILSpy
}
}
static string GetPackageFolderPath(SharpTreeNode node)
{
string name = "";
while (node is PackageFolderTreeNode)
{
name = Path.Combine(node.Text.ToString(), name);
node = node.Parent;
}
return name;
}
static string GetFileName(string path, bool isFile, SharpTreeNode containingNode, PackageEntry entry)
{
string fileName;
if (isFile)
{
fileName = path;
}
else
{
string relativePackagePath = WholeProjectDecompiler.SanitizeFileName(Path.Combine(GetPackageFolderPath(containingNode), entry.Name));
fileName = Path.Combine(path, relativePackagePath);
}
return fileName;
}
internal static void Save(DockWorkspace dockWorkspace, IEnumerable<SharpTreeNode> nodes, string path, bool isFile)
internal static void Save(DockWorkspace dockWorkspace, ICollection<SharpTreeNode> nodes, string path, bool isFile)
{
dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
AvalonEditTextOutput output = new AvalonEditTextOutput();
Stopwatch stopwatch = Stopwatch.StartNew();
var writtenFiles = new List<string>();
foreach (var node in nodes)
Dictionary<string, int> fileNameCounts = new Dictionary<string, int>(Platform.FileNameComparer);
foreach (var (entry, fileName) in CollectAllFiles(nodes))
{
if (node is AssemblyTreeNode { PackageEntry: { } assembly })
string actualFileName = WholeProjectDecompiler.SanitizeFileName(fileName);
while (fileNameCounts.TryGetValue(actualFileName, out int index))
{
string fileName = GetFileName(path, isFile, node.Parent, assembly);
SaveEntry(output, assembly, fileName);
if (File.Exists(fileName))
writtenFiles.Add(fileName);
index++;
fileNameCounts[actualFileName] = index;
actualFileName = Path.ChangeExtension(actualFileName, index + Path.GetExtension(actualFileName));
}
else if (node is ResourceTreeNode { Resource: PackageEntry { } resource })
if (!fileNameCounts.ContainsKey(actualFileName))
{
string fileName = GetFileName(path, isFile, node.Parent, resource);
SaveEntry(output, resource, fileName);
if (File.Exists(fileName))
writtenFiles.Add(fileName);
}
else if (node is PackageFolderTreeNode)
{
node.EnsureLazyChildren();
foreach (var item in node.DescendantsAndSelf())
{
if (item is AssemblyTreeNode { PackageEntry: { } asm })
{
string fileName = GetFileName(path, isFile, item.Parent, asm);
SaveEntry(output, asm, fileName);
if (File.Exists(fileName))
writtenFiles.Add(fileName);
}
else if (item is ResourceTreeNode { Resource: PackageEntry { } entry })
{
string fileName = GetFileName(path, isFile, item.Parent, entry);
SaveEntry(output, entry, fileName);
if (File.Exists(fileName))
writtenFiles.Add(fileName);
}
else if (item is PackageFolderTreeNode)
{
Directory.CreateDirectory(Path.Combine(path, WholeProjectDecompiler.SanitizeFileName(GetPackageFolderPath(item))));
}
}
fileNameCounts[actualFileName] = 1;
}
SaveEntry(output, entry, Path.Combine(path, actualFileName));
}
stopwatch.Stop();
output.WriteLine(Resources.GenerationCompleteInSeconds, stopwatch.Elapsed.TotalSeconds.ToString("F1"));
output.WriteLine();
// If we have written files, open explorer and select them grouped by folder; otherwise fall back to opening containing folder.
if (writtenFiles.Count > 0)
{
output.AddButton(null, Resources.OpenExplorer, delegate { ShellHelper.OpenFolderAndSelectItems(writtenFiles); });
}
if (isFile && File.Exists(path))
output.AddButton(null, Resources.OpenExplorer, delegate { ShellHelper.OpenFolderAndSelectItem(path); });
else
{
if (isFile && File.Exists(path))
output.AddButton(null, Resources.OpenExplorer, delegate { ShellHelper.OpenFolderAndSelectItem(path); });
else
output.AddButton(null, Resources.OpenExplorer, delegate { ShellHelper.OpenFolder(path); });
}
output.AddButton(null, Resources.OpenExplorer, delegate { ShellHelper.OpenFolder(path); });
output.WriteLine();
return output;
}, ct)).Then(dockWorkspace.ShowText).HandleExceptions();
static IEnumerable<(PackageEntry Entry, string TargetFileName)> CollectAllFiles(ICollection<SharpTreeNode> nodes)
{
foreach (var node in nodes)
{
if (node is AssemblyTreeNode { PackageEntry: { } assembly })
{
yield return (assembly, Path.GetFileName(assembly.FullName));
}
else if (node is ResourceTreeNode { Resource: PackageEntry { } resource })
{
yield return (resource, Path.GetFileName(resource.FullName));
}
else if (node is AssemblyTreeNode { PackageKind: not null } asm)
{
var package = asm.LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult().Package;
foreach (var entry in package.Entries)
{
yield return (entry, entry.FullName);
}
}
else if (node is PackageFolderTreeNode folder)
{
int prefixLength = 0;
PackageFolder current = folder.Folder;
if (nodes.Count > 1)
current = current.Parent;
while (current != null)
{
prefixLength += current.Name.Length + 1;
current = current.Parent;
}
if (prefixLength > 0)
prefixLength--;
foreach (var item in TreeTraversal.PreOrder(folder.Folder, f => f.Folders).SelectMany(f => f.Entries))
{
yield return (item, item.FullName.Substring(prefixLength));
}
}
}
}
}
static void SaveEntry(ITextOutput output, PackageEntry entry, string targetFileName)
@ -197,6 +178,8 @@ namespace ICSharpCode.ILSpy @@ -197,6 +178,8 @@ namespace ICSharpCode.ILSpy
return;
}
Directory.CreateDirectory(Path.GetDirectoryName(targetFileName));
stream.Position = 0;
using FileStream fileStream = new FileStream(targetFileName, FileMode.OpenOrCreate);
stream.CopyTo(fileStream);
@ -211,7 +194,7 @@ namespace ICSharpCode.ILSpy @@ -211,7 +194,7 @@ namespace ICSharpCode.ILSpy
{
if (node is AssemblyTreeNode { PackageEntry: { } } or PackageFolderTreeNode)
return true;
if (node is ResourceTreeNode { Resource: PackageEntry { } resource } && resource.FullName.StartsWith("bundle://"))
if (node is ResourceTreeNode { Resource: PackageEntry { } resource } && resource.PackageQualifiedFileName.StartsWith("bundle://"))
return true;
return false;
}
@ -241,22 +224,14 @@ namespace ICSharpCode.ILSpy @@ -241,22 +224,14 @@ namespace ICSharpCode.ILSpy
return;
}
asm.EnsureLazyChildren();
ExtractPackageEntryContextMenuEntry.Save(dockWorkspace, asm.Descendants(), folderName, false);
ExtractPackageEntryContextMenuEntry.Save(dockWorkspace, [asm], folderName, false);
}
public bool IsEnabled(TextViewContext context) => true;
public bool IsVisible(TextViewContext context)
{
if (context.SelectedTreeNodes is [AssemblyTreeNode { LoadedAssembly.IsLoaded: true, LoadedAssembly.HasLoadError: false, PackageEntry: null } asm])
{
// Using .GetAwaiter().GetResult() is no problem here, since we already checked IsLoaded and HasLoadError.
var loadResult = asm.LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult();
if (loadResult.Package is { Kind: PackageKind.Bundle })
return true;
}
return false;
return context.SelectedTreeNodes is [AssemblyTreeNode { PackageEntry: null, PackageKind: PackageKind.Bundle }];
}
}
}

14
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -83,6 +83,20 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -83,6 +83,20 @@ namespace ICSharpCode.ILSpy.TreeNodes
/// </summary>
public PackageEntry PackageEntry { get; }
/// <summary>
/// If this assembly is a bundle or package, returns the <see cref="LoadedAssembly.PackageKind"/>,
/// otherwise returns <see langword="null"/>.
/// Returns <see langword="null"/> if this assembly was not yet loaded or an error occurred.
/// </summary>
public LoadedPackage.PackageKind? PackageKind {
get {
if (LoadedAssembly.HasLoadError || !LoadedAssembly.IsLoaded)
return null;
var loadResult = LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult();
return loadResult.Package?.Kind;
}
}
public override bool IsAutoLoaded {
get {
return LoadedAssembly.IsAutoLoaded;

6
ILSpy/TreeNodes/PackageFolderTreeNode.cs

@ -31,11 +31,11 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -31,11 +31,11 @@ namespace ICSharpCode.ILSpy.TreeNodes
/// </summary>
sealed class PackageFolderTreeNode : ILSpyTreeNode
{
readonly PackageFolder folder;
public PackageFolder Folder { get; }
public PackageFolderTreeNode(PackageFolder folder, string text = null)
{
this.folder = folder;
this.Folder = folder;
this.Text = text ?? folder.Name;
this.LazyLoading = true;
}
@ -48,7 +48,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -48,7 +48,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
protected override void LoadChildren()
{
this.Children.AddRange(LoadChildrenForFolder(folder));
this.Children.AddRange(LoadChildrenForFolder(Folder));
}
internal static IEnumerable<SharpTreeNode> LoadChildrenForFolder(PackageFolder root)

32
ILSpy/Util/ShellHelper.cs

@ -44,37 +44,9 @@ namespace ICSharpCode.ILSpy.Util @@ -44,37 +44,9 @@ namespace ICSharpCode.ILSpy.Util
public static void OpenFolder(string folderPath)
{
nint folderPidl = IntPtr.Zero;
try
{
if (string.IsNullOrEmpty(folderPath))
return;
if (!Directory.Exists(folderPath))
return;
int hr = SHParseDisplayName(folderPath, IntPtr.Zero, out folderPidl, 0, out var attrs);
Marshal.ThrowExceptionForHR(hr);
hr = SHOpenFolderAndSelectItems(folderPidl, 0, null, 0);
Marshal.ThrowExceptionForHR(hr);
}
catch (Exception ex) when (ex is COMException or Win32Exception)
{
// fall back to Process.Start
OpenFolderFallback(folderPath);
}
finally
{
if (folderPidl != IntPtr.Zero)
CoTaskMemFree(folderPidl);
}
}
static void OpenFolderFallback(string path)
{
try
{
Process.Start(new ProcessStartInfo { FileName = path, UseShellExecute = true });
Process.Start(new ProcessStartInfo { FileName = folderPath, UseShellExecute = true });
}
catch (Exception)
{
@ -156,7 +128,7 @@ namespace ICSharpCode.ILSpy.Util @@ -156,7 +128,7 @@ namespace ICSharpCode.ILSpy.Util
catch (Exception ex) when (ex is COMException or Win32Exception)
{
// fall back to Process.Start
OpenFolderFallback(folder);
OpenFolder(folder);
}
finally
{

Loading…
Cancel
Save