using Amazon.PowerShell.Common; using System; using System.Management.Automation; using System.Threading; using Amazon.Runtime; namespace Amazon.PowerShell.Utils { /// /// Class that runs a task on a background thread and reports /// progress to the source cmdlet. /// /// Due to PowerShell limitation, cmdlet progress must be reported /// from the thread the cmdlet is executing on. /// public class ProgressRunner { #region Private members private Cmdlet SourceCmdlet { get; set; } private Action MainAction { get; set; } private Exception MainException { get; set; } private bool MainActionCompleted { get; set; } private WaitCallback ExecutionCallback { get; set; } public int ActivityId { get; set; } private ReportedProgress Progress { get; set; } private static readonly TimeSpan ProgressUpdatePeriod = TimeSpan.FromSeconds(.25); private class ReportedProgress { public string Activity { get; private set; } public int PercentComplete { get; private set; } public string StatusDescription { get; private set; } public ReportedProgress(string activity, int percentComplete, string statusDescription) { this.Activity = activity; this.PercentComplete = percentComplete; this.StatusDescription = statusDescription; } } #endregion #region Public methods/constructor /// /// Constructs the runner. /// /// Source cmdlet is used to WriteProgress. /// All progress reports must be routed through the runner's Report method. /// /// public ProgressRunner(Cmdlet sourceCmdlet) { SourceCmdlet = sourceCmdlet; MainException = null; ActivityId = this.GetHashCode(); } /// /// Reports the status to the shell /// /// /// /// public void Report(string activity, int percentComplete, string statusDescription) { Progress = new ReportedProgress(activity, percentComplete, statusDescription); } /// /// Starts execution of the main action and begins /// processing progress records. /// internal void Run(Action action, ProgressTracker tracker) { MainAction = action; ExecutionCallback = o => { try { MainAction(); } catch (Exception e) { MainException = e; } MainActionCompleted = true; }; ThreadPool.QueueUserWorkItem(ExecutionCallback); while (!MainActionCompleted) { Thread.Sleep(ProgressUpdatePeriod); PushRecord(false); } PushRecord(true); if (MainException != null) throw MainException; } // Pushes a record (Completed or Processing) for the current progress to the shell private void PushRecord(bool isComplete) { ProgressRecord record; record = CreateRecord(isComplete); SourceCmdlet.WriteProgress(record); } // Creates a record (Completed or Processing) for the current progress private ProgressRecord CreateRecord(bool isComplete) { var currentProgress = Progress; var record = new ProgressRecord(this.ActivityId, currentProgress.Activity, currentProgress.StatusDescription) { PercentComplete = currentProgress.PercentComplete, RecordType = isComplete ? ProgressRecordType.Completed : ProgressRecordType.Processing }; return record; } /// /// Calls the Run method and returns a new CmdletOutput instance. /// If an exception was thrown, it will be stored in CmdletOutput.ErrorResponse /// /// public CmdletOutput SafeRun(Action action, ProgressTracker tracker) { return SafeRun(action, tracker, new CmdletOutput()); } /// /// Calls the Run method, posting input into the supplied CmdletOutput instance. /// If an exception was thrown, it will be stored in CmdletOutput.ErrorResponse /// /// public CmdletOutput SafeRun(Action action, ProgressTracker tracker, CmdletOutput output) { try { Run(action, tracker); } catch (AmazonServiceException exc) { var webException = exc.InnerException as System.Net.WebException; if (webException != null) { var serviceCmdlet = SourceCmdlet as ServiceCmdlet; if (serviceCmdlet != null) { throw new Exception(Utils.Common.FormatNameResolutionFailureMessage(serviceCmdlet._RegionEndpoint, serviceCmdlet.EndpointUrl, webException.Message), webException); } } throw; } catch (Exception e) { output.ErrorResponse = e; } return output; } #endregion } /// /// Base, non-generic class for tracking an activity and reporting the progress. /// public abstract class ProgressTracker { #region Private members/methods private readonly ProgressRunner _runner; #endregion /// /// Constructs a tracker to work with a given ProgressRunner. /// The subscribe action must subscribe the specified handler to the /// even the tracker will be listening to. /// /// protected ProgressTracker(ProgressRunner runner) { _runner = runner; } #region Protected members protected void ReportProgress(int done, int total, string message, params object[] args) { ReportProgress((int)(((float)done / total) * 100), message, args); } protected void ReportProgress(int percentComplete, string message, params object[] args) { _runner.Report(Activity, percentComplete, string.Format(message, args)); } #endregion #region Public abstract methods /// /// Name of the activity. /// public abstract string Activity { get; } #endregion } /// /// Class that tracks progress of an activity through event callbacks /// and reports the activity to a ProgressRunner. /// /// public abstract class ProgressTracker : ProgressTracker where T : EventArgs { /// /// Constructs a tracker to work with a given ProgressRunner. /// The subscribe action must subscribe the specified handler to the /// even the tracker will be listening to. /// /// /// protected ProgressTracker(ProgressRunner runner, Action> subscribe) : base(runner) { subscribe((s, e) => { ReportProgress(e); }); } #region Public abstract methods /// /// Abstract method to process event data. Must be overriden by subclass. /// If progress has changed, should invoke ReportProgress to update shell. /// /// public abstract void ReportProgress(T args); #endregion } }