Innovenergy_trunk/csharp/lib/SysTools/Process/SyncProcess.cs

284 lines
7.1 KiB
C#

using System.Diagnostics;
using System.Text.RegularExpressions;
using InnovEnergy.SysTools.Utils;
using static System.ConsoleColor;
namespace InnovEnergy.SysTools.Process;
using Env = Dictionary<String, String>;
public class SyncProcess
{
public SysCommand Command { get; }
public Env Environment { get; }
public Boolean LogToConsole { get; }
public Boolean HasFinished => Process.HasExited;
private Task<(DateTime timeStamp, StdOutput output)> _ReadStdOut;
private Task<(DateTime timeStamp, StdError error )> _ReadStdErr;
private System.Diagnostics.Process Process { get; }
public SyncProcess(SysCommand command, Env environment = null, Boolean logToConsole = false)
{
Command = command;
Environment = environment;
LogToConsole = logToConsole;
Process = InitProcess();
_ReadStdOut = ReadNextStdOut();
_ReadStdErr = ReadNextStdErr();
}
private Task<(DateTime timeStamp, StdOutput output)> ReadNextStdOut()
{
Debug.Assert(_ReadStdOut == null || _ReadStdOut.IsCompleted);
return Process
.StandardOutput
.ReadLineAsync()
.ContinueWith(t => (DateTime.Now, SyncProcessOutput.StdOutput(t.Result)));
}
private Task<(DateTime timeStamp, StdError error)> ReadNextStdErr()
{
Debug.Assert(_ReadStdErr == null || _ReadStdErr.IsCompleted);
return Process
.StandardError
.ReadLineAsync()
.ContinueWith(t => (DateTime.Now, SyncProcessOutput.StdError(t.Result)));
}
private System.Diagnostics.Process InitProcess()
{
var startInfo = new ProcessStartInfo
{
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true,
Arguments = Command.Args.Trim(),
FileName = Command.Path,
};
foreach (var ev in Environment.EmptyIfNull())
startInfo.EnvironmentVariables[ev.Key] = ev.Value;
var proc = new System.Diagnostics.Process
{
StartInfo = startInfo,
EnableRaisingEvents = true
};
proc.Start(); // must start BEFORE we access Streams
return proc;
}
public Match ReadStdOutUntil(Regex rx)
{
foreach (var l in ReadStdOut())
{
var match = rx.Match(l);
if (match.Success)
return match;
}
throw new Exception("no matching output");
}
public String ReadStdOutUntil(String line)
{
return ReadStdOutUntil(l => l == line);
}
public String ReadStdOutUntil(Func<String, Boolean> predicate)
{
foreach (var l in ReadStdOut())
{
if (predicate(l))
return l;
}
throw new Exception("no matching output");
}
public IEnumerable<String> ReadStdOut(TimeSpan timeOut = default) => ReadMany(timeOut).ThrownOnStdErr();
public IEnumerable<SyncProcessOutput> ReadMany(TimeSpan timeOut = default)
{
SyncProcessOutput output;
do
{
output = ReadNext(timeOut);
yield return output;
}
while (!(output is ExitCode) && !(output is TimeoutError));
}
public SyncProcessOutput ReadNext(TimeSpan timeOut = default)
{
var wait = timeOut == default
? Task.WaitAny(_ReadStdOut, _ReadStdErr)
: Task.WaitAny(_ReadStdOut, _ReadStdErr, Task.Delay(timeOut));
if (wait == 2)
{
if (LogToConsole)
"Timeout".WriteLine(Red);
return SyncProcessOutput.TimeoutError(timeOut);
}
if (_ReadStdOut.IsCompleted && _ReadStdErr.IsCompleted)
{
var (outTimeStamp, output) = _ReadStdOut.Result;
var (errTimeStamp, error) = _ReadStdErr.Result;
if (outTimeStamp < errTimeStamp && output != null)
{
_ReadStdOut = ReadNextStdOut();
if (LogToConsole)
output.Data.WriteLine();
return output;
}
if (error != null)
{
_ReadStdErr = ReadNextStdErr();
if (LogToConsole)
error.Data.WriteLine(Red);
return error;
}
Process.WaitForExit();
var exitCode = Process.ExitCode;
if (LogToConsole)
{
"ExitCode: ".Write(Cyan);
exitCode.WriteLine(exitCode == 0 ? Green : Red);
}
return SyncProcessOutput.ExitCode(exitCode);
}
if (_ReadStdOut.IsCompleted)
{
var (_, output) = _ReadStdOut.Result;
if (output != null)
{
_ReadStdOut = ReadNextStdOut();
return output;
}
}
if (_ReadStdErr.IsCompleted)
{
var (_, error) = _ReadStdErr.Result;
if (error != null)
{
_ReadStdErr = ReadNextStdErr();
return error;
}
}
throw new Exception("This should not happen");
}
public SyncProcess Terminate()
{
Process.Kill();
return this;
}
public SyncProcess Kill()
{
Process.Kill();
return this;
}
public void Write(String line)
{
Process.StandardInput.WriteLine(line);
}
public override String ToString() => Command.ToString(); // TODO: env
}
public abstract class SyncProcessOutput
{
public static StdOutput StdOutput (String data ) => new StdOutput(data);
public static StdError StdError (String data ) => new StdError(data);
public static ExitCode ExitCode (Int32 data ) => new ExitCode(data);
public static TimeoutError TimeoutError (TimeSpan timeout) => new TimeoutError(timeout);
}
public class ExitCode : SyncProcessOutput
{
public ExitCode(Int32 data) { Data = data;}
public Int32 Data { get; }
public override String ToString() => Data.ToString();
}
public class StdError : SyncProcessOutput
{
public StdError(String data) { Data = data;}
public String Data { get; }
public override String ToString() => Data;
}
public class StdOutput : SyncProcessOutput
{
public StdOutput(String data) { Data = data; }
public String Data { get; }
public override String ToString() => Data;
}
public class TimeoutError : SyncProcessOutput
{
public TimeoutError(TimeSpan timeout) { Timeout = timeout; }
public TimeSpan Timeout { get; }
public override String ToString() => Timeout.ToString();
}
public static class ProcessOutputExtensions
{
public static IEnumerable<String> ThrownOnStdErr(this IEnumerable<SyncProcessOutput> po)
{
foreach (var p in po)
{
if (p is StdOutput o)
yield return o.Data;
if (p is StdError e)
throw new Exception(e.Data);
// Ignore exit code
}
}
}