More Android
This commit is contained in:
Родитель
8ec538e76c
Коммит
72a3039011
|
@ -60,17 +60,22 @@ namespace DotNetDevices.Android
|
|||
{
|
||||
logger?.LogInformation("Retrieving all the virtual devices...");
|
||||
|
||||
return await GetVirtualDeviceNoLogging(cancellationToken).ConfigureAwait(false);
|
||||
var args = $"list avd -c";
|
||||
|
||||
var result = await processRunner.RunAsync(avdmanager, args, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var avd = new List<VirtualDevice>(result.OutputCount);
|
||||
foreach (var output in GetListResults(result))
|
||||
{
|
||||
avd.Add(new VirtualDevice(output));
|
||||
}
|
||||
return avd;
|
||||
}
|
||||
|
||||
public async Task DeleteVirtualDeviceAsync(string name, CancellationToken cancellationToken = default)
|
||||
{
|
||||
logger?.LogInformation($"Deleting virtual device '{name}'...");
|
||||
|
||||
var avds = await GetVirtualDeviceNoLogging(cancellationToken);
|
||||
if (!avds.Any(x => x.Name.ToLowerInvariant() == name.ToLowerInvariant()))
|
||||
throw new Exception($"Unable to find virtual device '{name}'.");
|
||||
|
||||
var args = $"delete avd --name \"{name}\"";
|
||||
|
||||
await processRunner.RunAsync(avdmanager, args, null, cancellationToken).ConfigureAwait(false);
|
||||
|
@ -80,13 +85,6 @@ namespace DotNetDevices.Android
|
|||
{
|
||||
logger?.LogInformation($"Creating virtual device '{name}'...");
|
||||
|
||||
if (options?.Overwrite != true)
|
||||
{
|
||||
var avds = await GetVirtualDeviceNoLogging(cancellationToken);
|
||||
if (avds.Any(x => x.Name.ToLowerInvariant() == name.ToLowerInvariant()))
|
||||
throw new Exception($"Virtual device '{name}' already exists.");
|
||||
}
|
||||
|
||||
var args = $"create avd --name \"{name}\" --package \"{package}\"";
|
||||
if (options?.Overwrite == true)
|
||||
args += " --force";
|
||||
|
@ -94,6 +92,24 @@ namespace DotNetDevices.Android
|
|||
await processRunner.RunWithInputAsync("no", avdmanager, args, null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task RenameVirtualDeviceAsync(string name, string newName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
logger?.LogInformation($"Renaming virtual device '{name}'...");
|
||||
|
||||
var args = $"move avd --name \"{name}\" --rename \"{newName}\"";
|
||||
|
||||
await processRunner.RunAsync(avdmanager, args, null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task MoveVirtualDeviceAsync(string name, string newPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
logger?.LogInformation($"Moving virtual device '{name}'...");
|
||||
|
||||
var args = $"move avd --name \"{name}\" --path \"{newPath}\"";
|
||||
|
||||
await processRunner.RunAsync(avdmanager, args, null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetListResults(ProcessResult result)
|
||||
{
|
||||
foreach (var output in result.GetOutput())
|
||||
|
@ -118,22 +134,6 @@ namespace DotNetDevices.Android
|
|||
yield return o;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<VirtualDevice>> GetVirtualDeviceNoLogging(CancellationToken cancellationToken = default)
|
||||
{
|
||||
logger?.LogDebug("Retrieving all the virtual devices...");
|
||||
|
||||
var args = $"list avd -c";
|
||||
|
||||
var result = await processRunner.RunAsync(avdmanager, args, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var avd = new List<VirtualDevice>(result.OutputCount);
|
||||
foreach (var output in GetListResults(result))
|
||||
{
|
||||
avd.Add(new VirtualDevice(output));
|
||||
}
|
||||
return avd;
|
||||
}
|
||||
}
|
||||
|
||||
public class VirtualDeviceCreateOptions
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
namespace DotNetDevices.Android
|
||||
{
|
||||
public class BootVirtualDeviceOptions
|
||||
{
|
||||
public bool NoWindow { get; set; }
|
||||
|
||||
public bool NoSnapshots { get; set; }
|
||||
|
||||
public bool WipeData { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetDevices.Processes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace DotNetDevices.Android
|
||||
{
|
||||
public class EmulatorManager
|
||||
{
|
||||
private static readonly Regex consoleListeningRegex = new Regex(@"emulator: control console listening on port (\d+), ADB on port (\d+)");
|
||||
private static readonly Regex adbConnectedRegex = new Regex(@"emulator: onGuestSendCommand: \[(.+)\] Adb connected, start proxing data");
|
||||
private static readonly Regex alreadyBootedRegex = new Regex(@"emulator: ERROR: Running multiple emulators with the same AVD is an experimental feature\.");
|
||||
|
||||
private readonly ProcessRunner processRunner;
|
||||
private readonly ILogger? logger;
|
||||
private readonly string emulator;
|
||||
|
||||
public EmulatorManager(string? sdkRoot = null, ILogger? logger = null)
|
||||
{
|
||||
this.logger = logger;
|
||||
processRunner = new ProcessRunner(logger);
|
||||
emulator = AndroidSDK.FindPath(sdkRoot, Path.Combine("emulator", "emulator"), logger)
|
||||
?? throw new ArgumentException($"Unable to locate the Android Emulator. Make sure that ANDROID_HOME or ANDROID_SDK_ROOT is set.");
|
||||
}
|
||||
|
||||
public async Task<int> BootVirtualDeviceAsync(string name, BootVirtualDeviceOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
logger?.LogInformation($"Booting virtual device '{name}'...");
|
||||
|
||||
var args = $"-avd {name} -verbose";
|
||||
if (options?.NoWindow == true)
|
||||
args += " -no-boot-anim -no-window";
|
||||
if (options?.NoSnapshots == true)
|
||||
args += " -no-snapshot";
|
||||
if (options?.WipeData == true)
|
||||
args += " -wipe-data";
|
||||
|
||||
var port = -1;
|
||||
try
|
||||
{
|
||||
await processRunner.RunAsync(emulator, args, FindComplete, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (ProcessResultException ex) when (IsAlreadyLaunched(ex))
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
return port;
|
||||
|
||||
bool FindComplete(ProcessOutput output)
|
||||
{
|
||||
if (!output.IsError && output.Data is string o)
|
||||
{
|
||||
if (port <= 0)
|
||||
{
|
||||
// first find port
|
||||
var match = consoleListeningRegex.Match(o);
|
||||
if (match.Success)
|
||||
port = int.Parse(match.Groups[1].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// then wait for the boot finished
|
||||
var match = adbConnectedRegex.Match(o);
|
||||
if (match.Success)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsAlreadyLaunched(ProcessResultException ex)
|
||||
{
|
||||
foreach (var output in ex.ProcessResult.GetOutput())
|
||||
{
|
||||
var match = alreadyBootedRegex.Match(output);
|
||||
if (match.Success)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<VirtualDevice>> GetVirtualDevicesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
logger?.LogInformation("Retrieving all the virtual devices...");
|
||||
|
||||
var args = $"-list-avds";
|
||||
|
||||
var result = await processRunner.RunAsync(emulator, args, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var avd = new List<VirtualDevice>(result.OutputCount);
|
||||
foreach (var output in result.GetOutput())
|
||||
{
|
||||
avd.Add(new VirtualDevice(output));
|
||||
}
|
||||
return avd;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using DotNetDevices.Processes;
|
|||
|
||||
namespace DotNetDevices.Apple
|
||||
{
|
||||
public class SimulatorLaunchOptions
|
||||
public class LaunchAppOptions
|
||||
{
|
||||
public bool CaptureOutput { get; set; } = false;
|
||||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace DotNetDevices.Apple
|
||||
{
|
||||
public class LaunchedSimulator
|
||||
public class LaunchAppResult
|
||||
{
|
||||
private ProcessResult result;
|
||||
|
||||
public LaunchedSimulator(ProcessResult result)
|
||||
public LaunchAppResult(ProcessResult result)
|
||||
{
|
||||
this.result = result;
|
||||
}
|
|
@ -223,7 +223,7 @@ namespace DotNetDevices.Apple
|
|||
await processRunner.RunAsync(xcrun, args, null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<LaunchedSimulator> LaunchAppAsync(string udid, string appBundleId, SimulatorLaunchOptions? options = null, CancellationToken cancellationToken = default)
|
||||
public async Task<LaunchAppResult> LaunchAppAsync(string udid, string appBundleId, LaunchAppOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (udid == null)
|
||||
throw new ArgumentNullException(nameof(udid));
|
||||
|
@ -247,13 +247,25 @@ namespace DotNetDevices.Apple
|
|||
|
||||
try
|
||||
{
|
||||
var result = await processRunner.RunAsync(xcrun, args, options?.HandleOutput, cancellationToken).ConfigureAwait(false);
|
||||
return new LaunchedSimulator(result);
|
||||
var result = await processRunner.RunAsync(xcrun, args, Wrap(options?.HandleOutput), cancellationToken).ConfigureAwait(false);
|
||||
return new LaunchAppResult(result);
|
||||
}
|
||||
catch (ProcessResultException ex) when (ex.InnerException is OperationCanceledException && ex.ProcessResult != null)
|
||||
{
|
||||
await TerminateAppAsync(udid, appBundleId, cancellationToken);
|
||||
return new LaunchedSimulator(ex.ProcessResult);
|
||||
return new LaunchAppResult(ex.ProcessResult);
|
||||
}
|
||||
|
||||
static Func<ProcessOutput, bool>? Wrap(Action<ProcessOutput>? handle)
|
||||
{
|
||||
if (handle == null)
|
||||
return null;
|
||||
|
||||
return o =>
|
||||
{
|
||||
handle(o);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ namespace DotNetDevices.Commands
|
|||
{
|
||||
public static Command Create()
|
||||
{
|
||||
return new Command("android", "Work with Android emulators.")
|
||||
return new Command("android", "Work with Android virtual devices.")
|
||||
{
|
||||
new Command("list", "List the emulators.")
|
||||
new Command("list", "List the virtual devices.")
|
||||
{
|
||||
new Option<string?>(new[] { "--sdk" }, "Whether or not to only include the available simulators."),
|
||||
new Option(new[] { "--available" }, "Whether or not to only include the available simulators."),
|
||||
|
@ -33,27 +33,21 @@ namespace DotNetDevices.Commands
|
|||
new Argument<string?>("TERM", "The search term to use when filtering simulators. This could be any number of properties (UDID, runtime, version, availability, or state) as well as part of the simulator name.")
|
||||
{ Arity = ArgumentArity.ZeroOrOne },
|
||||
}.WithHandler(CommandHandler.Create(typeof(AndroidCommand).GetMethod(nameof(HandleListAsync))!)),
|
||||
new Command("create", "Create a new virtual device.")
|
||||
{
|
||||
new Option<string?>(new[] { "--sdk" }, "The path to the Android SDK directory."),
|
||||
new Option(new[] { "--replace" }, "Replace any existing virtual devices with the same name."),
|
||||
CommandLine.CreateVerbosity(),
|
||||
new Argument<string?>("NAME", "The name of the new virtual device."),
|
||||
new Argument<string?>("PACKAGE", "The package to use for the new virtual device."),
|
||||
}.WithHandler(CommandHandler.Create(typeof(AndroidCommand).GetMethod(nameof(HandleCreateAsync))!)),
|
||||
new Command("boot", "Boot a particular simulator.")
|
||||
{
|
||||
new Option<string?>(new[] { "--sdk" }, "Whether or not to only include the available simulators."),
|
||||
CommandLine.CreateVerbosity(),
|
||||
new Argument<string?>("UDID", ParseUdid)
|
||||
{
|
||||
Description = "The UDID of the simulator to boot.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
},
|
||||
new Argument<string?>("NAME", "The UDID of the simulator to boot."),
|
||||
}.WithHandler(CommandHandler.Create(typeof(AndroidCommand).GetMethod(nameof(HandleBootAsync))!)),
|
||||
};
|
||||
|
||||
static string? ParseUdid(ArgumentResult result)
|
||||
{
|
||||
var udid = result.Tokens[0].Value;
|
||||
|
||||
if (Guid.TryParse(udid, out _))
|
||||
return udid;
|
||||
|
||||
result.ErrorMessage = "The UDID must be a valid UDID.";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task HandleListAsync(
|
||||
|
@ -94,11 +88,20 @@ namespace DotNetDevices.Commands
|
|||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
await avdmanager.DeleteVirtualDeviceAsync("TESTED");
|
||||
}
|
||||
catch { }
|
||||
|
||||
await avdmanager.CreateVirtualDeviceAsync("TESTING", "system-images;android-28;google_apis_playstore;x86_64");
|
||||
|
||||
await avdmanager.CreateVirtualDeviceAsync("TESTING", "system-images;android-28;google_apis_playstore;x86_64", new VirtualDeviceCreateOptions { Overwrite = true });
|
||||
|
||||
await avdmanager.DeleteVirtualDeviceAsync("TESTING");
|
||||
await avdmanager.RenameVirtualDeviceAsync("TESTING", "TESTED");
|
||||
await avdmanager.MoveVirtualDeviceAsync("TESTED", "/Users/matthew/.android/avd/tested.avd");
|
||||
|
||||
//await avdmanager.DeleteVirtualDeviceAsync("TESTING");
|
||||
|
||||
//term = term?.ToLowerInvariant()?.Trim();
|
||||
|
||||
|
@ -155,27 +158,55 @@ namespace DotNetDevices.Commands
|
|||
//console.Append(new StackLayoutView { table });
|
||||
}
|
||||
|
||||
public static async Task<int> HandleBootAsync(
|
||||
string udid,
|
||||
public static async Task HandleCreateAsync(
|
||||
string name,
|
||||
string package,
|
||||
bool replace = false,
|
||||
string? sdk = null,
|
||||
string? verbosity = null,
|
||||
IConsole console = null!,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var logger = console.CreateLogger(verbosity);
|
||||
|
||||
var simctl = new SimulatorControl(logger);
|
||||
var simulator = await simctl.GetSimulatorAsync(udid, cancellationToken);
|
||||
var avdmanager = new AVDManager(sdk, logger);
|
||||
|
||||
var options = new VirtualDeviceCreateOptions
|
||||
{
|
||||
Overwrite = replace
|
||||
};
|
||||
|
||||
await avdmanager.CreateVirtualDeviceAsync(name, package, options, cancellationToken);
|
||||
}
|
||||
|
||||
if (simulator == null)
|
||||
public static async Task<int> HandleBootAsync(
|
||||
string name,
|
||||
string? sdk = null,
|
||||
string? verbosity = null,
|
||||
IConsole console = null!,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var logger = console.CreateLogger(verbosity);
|
||||
|
||||
var emulator = new EmulatorManager(sdk, logger);
|
||||
|
||||
var avds = await emulator.GetVirtualDevicesAsync(cancellationToken);
|
||||
if (avds.All(a => !a.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
logger.LogError($"No simulator with UDID {udid} was found.");
|
||||
logger.LogError($"No virtual device with name {name} was found.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (simulator.State == SimulatorState.Booted)
|
||||
logger.LogInformation($"Simulator was already booted.");
|
||||
var options = new BootVirtualDeviceOptions
|
||||
{
|
||||
NoSnapshots = false,
|
||||
WipeData = true,
|
||||
};
|
||||
var port = await emulator.BootVirtualDeviceAsync(name, options, cancellationToken);
|
||||
if (port == -1)
|
||||
logger.LogInformation($"Virtual device was already booted.");
|
||||
else
|
||||
await simctl.BootSimulatorAsync(udid, cancellationToken);
|
||||
logger.LogInformation($"device was booted to port {port}.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace DotNetDevices.Commands
|
|||
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
var launched = await simctl.LaunchAppAsync(simulator.Udid, bundleId, new SimulatorLaunchOptions
|
||||
var launched = await simctl.LaunchAppAsync(simulator.Udid, bundleId, new LaunchAppOptions
|
||||
{
|
||||
CaptureOutput = true,
|
||||
BootSimulator = true,
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace DotNetDevices.Processes
|
|||
private const int ThreadStillRunningExitCode = 259;
|
||||
private const int ThreadStillRunningRetry = 3;
|
||||
|
||||
public static async Task<ProcessResult> RunAsync(this ProcessStartInfo processStartInfo, string? input = null, Action<ProcessOutput>? handleOutput = null, CancellationToken cancellationToken = default)
|
||||
public static async Task<ProcessResult> RunAsync(this ProcessStartInfo processStartInfo, string? input = null, Func<ProcessOutput, bool>? handleOutput = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// override some info in order to capture the output
|
||||
processStartInfo.UseShellExecute = false;
|
||||
|
@ -73,7 +73,7 @@ namespace DotNetDevices.Processes
|
|||
// if the process is still exiting, give it a little more time
|
||||
for (var retries = 0; retries < ThreadStillRunningRetry && process.ExitCode == ThreadStillRunningExitCode; retries++)
|
||||
{
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// if it takes too long, just pretend it exited completely
|
||||
|
@ -81,6 +81,109 @@ namespace DotNetDevices.Processes
|
|||
if (exitCode == ThreadStillRunningExitCode)
|
||||
exitCode = 0;
|
||||
|
||||
await FinalizeTask(exitCode).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
async void HandleOutputData(object? sender, DataReceivedEventArgs? e)
|
||||
{
|
||||
if (e?.Data == null)
|
||||
{
|
||||
outputTcs.TrySetResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var o = new ProcessOutput(e.Data, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
if (handleOutput != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!handleOutput.Invoke(o))
|
||||
await Detatch();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
outputTcs.TrySetCanceled();
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
outputTcs.TrySetException(ex);
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
output.Enqueue(o);
|
||||
}
|
||||
|
||||
async void HandleErrorData(object? sender, DataReceivedEventArgs? e)
|
||||
{
|
||||
if (e?.Data == null)
|
||||
{
|
||||
errorsTcs.TrySetResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var o = new ProcessOutput(e.Data, stopwatch.ElapsedMilliseconds, true);
|
||||
|
||||
if (handleOutput != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!handleOutput.Invoke(o))
|
||||
await Detatch();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
errorsTcs.TrySetCanceled();
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorsTcs.TrySetException(ex);
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
output.Enqueue(o);
|
||||
}
|
||||
|
||||
void Terminate(bool cancel = false)
|
||||
{
|
||||
if (cancel)
|
||||
tcs?.TrySetCanceled();
|
||||
|
||||
if (process != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!process.HasExited)
|
||||
process.Kill();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task Detatch()
|
||||
{
|
||||
process.Exited -= HandleExited;
|
||||
process.OutputDataReceived -= HandleOutputData;
|
||||
process.ErrorDataReceived -= HandleErrorData;
|
||||
|
||||
HandleErrorData(null, null);
|
||||
HandleOutputData(null, null);
|
||||
|
||||
return FinalizeTask(0);
|
||||
}
|
||||
|
||||
async Task FinalizeTask(int exitCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
startTime = process.StartTime;
|
||||
|
@ -102,93 +205,9 @@ namespace DotNetDevices.Processes
|
|||
{
|
||||
var result = new ProcessResult(output.ToArray(), exitCode, startTime, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
tcs.TrySetException(new ProcessResultException($"The process threw an exception: {ex.Message}", ex, result));
|
||||
tcs.TrySetException(new ProcessResultException(result, $"The process threw an exception: {ex.Message}", ex));
|
||||
}
|
||||
}
|
||||
|
||||
void Terminate(bool cancel = false)
|
||||
{
|
||||
if (cancel)
|
||||
tcs?.TrySetCanceled();
|
||||
|
||||
if (process != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!process.HasExited)
|
||||
process.Kill();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleOutputData(object? sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
outputTcs.TrySetResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var o = new ProcessOutput(e.Data, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
if (handleOutput != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handleOutput.Invoke(o);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
outputTcs.TrySetCanceled();
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
outputTcs.TrySetException(ex);
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
output.Enqueue(o);
|
||||
}
|
||||
|
||||
void HandleErrorData(object? sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
errorsTcs.TrySetResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var o = new ProcessOutput(e.Data, stopwatch.ElapsedMilliseconds, true);
|
||||
|
||||
if (handleOutput != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handleOutput.Invoke(o);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
errorsTcs.TrySetCanceled();
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorsTcs.TrySetException(ex);
|
||||
Terminate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
output.Enqueue(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,23 +4,23 @@ namespace DotNetDevices.Processes
|
|||
{
|
||||
public class ProcessResultException : Exception
|
||||
{
|
||||
public ProcessResultException(ProcessResult? processResult = null)
|
||||
public ProcessResultException(ProcessResult processResult)
|
||||
{
|
||||
ProcessResult = processResult;
|
||||
}
|
||||
|
||||
public ProcessResultException(string? message, ProcessResult? processResult = null)
|
||||
public ProcessResultException(ProcessResult processResult, string? message)
|
||||
: base(message)
|
||||
{
|
||||
ProcessResult = processResult;
|
||||
}
|
||||
|
||||
public ProcessResultException(string? message, Exception? innerException, ProcessResult? processResult = null)
|
||||
public ProcessResultException(ProcessResult processResult, string? message, Exception? innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
ProcessResult = processResult;
|
||||
}
|
||||
|
||||
public ProcessResult? ProcessResult { get; }
|
||||
public ProcessResult ProcessResult { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,30 +17,30 @@ namespace DotNetDevices.Processes
|
|||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ProcessResult> RunAsync(string path, string? arguments = null, Action<ProcessOutput>? handleOutput = null, CancellationToken cancellationToken = default)
|
||||
public async Task<ProcessResult> RunAsync(string path, string? arguments = null, Func<ProcessOutput, bool>? handleOutput = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var output = await RunProcessAsync(FindCommand(path), arguments, null, handleOutput, cancellationToken);
|
||||
var result = await RunProcessAsync(FindCommand(path), arguments, null, handleOutput, cancellationToken);
|
||||
|
||||
if (output.ExitCode != 0)
|
||||
throw new Exception($"Failed to execute: {path} {arguments} - exit code: {output.ExitCode}{Environment.NewLine}{output.Output}");
|
||||
if (result.ExitCode != 0)
|
||||
throw new ProcessResultException(result, $"Failed to execute: {path} {arguments} - exit code: {result.ExitCode}{Environment.NewLine}{result.Output}");
|
||||
|
||||
logger?.LogDebug(output.ToString());
|
||||
logger?.LogTrace(output.Output);
|
||||
logger?.LogDebug(result.ToString());
|
||||
logger?.LogTrace(result.Output);
|
||||
|
||||
return output;
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ProcessResult> RunWithInputAsync(string input, string path, string? arguments = null, Action<ProcessOutput>? handleOutput = null, CancellationToken cancellationToken = default)
|
||||
public async Task<ProcessResult> RunWithInputAsync(string input, string path, string? arguments = null, Func<ProcessOutput, bool>? handleOutput = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var output = await RunProcessAsync(FindCommand(path), arguments, input, handleOutput, cancellationToken);
|
||||
var result = await RunProcessAsync(FindCommand(path), arguments, input, handleOutput, cancellationToken);
|
||||
|
||||
if (output.ExitCode != 0)
|
||||
throw new Exception($"Failed to execute: {path} {arguments} - exit code: {output.ExitCode}{Environment.NewLine}{output.Output}");
|
||||
if (result.ExitCode != 0)
|
||||
throw new ProcessResultException(result, $"Failed to execute: {path} {arguments} - exit code: {result.ExitCode}{Environment.NewLine}{result.Output}");
|
||||
|
||||
logger?.LogDebug(output.ToString());
|
||||
logger?.LogTrace(output.Output);
|
||||
logger?.LogDebug(result.ToString());
|
||||
logger?.LogTrace(result.Output);
|
||||
|
||||
return output;
|
||||
return result;
|
||||
}
|
||||
|
||||
private string FindCommand(string path, bool allowOSFallback = true)
|
||||
|
@ -61,7 +61,7 @@ namespace DotNetDevices.Processes
|
|||
return path;
|
||||
}
|
||||
|
||||
private Task<ProcessResult> RunProcessAsync(string path, string? arguments = null, string? input = null, Action<ProcessOutput>? handleOutput = null, CancellationToken cancellationToken = default)
|
||||
private Task<ProcessResult> RunProcessAsync(string path, string? arguments = null, string? input = null, Func<ProcessOutput, bool>? handleOutput = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче