using qdr.fnd.core.pqueue.Enums;
using qdr.fnd.core.pqueue.Exceptions;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.Logging;
using Quadarax.Foundation.Core.Object;
using Quadarax.Foundation.Core.Thread.Extensions;
using Quadarax.Foundation.Core.Value.Extensions;
namespace qdr.fnd.core.pqueue.Process
{
public delegate void ProcessDoneCallback(string itemId, PsStatusEnum newStatus, string message);
public delegate void ProcessStdOutputCallback(string itemId, string message);
public abstract class PsProcessProviderBase : DisposableObject, IPsProcessProvider
{
#region *** Constants ***
///
/// (Output) Argument name for the result code. Type is
///
public const string CS_ARG_RESULT_CODE = "result-code";
///
/// (Output) Argument name for the result test. Type is
///
public const string CS_ARG_RESULT_TEXT = "result-text";
///
/// (Input) Argument name for process timeout. Type is
///
public const string CS_ARG_PROCESS_TIMEOUT = "process-timeout";
#endregion
#region *** Properties ***
protected ILog Log { get; private set; }
protected ProcessDoneCallback ProcessDoneCallback { get; private set; }
protected ProcessStdOutputCallback ProcessStdOutputCallback { get; private set; }
public IDictionary Arguments { get; private set; }
public bool IsRunning { get; private set; }
public string ItemId { get; private set; }
#endregion
#region *** Private fields ***
private CancellationTokenSource? _cancelationToken;
#endregion
#region *** Constructors ***
public PsProcessProviderBase(ProcessDoneCallback processDoneCallback, ProcessStdOutputCallback processStdOutputCallback, string itemId, TypedArgument[] arguments, ILogger logger)
{
if (string.IsNullOrEmpty(itemId)) throw new ArgumentNullException(nameof(itemId));
ProcessDoneCallback = processDoneCallback ?? throw new ArgumentNullException(nameof(processDoneCallback));
ProcessStdOutputCallback = processStdOutputCallback ?? throw new ArgumentNullException(nameof(processStdOutputCallback));
ItemId = itemId;
Log = logger.GetLogger(GetType());
Arguments = arguments.ToDictionary(x => x.Name, x => x);
_cancelationToken = new CancellationTokenSource();
}
#endregion
#region *** Public Operations ***
///
public void Validate()
{
OnValidate();
}
///
public void Start()
{
if (IsRunning)
throw new PsProcessProviderException(PsProcessProviderException.ErrorCodes.ProcessProviderIsRunning, GetType().Name, ItemId);
Validate();
IsRunning = true;
var timeout = GetArgumentValue(CS_ARG_PROCESS_TIMEOUT);
Task.Factory.StartNew(()=>StartInternal(timeout), _cancelationToken!.Token);
}
///
public void Stop()
{
if (!IsRunning)
throw new PsProcessProviderException(PsProcessProviderException.ErrorCodes.ProcessProviderNotRunning, GetType().Name, ItemId);
_cancelationToken?.Cancel();
IsRunning = false;
}
///
/// Generates all arguments collection
///
/// Full scoped arguments collection
public static IEnumerable CreateArguments()
{
var list = new List
{
new TypedArgument(CS_ARG_RESULT_CODE, typeof(long), true),
new TypedArgument(CS_ARG_RESULT_TEXT, typeof(string), true),
new TypedArgument(CS_ARG_PROCESS_TIMEOUT, typeof(TimeSpan), false)
};
return list;
}
#endregion
#region *** Protected Operations ***
protected TValue? GetArgumentValue(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
if (Arguments.TryGetValue(name, out var arg))
return arg.GetTypedValue();
return default;
}
protected bool ContainsArgument(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
return Arguments.ContainsKey(name);
}
protected virtual void OnValidate()
{
if (!ContainsArgument(CS_ARG_RESULT_CODE) || !Arguments[CS_ARG_RESULT_CODE].IsOutput)
throw new PsProcessProviderException(PsProcessProviderException.ErrorCodes.ProcessProviderNotDefinedArgument, GetType().Name, ItemId, CS_ARG_RESULT_CODE);
if (!ContainsArgument(CS_ARG_RESULT_TEXT) || !Arguments[CS_ARG_RESULT_TEXT].IsOutput)
throw new PsProcessProviderException(PsProcessProviderException.ErrorCodes.ProcessProviderNotDefinedArgument, GetType().Name, ItemId, CS_ARG_RESULT_TEXT);
if (!ContainsArgument(CS_ARG_PROCESS_TIMEOUT) || Arguments[CS_ARG_PROCESS_TIMEOUT].IsOutput)
throw new PsProcessProviderException(PsProcessProviderException.ErrorCodes.ProcessProviderNotDefinedArgument, GetType().Name, ItemId, CS_ARG_PROCESS_TIMEOUT);
}
protected virtual async Task OnProcessError(Exception exception)
{
if (exception == null)
throw new ArgumentNullException(nameof(exception));
if (ProcessStdOutputCallback != null)
ProcessStdOutputCallback(ItemId, exception.Message);
if (ContainsArgument(CS_ARG_RESULT_TEXT))
Arguments[CS_ARG_RESULT_TEXT].Value = exception.Message;
}
protected virtual async Task OnProcess()
{
//nothing to do
}
///
protected override void OnDisposing()
{
ProcessDoneCallback = null!;
ProcessStdOutputCallback = null!;
}
private async Task StartInternal(TimeSpan timeout)
{
var dtStart = DateTime.Now;
try
{
await OnProcess().TimeoutAfter(timeout).ContinueWith(task =>
{
Log.Log(LogSeverityEnum.Info, $"Provider for ItemId='{ItemId}' done. Duration {(DateTime.Now - dtStart).ToReadableString()}.");
if (ProcessDoneCallback != null)
ProcessDoneCallback(ItemId, PsStatusEnum.DoneOk, "Succesfuly done.");
IsRunning = false;
});
}
catch (Exception e)
{
await OnProcessError(e).ContinueWith(task =>
{
Log.Log(LogSeverityEnum.Error, $"Provider running failed. Duration {(DateTime.Now - dtStart).ToReadableString()}.", e);
if (ProcessDoneCallback != null)
ProcessDoneCallback(ItemId, PsStatusEnum.DoneFailed, e.Message);
IsRunning = false;
});
}
}
#endregion
}
}