Saxonite 1.2.11.2
This commit is contained in:
@@ -1,29 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Tasks;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using static Sarand.Net.Standard.Base.Procs.SaProcess_Base;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
public interface ISacsProcessNotifier
|
||||
{
|
||||
void OnStartProcess ();
|
||||
void SetProgressInfo (List<String> progressInfo);
|
||||
void OnEndProcess ();
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
public abstract class SaProcess_Base : SacsDisposable
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Exceptions.
|
||||
[Serializable]
|
||||
public class InProgressException : Exception
|
||||
{
|
||||
public InProgressException() { }
|
||||
public InProgressException(string message) : base(message) { }
|
||||
public InProgressException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Process options and groups - for configuration of process to run.
|
||||
// See Sarand.Win.Forms.Utility.SaForm_SelectOptions.
|
||||
public class OptionData
|
||||
{
|
||||
public OptionData (TypeCodeDesc option)
|
||||
public OptionData(TypeCodeDesc option)
|
||||
{
|
||||
Option = option;
|
||||
}
|
||||
@@ -33,7 +41,7 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
}
|
||||
public class GroupData
|
||||
{
|
||||
public GroupData (TypeCodeDesc optionGroup)
|
||||
public GroupData(TypeCodeDesc optionGroup)
|
||||
{
|
||||
OptionGroup = optionGroup;
|
||||
}
|
||||
@@ -55,13 +63,13 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
}
|
||||
public class Config
|
||||
{
|
||||
protected Config ()
|
||||
protected Config()
|
||||
{
|
||||
}
|
||||
/// <summary>Identifies the type of process.</summary>
|
||||
public ProcessType ProcessType { get; set; } = ProcessType.Unknown;
|
||||
/// <summary>Is the process to be run in 'Test' mode.</summary>
|
||||
public bool TestProcess{ get; set; } = SacsSystem.DebugMode;
|
||||
public bool TestProcess { get; set; } = SacsSystem.DebugMode;
|
||||
/// <summary>Process imports data.</summary>
|
||||
public bool Import { get { return ProcessType == ProcessType.ImportData || ProcessType == ProcessType.ImportExportData; } }
|
||||
/// <summary>Process exports data.</summary>
|
||||
@@ -74,28 +82,131 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
public bool Automated { get; set; } = false;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Process control definitions.
|
||||
|
||||
public class ProcessMessage
|
||||
{
|
||||
public ProcessMessage(String message, SaMessageLevel level = SaMessageLevel.Info)
|
||||
{
|
||||
this.Message = message;
|
||||
this.Level = level;
|
||||
}
|
||||
public String Message { get; }
|
||||
public SaMessageLevel Level { get; }
|
||||
public int Instances { get; internal set; }
|
||||
}
|
||||
|
||||
private class ProcessControlData
|
||||
{
|
||||
// Interrupt.
|
||||
private class InterruptData
|
||||
{
|
||||
public readonly Object lockable = new object();
|
||||
private bool interrupted = false;
|
||||
public bool Interrupted { get { lock (lockable) { return interrupted; } } set { lock (lockable) { interrupted = value; } } }
|
||||
}
|
||||
private InterruptData interrupt { get; } = new InterruptData();
|
||||
public bool Interrupted { get { return interrupt.Interrupted; } set { interrupt.Interrupted = value; } }
|
||||
|
||||
// Process messages.
|
||||
private class ProcessMessagesData
|
||||
{
|
||||
public readonly Object lockable = new object();
|
||||
public List<ProcessMessage> messages { get; } = new List<ProcessMessage>();
|
||||
}
|
||||
private ProcessMessagesData processMessages { get; } = new ProcessMessagesData();
|
||||
/// <summary>Convert the message levele to one of the 3 values used with Process Messages: Info, Warning, Error.</summary>
|
||||
public static SaMessageLevel GetLevel(SaMessageLevel level)
|
||||
{
|
||||
if (level == SaMessageLevel.Error || level == SaMessageLevel.Fatal || level == SaMessageLevel.Exception)
|
||||
level = SaMessageLevel.Error;
|
||||
else if (level == SaMessageLevel.Warning)
|
||||
level = SaMessageLevel.Warning;
|
||||
else
|
||||
level = SaMessageLevel.Info;
|
||||
return level;
|
||||
}
|
||||
/// <summary>Add a message of the specified level (Info, Warning, Error).</summary>
|
||||
public bool AddProcessMessage(String message, SaMessageLevel level)
|
||||
{
|
||||
message = StringUtil.TrimWhitespace(message);
|
||||
if (StringUtil.IsBlank(message)) return false;
|
||||
level = GetLevel(level);
|
||||
lock (processMessages.lockable)
|
||||
{
|
||||
ProcessMessage procMsg = processMessages.messages.Find(item => item.Message.EqualsCI(message) && item.Level == level);
|
||||
// Duplicate messages increment the 'Instances' value.
|
||||
if (procMsg != null)
|
||||
procMsg.Instances++;
|
||||
// Otherwise add a new message to the list.
|
||||
else processMessages.messages.Add(new ProcessMessage(message, level));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// <summary>Are there any message of the specified level (Info, Warning, Error).</summary>
|
||||
public bool HasProcessMessages(SaMessageLevel level)
|
||||
{
|
||||
lock (processMessages.lockable)
|
||||
return processMessages.messages.Exists(item => item.Level == GetLevel(level));
|
||||
}
|
||||
/// <summary>Get the first message of the specified level (Info, Warning, Error).</summary>
|
||||
public String GetProcessMessage(SaMessageLevel level)
|
||||
{
|
||||
lock (processMessages.lockable)
|
||||
return processMessages.messages.Find(item => item.Level == GetLevel(level))?.Message ?? "";
|
||||
}
|
||||
/// <summary>Return all process messages.</summary>
|
||||
public List<ProcessMessage> GetProcessMessages()
|
||||
{
|
||||
List<ProcessMessage> messages = new List<ProcessMessage>();
|
||||
lock (processMessages.lockable)
|
||||
messages.AddRange(processMessages.messages);
|
||||
return messages;
|
||||
}
|
||||
/// <summary>Return all process messages of the specified Level (Info, Warning, Error).</summary>
|
||||
public List<ProcessMessage> GetProcessMessages(SaMessageLevel level)
|
||||
{
|
||||
List<ProcessMessage> messages = new List<ProcessMessage>();
|
||||
lock (processMessages.lockable)
|
||||
messages.AddRange(processMessages.messages.FindAll(item => item.Level == GetLevel(level)));
|
||||
return messages;
|
||||
}
|
||||
/// <summary>Clear process messages.</summary>
|
||||
public void ClearProcessMessages()
|
||||
{
|
||||
lock (processMessages.lockable)
|
||||
processMessages.messages.Clear();
|
||||
}
|
||||
|
||||
// Task control data.
|
||||
private SacsTaskData taskData = null;
|
||||
public SacsTaskData TaskData { get { return taskData; } set { if (taskData != null) taskData.Dispose(); taskData = value; } }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
private String processError = "";
|
||||
private ProcessControlData Control { get; } = new ProcessControlData();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
protected SaProcess_Base (Config config)
|
||||
protected SaProcess_Base(Config config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
protected override void Dispose (bool disposing)
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Clear process data to dispose.
|
||||
Control.TaskData = null;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public bool DebugMode { get { return SacsSystem.DebugMode; } }
|
||||
public ISacsProcessNotifier ProcessNotifier { get; set; } = null;
|
||||
public bool Processing { get; protected set; } = false;
|
||||
public bool ProcessEnded { get; protected set; } = false;
|
||||
public DateTime StartedAt { get; private set; } = DateTime.MinValue;
|
||||
@@ -120,56 +231,195 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
public virtual List<StringPair> ProcessingStatistics { get { return new List<StringPair>(); } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Run process inline.
|
||||
public bool RunProcess ()
|
||||
// Run process asynchronously.
|
||||
private class AsyncProcessData : SacsDisposable
|
||||
{
|
||||
String fn = GetType().Name+".RunProcess";
|
||||
if (ProcessNotifier == null)
|
||||
throw new NotSupportedException("Process Notifier must be specified");
|
||||
public AsyncProcessData(Task task)
|
||||
{
|
||||
this.Task = task;
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) { }
|
||||
}
|
||||
public Task Task { get; }
|
||||
}
|
||||
private AsyncProcessData AsyncData { get; set; }
|
||||
public bool IsAsync { get { return AsyncData != null; } }
|
||||
private void ClearAsyncData()
|
||||
{
|
||||
if (AsyncData != null)
|
||||
{
|
||||
lock (AsyncData)
|
||||
{
|
||||
AsyncData.Dispose();
|
||||
AsyncData = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Run the process asynchronously.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InProgressException">If process is already running.</exception>
|
||||
public async void RunAsync()
|
||||
{
|
||||
if (AsyncData != null)
|
||||
throw new InProgressException();
|
||||
Control.TaskData = new SacsTaskData();
|
||||
// Run on separate thread.
|
||||
Task task = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Execute the process.
|
||||
RunProcess();
|
||||
}
|
||||
catch (OperationCanceledException exc)
|
||||
{
|
||||
SaLogger.Warn(exc.GetType().Name + " during asynchronous processing on " + GetType().Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clear processing data.
|
||||
ClearAsyncData();
|
||||
}
|
||||
}, Control.TaskData.CancellationToken);
|
||||
AsyncData = new AsyncProcessData(task);
|
||||
task.Start();
|
||||
await task;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Run process inline.
|
||||
public bool RunProcess()
|
||||
{
|
||||
String fn = GetType().Name + ".RunProcess";
|
||||
// Prepare process.
|
||||
bool runProcess = false;
|
||||
Reset();
|
||||
if (Control.TaskData == null)
|
||||
Control.TaskData = new SacsTaskData();
|
||||
Processing = true;
|
||||
StartedAt = DateTime.Now;
|
||||
|
||||
// Process starting.
|
||||
try
|
||||
{
|
||||
// Notify start of process and log/ignore exceptions.
|
||||
ProcessNotifier.OnStartProcess();
|
||||
InvokeProcessStarting();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Error(fn + " OnStartProcess: " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
String msg = exc.GetType().Name + ": " + exc.Message;
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Error(fn + " ProcessStarting: " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
}
|
||||
|
||||
// Process running.
|
||||
try
|
||||
{
|
||||
// Run process.
|
||||
OnProcessStarting();
|
||||
runProcess = Run();
|
||||
OnProcessEnding();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SetProcessError(exc.GetType().Name + ": " + exc.Message);
|
||||
SaLogger.Error(fn + " " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
String msg = exc.GetType().Name + ": " + exc.Message;
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Error(fn + " " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
}
|
||||
// Processing completed.
|
||||
FinishedAt = DateTime.Now;
|
||||
Processing = false;
|
||||
ProcessEnded = true;
|
||||
finally
|
||||
{
|
||||
// Processing completed.
|
||||
FinishedAt = DateTime.Now;
|
||||
Processing = false;
|
||||
ProcessEnded = true;
|
||||
Control.TaskData = null;
|
||||
}
|
||||
|
||||
// Process completed.
|
||||
try
|
||||
{
|
||||
// Notify end of process and log/ignore exceptions.
|
||||
ProcessNotifier.OnEndProcess();
|
||||
// Notify completion of process and log/ignore exceptions.
|
||||
InvokeProcessCompleted();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Error(fn + " OnEndProcess: " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
String msg = exc.GetType().Name + ": " + exc.Message;
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Error(fn + " ProcessCompleted: " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
}
|
||||
return runProcess;
|
||||
}
|
||||
protected abstract bool Run ();
|
||||
protected virtual void OnProcessStarting () { }
|
||||
protected virtual void OnProcessEnding () { }
|
||||
protected abstract bool Run();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public async Task StopProcessAsync(int timeout = 50)
|
||||
{
|
||||
if (!Processing) return;
|
||||
if (AsyncData != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Cancel the task.
|
||||
Control.TaskData.TriggerCancellation();
|
||||
// Wait for task termination or timeout.
|
||||
if (await Task.WhenAny(AsyncData.Task, Task.Delay(Math.Max(20, Math.Min(1000, timeout)))) != AsyncData.Task)
|
||||
{
|
||||
// Timeout completed before task - force termination.
|
||||
Control.TaskData.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Control.TaskData = null;
|
||||
ClearAsyncData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Processing events.
|
||||
|
||||
// Processing Starting.
|
||||
public class ProcessStartingEventArgs : EventArgs { }
|
||||
public event EventHandler<ProcessStartingEventArgs> OnProcessStarting;
|
||||
protected virtual void InvokeProcessStarting()
|
||||
{
|
||||
if (OnProcessStarting != null)
|
||||
{
|
||||
ProcessStartingEventArgs args = new ProcessStartingEventArgs();
|
||||
OnProcessStarting(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Process Info Updated.
|
||||
public class ProgressInfoUpdatedEventArgs : EventArgs
|
||||
{
|
||||
public List<String> ProgressInfo { get; } = new List<string>();
|
||||
}
|
||||
public event EventHandler<ProgressInfoUpdatedEventArgs> OnProgressInfoUpdated;
|
||||
protected virtual void InvokeProgressInfoUpdated(List<String> progressInfo)
|
||||
{
|
||||
if (OnProgressInfoUpdated != null)
|
||||
{
|
||||
ProgressInfoUpdatedEventArgs args = new ProgressInfoUpdatedEventArgs();
|
||||
args.ProgressInfo.AddRange(progressInfo);
|
||||
OnProgressInfoUpdated(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Processing Completed.
|
||||
public class ProcessCompletedEventArgs : EventArgs { }
|
||||
public event EventHandler<ProcessCompletedEventArgs> OnProcessCompleted;
|
||||
protected virtual void InvokeProcessCompleted()
|
||||
{
|
||||
if (OnProcessCompleted != null)
|
||||
{
|
||||
ProcessCompletedEventArgs args = new ProcessCompletedEventArgs();
|
||||
OnProcessCompleted(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Process control.
|
||||
@@ -177,15 +427,15 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
{
|
||||
get
|
||||
{
|
||||
if (HasProcessError()) return false;
|
||||
if (isInterrupted) return false;
|
||||
if (HasErrorMessage()) return false;
|
||||
if (Control.Interrupted) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
private void Reset ()
|
||||
private void Reset()
|
||||
{
|
||||
ClearProcessError();
|
||||
isInterrupted = false;
|
||||
Control.ClearProcessMessages();
|
||||
Control.Interrupted = false;
|
||||
ProcessEnded = false;
|
||||
StartedAt = DateTime.MinValue;
|
||||
FinishedAt = DateTime.MinValue;
|
||||
@@ -193,36 +443,70 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Progress information.
|
||||
protected void SetProgressInfo (String progressInfo)
|
||||
protected void SetProgressInfo(String progressInfo)
|
||||
{
|
||||
SetProgressInfo(StringListUtil.CreateList(progressInfo));
|
||||
}
|
||||
protected void SetProgressInfo (List<String> progressInfo)
|
||||
protected void SetProgressInfo(List<String> progressInfo)
|
||||
{
|
||||
if (ProcessNotifier != null)
|
||||
ProcessNotifier.SetProgressInfo(progressInfo);
|
||||
InvokeProgressInfoUpdated(progressInfo);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Error handling.
|
||||
public bool HasProcessError () { return !StringUtil.IsEmpty(processError); }
|
||||
public String GetProcessError () { return StringUtil.EmptyOnNull(processError); }
|
||||
public void ClearProcessError () { processError = ""; }
|
||||
/// <summary>Record the processing error - always returns false.</summary>
|
||||
public bool SetProcessError (String error)
|
||||
// Process messages.
|
||||
/// <summary>Add a message of the specified level (Info, Warning, Error).</summary>
|
||||
protected void AddMessage(String message, SaMessageLevel level)
|
||||
{
|
||||
processError = StringUtil.EmptyOnNull(error);
|
||||
if (Control.AddProcessMessage(message, level))
|
||||
SaLogger.Log(GetType().Name + " " + message, ProcessControlData.GetLevel(level));
|
||||
}
|
||||
protected void AddInfoMessage(String message)
|
||||
{
|
||||
AddMessage(message, SaMessageLevel.Info);
|
||||
}
|
||||
protected void AddWarningMessage(String message)
|
||||
{
|
||||
AddMessage(message, SaMessageLevel.Warning);
|
||||
}
|
||||
/// <summary>Add the error message and always return false.</summary>
|
||||
protected bool AddErrorMessage(String message)
|
||||
{
|
||||
AddMessage(message, SaMessageLevel.Error);
|
||||
return false;
|
||||
}
|
||||
/// <summary>Are there any error messages.</summary>
|
||||
public bool HasErrorMessage()
|
||||
{
|
||||
return Control.HasProcessMessages(SaMessageLevel.Error);
|
||||
}
|
||||
/// <summary>Get the first error message.</summary>
|
||||
public String GetErrorMessage()
|
||||
{
|
||||
return Control.GetProcessMessage(SaMessageLevel.Error);
|
||||
}
|
||||
/// <summary>Return all process messages.</summary>
|
||||
public List<ProcessMessage> GetProcessMessages()
|
||||
{
|
||||
return Control.GetProcessMessages();
|
||||
}
|
||||
/// <summary>Return all process messages of the specified Level (Info, Warning, Error).</summary>
|
||||
public List<ProcessMessage> GetProcessMessages(SaMessageLevel level)
|
||||
{
|
||||
return Control.GetProcessMessages(level);
|
||||
}
|
||||
/// <summary>Clear process messages.</summary>
|
||||
public void ClearProcessMessages()
|
||||
{
|
||||
Control.ClearProcessMessages();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Interruption.
|
||||
private bool isInterrupted = false;
|
||||
public bool IsInterrupted
|
||||
{
|
||||
get { return isInterrupted; }
|
||||
get { return Control.Interrupted; }
|
||||
}
|
||||
public bool Interrupt ()
|
||||
public bool Interrupt()
|
||||
{
|
||||
if (!config.AllowInterruption)
|
||||
{
|
||||
@@ -230,10 +514,10 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
return false;
|
||||
}
|
||||
if (config.ConfirmInterruption && !IsInterruptionConfirmed()) return false;
|
||||
isInterrupted = true;
|
||||
Control.Interrupted = true;
|
||||
return true;
|
||||
}
|
||||
protected virtual bool IsInterruptionConfirmed ()
|
||||
protected virtual bool IsInterruptionConfirmed()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user