using qdr.fnd.core.pqueue.Enums;
using qdr.fnd.core.pqueue.Exceptions;
using qdr.fnd.core.pqueue.fnd.candidates;
using Quadarax.Foundation.Core.Data;
using Quadarax.Foundation.Core.Logging;
using Quadarax.Foundation.Core.Object;
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; }
///
public DateTime? StartedTimestamp { get; private set; }
///
public DateTime? FinishedTimestamp { 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 async void Start()
{
if (IsRunning)
throw new PsProcessProviderException(PsProcessProviderException.ErrorCodes.ProcessProviderIsRunning, GetType().Name, ItemId);
Validate();
Log.Log(LogSeverityEnum.Info, $"Provider for ItemId='{ItemId}' starting.");
IsRunning = true;
StartedTimestamp = DateTime.Now;
var timeout = GetArgumentValue(CS_ARG_PROCESS_TIMEOUT);
try
{
await TimeoutUtils.ExecuteWithTimeoutAsync(async () =>
{
await OnProcess().ContinueWith(task =>
{
ProcessDoneOk("Succesfuly done.");
});
}, timeout);
}
catch (Exception e)
{
await OnProcessError(e).ContinueWith(task =>
{
ProcessDoneFailed(e.Message, e);
});
}
}
///
public void Stop()
{
if (!IsRunning)
throw new PsProcessProviderException(PsProcessProviderException.ErrorCodes.ProcessProviderNotRunning, GetType().Name, ItemId);
_cancelationToken?.Cancel();
ProcessDoneFailed("Process was forced stopped.");
Log.Log(LogSeverityEnum.Info, $"Provider for ItemId='{ItemId}' was forced stopped.");
}
///
/// 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;
}
///
public void WaitToDone()
{
while (IsRunning)
{
Task.Delay(100).Wait();
};
Log.Log(LogSeverityEnum.Debug, $"Provider for ItemId='{ItemId}' detect that process finished.");
}
#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);
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
protected virtual async Task OnProcessError(Exception exception)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
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;
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
protected virtual async Task OnProcess()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
//nothing to do
}
///
protected override void OnDisposing()
{
_cancelationToken?.Dispose();
ProcessDoneCallback = null!;
ProcessStdOutputCallback = null!;
_cancelationToken = null;
}
private void ProcessDoneFailed(string message, Exception? e = null)
{
Log.Log(LogSeverityEnum.Error, $"Provider running failed. Duration {(DateTime.Now - StartedTimestamp.GetValueOrDefault()).ToReadableString()}.", e);
if (ProcessDoneCallback != null)
ProcessDoneCallback(ItemId, PsStatusEnum.DoneFailed, message);
IsRunning = false;
FinishedTimestamp = DateTime.Now;
}
private void ProcessDoneOk(string message)
{
Log.Log(LogSeverityEnum.Info, $"Provider for ItemId='{ItemId}' done. Duration {(DateTime.Now - StartedTimestamp.GetValueOrDefault()).ToReadableString()}.");
if (ProcessDoneCallback != null)
ProcessDoneCallback(ItemId, PsStatusEnum.DoneOk, message);
IsRunning = false;
FinishedTimestamp = DateTime.Now;
}
#endregion
}
}