using PaintDotNet.Measurement.Enum;
using System;
using System.ComponentModel;
using System.Threading;
namespace PaintDotNet.Measurement
{
public abstract class HistoryFunction
{
private int criticalRegionCount = 0;
private bool executed = false;
private volatile bool pleaseCancel = false;
private ISynchronizeInvoke eventSink = null;
public bool IsAsync
{
get
{
return this.eventSink != null;
}
}
public ISynchronizeInvoke EventSink
{
get
{
if (!IsAsync)
{
throw new InvalidOperationException("EventSink property is only accessible when IsAsync is true");
}
return this.eventSink;
}
}
public bool Cancellable
{
get
{
return ((this.actionFlags & ActionFlags.Cancellable) == ActionFlags.Cancellable);
}
}
private ActionFlags actionFlags;
public ActionFlags ActionFlags
{
get
{
return this.actionFlags;
}
}
protected bool PleaseCancel
{
get
{
return this.pleaseCancel;
}
}
private void ExecuteTrampoline(object context)
{
Execute((IDocumentWorkspace)context);
}
///
/// Executes the HistoryFunction.
///
///
/// A HistoryMemento instance if an operation was performed successfully,
/// or null for success but no operation was performed.
///
/// There was error while performing the operation. No changes have been made to the HistoryWorkspace (no-op).
///
///
/// If this HistoryFunction's ActionFlags contain the HistoryFlags.Cancellable bit, then it will be executed in
/// a background thread.
///
public HistoryMemento Execute(IDocumentWorkspace historyWorkspace)
{
SystemLayer.Tracing.LogFeature("HF(" + GetType().Name + ")");
HistoryMemento returnVal = null;
Exception exception = null;
try
{
try
{
if (this.executed)
{
throw new InvalidOperationException("Already executed this HistoryFunction");
}
this.executed = true;
returnVal = OnExecute(historyWorkspace);
return returnVal;
}
catch (ArgumentOutOfRangeException aoorex)
{
if (this.criticalRegionCount > 0)
{
throw;
}
else
{
throw new HistoryFunctionNonFatalException(null, aoorex);
}
}
catch (OutOfMemoryException oomex)
{
if (this.criticalRegionCount > 0)
{
throw;
}
else
{
throw new HistoryFunctionNonFatalException(null, oomex);
}
}
}
catch (Exception ex)
{
if (IsAsync)
{
exception = ex;
return returnVal;
}
else
{
throw;
}
}
finally
{
if (IsAsync)
{
OnFinished(returnVal, exception);
}
}
}
///
/// Executes the function asynchronously.
///
///
///
///
/// If you use this method to execute the function, completion will be signified
/// using the Finished event. This will be raised no matter if the function completes
/// successfully or not.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "eventSink")]
public void BeginExecute(ISynchronizeInvoke eventSink, IDocumentWorkspace historyWorkspace, EventHandler> finishedCallback)
{
if (finishedCallback == null)
{
throw new ArgumentNullException("finishedCallback");
}
if (this.eventSink != null)
{
throw new InvalidOperationException("already executing this function");
}
this.eventSink = eventSink;
this.Finished += finishedCallback;
System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(ExecuteTrampoline), historyWorkspace);
}
///
/// This event is raised when the function has finished execution, whether it finished successfully or not.
///
public event EventHandler> Finished;
private void OnFinished(HistoryMemento memento, Exception exception)
{
if (this.eventSink.InvokeRequired)
{
this.eventSink.BeginInvoke(
new Procedure(OnFinished),
new object[2] { memento, exception });
}
else
{
if (exception != null)
{
throw new WorkerThreadException(exception);
}
if (Finished != null)
{
Finished(this, new EventArgs(memento));
}
}
}
///
/// This event is raised when the function wants to report its progress.
///
///
/// This event is only ever raised if the function has the ActionFlags.ReportsProgres flag set.
/// There is no guarantee that the value reported via this event will start at 0 or finish at 100,
/// nor that it will report values in non-descending order. Clients of this event are advised
/// to clamp the values reported to the range [0, 100] and to define their own policy for
/// handling progress values that are less than the previously reported progress values.
///
public event ProgressEventHandler Progress;
protected virtual void OnProgress(double percent)
{
if ((this.actionFlags & ActionFlags.ReportsProgress) != ActionFlags.ReportsProgress)
{
System.Diagnostics.Debug.WriteLine("This HistoryFunction does not support reporting progress, yet it called OnProgress()");
}
else if (Progress != null)
{
this.eventSink.BeginInvoke(Progress, new object[2] { this, new ProgressEventArgs(percent) });
}
}
///
/// Call this method from within OnExecute() in order to mark areas of your code where
/// the throwing of an OutOfMemoryException does not guarantee the coherency of data.
///
///
/// If OnExecute() is not within a critical region and throws an OutOfMemoryException,
/// it will be wrapped inside a HistoryFunctionNonFatalException as the InnerException.
/// This prevents the execution host from treating it as an operation that must
/// close the application.
/// Once you enter a critical region, you may not leave it. Therefore, it is recommended
/// that you do as much preparatory work as possible before entering a critical region.
/// Once a HistoryFunction has entered its critical region, it may not be cancelled.
///
protected void EnterCriticalRegion()
{
Interlocked.Increment(ref this.criticalRegionCount);
}
///
/// If the HistoryFunction is being executed asynchronously using BeginExecute() and EndExecute(),
/// and if it also has the ActionFlags.Cancellable flag, then you may request that it cancel its
/// long running operation by calling this method.
///
///
/// The request to cancel may have no effect, and the history function may still complete normally.
/// This may happen if the function has already entered its critical region, or if it has already
/// completed before this method is called.
///
public void RequestCancel()
{
if (!Cancellable)
{
throw new InvalidOperationException("This HistoryFunction is not cancellable");
}
if (!IsAsync)
{
throw new InvalidOperationException("This function is not in the process of being executed asynchronously, and therefore cannot be cancelled");
}
this.pleaseCancel = true;
OnCancelRequested();
}
public event EventHandler CancelRequested;
protected virtual void OnCancelRequested()
{
if (!this.pleaseCancel)
{
throw new InvalidOperationException("OnCancelRequested() was called when pleaseCancel equaled false");
}
if (CancelRequested != null)
{
this.eventSink.BeginInvoke(CancelRequested, new object[2] { this, EventArgs.Empty });
}
}
public abstract HistoryMemento OnExecute(IDocumentWorkspace historyWorkspace);
public HistoryFunction(ActionFlags actionFlags)
{
this.actionFlags = actionFlags;
}
}
}