From d051e3aca4c16b40e623d07825de5b0d9bb54810 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 31 Jan 2020 14:55:38 +0100 Subject: [PATCH] Fix #1923: Add progress reporting to PowerShell GetDecompiledProjectCmdlet --- .../GetDecompiledProjectCmdlet.cs | 58 ++++++++++++++++--- .../ICSharpCode.Decompiler.PowerShell.csproj | 9 ++- .../CSharp/WholeProjectDecompiler.cs | 18 +++++- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs index faf167052..26818b56a 100644 --- a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs +++ b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs @@ -1,8 +1,9 @@ using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.IO; using System.Management.Automation; -using System.Text; +using System.Threading; +using System.Threading.Tasks; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.Metadata; @@ -10,7 +11,7 @@ namespace ICSharpCode.Decompiler.PowerShell { [Cmdlet(VerbsCommon.Get, "DecompiledProject")] [OutputType(typeof(string))] - public class GetDecompiledProjectCmdlet : PSCmdlet + public class GetDecompiledProjectCmdlet : PSCmdlet, IProgress { [Parameter(Position = 0, Mandatory = true)] public CSharpDecompiler Decompiler { get; set; } @@ -20,6 +21,20 @@ namespace ICSharpCode.Decompiler.PowerShell [ValidateNotNullOrEmpty] public string LiteralPath { get; set; } + int completed; + string fileName; + ConcurrentQueue progress = new ConcurrentQueue(); + + public void Report(DecompilationProgress value) + { + int current = completed; + int next = current + 1; + next = Interlocked.CompareExchange(ref completed, next, current); + progress.Enqueue(new ProgressRecord(1, "Decompiling " + fileName, $"Completed {next} of {value.TotalNumberOfFiles}: {value.Status}") { + PercentComplete = (int)(next * 100.0 / value.TotalNumberOfFiles) + }); + } + protected override void ProcessRecord() { string path = GetUnresolvedProviderPathFromPSPath(LiteralPath); @@ -29,16 +44,43 @@ namespace ICSharpCode.Decompiler.PowerShell } try { - WholeProjectDecompiler decompiler = new WholeProjectDecompiler(); - PEFile module = Decompiler.TypeSystem.MainModule.PEFile; - decompiler.AssemblyResolver = new UniversalAssemblyResolver(module.FileName, false, module.Reader.DetectTargetFrameworkId()); - decompiler.DecompileProject(module, path); + var task = Task.Run(() => DoDecompile(path)); + int timeout = 100; + + // Give the decompiler some time to spin up all threads + Thread.Sleep(timeout); - WriteObject("Decompilation finished"); + while (!task.IsCompleted) { + if (progress.TryDequeue(out var record)) { + while (progress.TryDequeue(out var next)) { + record = next; + } + timeout = 100; + WriteProgress(record); + } else { + Thread.Sleep(timeout); + timeout = Math.Min(1000, timeout * 2); + } + } + + task.Wait(); + + WriteProgress(new ProgressRecord(1, "Decompiling " + fileName, "Decompilation finished") { RecordType = ProgressRecordType.Completed }); } catch (Exception e) { WriteVerbose(e.ToString()); WriteError(new ErrorRecord(e, ErrorIds.DecompilationFailed, ErrorCategory.OperationStopped, null)); } } + + private void DoDecompile(string path) + { + WholeProjectDecompiler decompiler = new WholeProjectDecompiler(); + PEFile module = Decompiler.TypeSystem.MainModule.PEFile; + decompiler.AssemblyResolver = new UniversalAssemblyResolver(module.FileName, false, module.Reader.DetectTargetFrameworkId()); + decompiler.ProgressIndicator = this; + fileName = module.FileName; + completed = 0; + decompiler.DecompileProject(module, path); + } } } diff --git a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj index d49bfc479..f8a4d616e 100644 --- a/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj +++ b/ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj @@ -9,10 +9,17 @@ - + + + + + + + + diff --git a/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs index f8283cc3f..23cce2661 100644 --- a/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs @@ -17,7 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -89,6 +88,8 @@ namespace ICSharpCode.Decompiler.CSharp public string StrongNameKeyFile { get; set; } public int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount; + + public IProgress ProgressIndicator { get; set; } #endregion // per-run members @@ -364,6 +365,8 @@ namespace ICSharpCode.Decompiler.CSharp return Path.Combine(dir, file); } }, StringComparer.OrdinalIgnoreCase).ToList(); + int total = files.Count; + var progress = this.ProgressIndicator; DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, settings); Parallel.ForEach( files, @@ -382,6 +385,7 @@ namespace ICSharpCode.Decompiler.CSharp throw new DecompilerException(module, $"Error decompiling for '{file.Key}'", innerException); } } + progress?.Report(new DecompilationProgress(total, file.Key)); }); return files.Select(f => Tuple.Create("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, cancellationToken)); } @@ -537,4 +541,16 @@ namespace ICSharpCode.Decompiler.CSharp } } } + + public readonly struct DecompilationProgress + { + public readonly int TotalNumberOfFiles; + public readonly string Status; + + public DecompilationProgress(int total, string status = null) + { + this.TotalNumberOfFiles = total; + this.Status = status ?? ""; + } + } }