tbf/TBF/Rig/RegisterReaders/PoseidonReader/communication/C7/Utils/CliRunner.cs

343 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace TBF.Rig.RegisterReaders.PoseidonReader.communication.C7.Utils
{
public class CliRunner
{
//static readonly Logger log = LogManager.GetLogger(typeof(CliRunner));
//private readonly ILog log;
private List<Task> taskPool = new List<Task>();
private long startTime;
public List<Task> TaskPool { get { return taskPool; } }
public void AddTask(Task task) { taskPool.Add(task); }
public void WaitAll() { Task.WaitAll(taskPool.ToArray()); }
/// <summary>
/// continue with WhenAny() to commpare results if time out is reached
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
public Task AddTimeOut(int timeout)
{
Task timeOut = Task.Delay(timeout);
taskPool.Add(timeOut);
return timeOut;
}
/// <summary>
/// mainly used for time out additional task to compare results
/// </summary>
/// <returns></returns>
[Obsolete("Use async/await instead - WaitAnyFinished()")]
public async Task<Task> WhenAny() { return await Task.WhenAny(taskPool.ToArray()); }
public Task WaitAnyFinished(Task task, Task timeOut)
{
// Task.WhenAny(task, timeOut);
// while (true)
// {
// if (task.IsCompleted) break;
// if (task.IsFaulted) break;
// if (task.IsCanceled) break;
//
// if(timeOut.IsCompleted) break;
// if(timeOut.IsFaulted) break;
// if(timeOut.IsCanceled) break;
//
// Thread.Sleep(100);
// }
var waitAny = Task.WaitAny(task, timeOut);
return waitAny == 0 ? task : timeOut;
}
public static async Task<T> WithTimeout<T>(Task<T> task, TimeSpan timeout, CancellationTokenSource externalCts = null)
{
var localCts = externalCts == null ? externalCts : new CancellationTokenSource();
var timeoutCts = new CancellationTokenSource(timeout);
var linked = CancellationTokenSource.CreateLinkedTokenSource(localCts.Token, timeoutCts.Token);
var completion = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
// Observe the task
_ = task.ContinueWith(_ => completion.TrySetResult(true), TaskScheduler.Default);
// Observe the timeout
_ = Task.Delay(Timeout.InfiniteTimeSpan, timeoutCts.Token)
.ContinueWith(_ => completion.TrySetResult(true), TaskScheduler.Default);
await completion.Task.ConfigureAwait(false);
if (timeoutCts.IsCancellationRequested == false && task.IsCompleted) // task finished first
return await task.ConfigureAwait(false);
// timeout won → cancel & throw TimeoutException
localCts.Cancel(); // triggers your SendAsync to kill process
throw new TimeoutException();
}
public Task<string> AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg, CancellationToken ct = default)
{
var task = SendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettingsRead(eMeterArg), ct);
taskPool.Add(task); // List<Task> is okay because Task<string> : Task
return task;
}
public Task WaitAnyFinished()
{
int xTask = Task.WaitAny(taskPool.ToArray());
return taskPool[xTask];
}
public void Clear() { taskPool.Clear(); }
public void CancelAll() { Task.WhenAll(taskPool).ContinueWith(t => { }); }
public void StartAll()
{
taskPool.ForEach(t => t.Start());
}
public bool AreTasksDone()
{
return taskPool.All(task => task.IsCompleted);
}
public long StartTime
{
get => startTime;
}
public bool TimeOutReceived(long timeout)
{
return (DateTime.Now.Ticks - startTime) > timeout;
}
public CliRunner(bool isCliLogging)
{
startTime = DateTime.Now.Ticks;
if (isCliLogging)
{
try
{
/*log = new ScopedLoggerFactory().CreateLogger<CliRunner>(
@"C:\TBF\Logs\CliRunner.txt",
10, // maxFileSizeMB
7, // maxBackups
log4net.Core.Level.Debug,
true, // zipRolledFiles
true, // singleZipPerDay
TimeSpan.FromMinutes(2) // zipScanInterval
);*/
}
catch (Exception ex)
{
// Fallback to console or handle gracefully
Console.WriteLine($"Failed to initialize logger: {ex.Message}");
}
}
}
/// <summary>
///
/// </summary>
/// <param name="fileName"></param>
/// <param name="args"></param>
/// <typeparam name="T"></typeparam>
public Task AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg)
{
var task = SendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettingsRead(eMeterArg));
taskPool.Add(task);
var t = Task.Run(async () => { await task; }); // OK: Task
return t;
}
public Task AddSendAsync(SerialPortData data, SerialPortData.EMeterArg eMeterArg, string arg)
{
var task = SendAsync(data.SerialPortCmdClientPath, data.DefaultArgSettingsWrite(eMeterArg, arg));
taskPool.Add(task);
return task;
}
public void AddSendAsync(string fileName, string args)
{
var task = SendAsync(fileName, args);
taskPool.Add(task);
}
public async Task<string> SendAsync(string fileName, string args, CancellationToken ct = default)
{
var psi = new ProcessStartInfo
{
FileName = fileName,
Arguments = args,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
try
{
process.Start();
Task<string> stdOutTask = process.StandardOutput.ReadToEndAsync();
Task<string> stdErrTask = process.StandardError.ReadToEndAsync();
try
{
await Task.WhenAll(stdOutTask, stdErrTask, process.WaitForExitAsync(ct));
}
catch (OperationCanceledException)
{
if (!process.HasExited)
{
process.Kill();
}
throw;
}
string allOutput = (stdOutTask.Result ?? "") + (stdErrTask.Result ?? "");
//log?.Debug(allOutput);
return allOutput;
}
finally
{
process?.Dispose();
}
}
public Task<T> AddRunAndCaptureJsonAsync<T>(SerialPortData data, SerialPortData.EMeterArg eMeterArg) where T : new()
{
var task = RunAndCaptureJsonAsync<T>(data.SerialPortCmdClientPath, data.DefaultArgSettingsRead(eMeterArg));
taskPool.Add(task);
return task;
}
public async Task<T> RunAndCaptureJsonAsync<T>(string fileName, string args, CancellationToken ct = default) where T : new()
{
var psi = new ProcessStartInfo
{
FileName = fileName,
Arguments = args,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
try
{
process.Start();
var stdoutTask = process.StandardOutput.ReadToEndAsync();
var stderrTask = process.StandardError.ReadToEndAsync();
await Task.WhenAll(stdoutTask, stderrTask, process.WaitForExitAsync(ct));
string allOutput = (stdoutTask.Result ?? "") + (stderrTask.Result ?? "");
//log?.Debug(allOutput);
string json = ExtractJson(allOutput);
if (TryJsonStringDeserialize(json, out T result)) return result;
return default;
}
finally
{
process?.Dispose();
}
}
public bool TryJsonStringDeserialize<T>(string json, out T runAndCaptureJsonAsync) where T : new()
{
if (json != null)
{
try
{
runAndCaptureJsonAsync = JsonConvert.DeserializeObject<T>(json);
return true;
}
catch (Exception ex)
{
// log.Debug(ex.Message);
runAndCaptureJsonAsync = TryConvert<T>(json);
return true;
}
}
runAndCaptureJsonAsync = default;
return false;
}
private static T TryConvert<T>(string json) where T : new()
{
T obj = new T();
try
{
JObject jObject = JObject.Parse(json);
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!prop.CanWrite) continue;
JToken token;
if (jObject.TryGetValue(prop.Name, StringComparison.OrdinalIgnoreCase, out token))
{
try
{
object value = token.ToObject(prop.PropertyType);
prop.SetValue(obj, value);
}
catch
{
// leave default if conversion fails
}
}
// else → keep default value
}
}
catch (Exception ex)
{
Console.WriteLine($"TryConvert failed: {ex.Message}");
}
return obj;
}
public string ExtractJson(string text)
{
int start = text.IndexOf('{');
int end = text.LastIndexOf('}');
if (start >= 0 && end > start)
{
return text.Substring(start, end - start + 1);
}
return null;
}
}
}