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
}
}