// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using System;
using System.Buffers.Binary;
using System.Buffers.Text;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace EpicGames.Perforce
{
///
/// Wraps a call to a p4.exe child process, and allows reading data from it
///
class PerforceChildProcess : IPerforceOutput, IDisposable
{
///
/// The process group
///
ManagedProcessGroup ChildProcessGroup;
///
/// The child process instance
///
ManagedProcess ChildProcess;
///
/// Scope object for tracing
///
ITraceSpan Scope;
///
/// The buffer data
///
byte[] Buffer;
///
/// End of the valid portion of the buffer (exclusive)
///
int BufferEnd;
///
public ReadOnlyMemory Data => Buffer.AsMemory(0, BufferEnd);
///
/// Temp file containing file arguments
///
string? TempFileName;
///
/// Constructor
///
///
/// Command line arguments
/// File arguments, which may be placed in a response file
/// Input data to pass to the child process
///
/// Logging device
public PerforceChildProcess(string Command, IReadOnlyList Arguments, IReadOnlyList? FileArguments, byte[]? InputData, IReadOnlyList GlobalOptions, ILogger Logger)
{
string PerforceFileName = GetExecutable();
List FullArguments = new List();
FullArguments.Add("-G");
FullArguments.AddRange(GlobalOptions);
if (FileArguments != null)
{
TempFileName = Path.GetTempFileName();
File.WriteAllLines(TempFileName, FileArguments);
FullArguments.Add($"-x{TempFileName}");
}
FullArguments.Add(Command);
FullArguments.AddRange(Arguments);
string FullArgumentList = CommandLineArguments.Join(FullArguments);
Logger.LogDebug("Running {Executable} {Arguments}", PerforceFileName, FullArgumentList);
Scope = TraceSpan.Create(Command, Service: "perforce");
Scope.AddMetadata("arguments", FullArgumentList);
ChildProcessGroup = new ManagedProcessGroup();
ChildProcess = new ManagedProcess(ChildProcessGroup, PerforceFileName, FullArgumentList, null, null, InputData, ProcessPriorityClass.Normal);
Buffer = new byte[64 * 1024];
}
///
/// Gets the path to the P4.EXE executable
///
/// Path to the executable
public static string GetExecutable()
{
string PerforceFileName;
if (RuntimePlatform.IsWindows)
{
PerforceFileName = "p4.exe";
}
else
{
PerforceFileName = File.Exists("/usr/local/bin/p4") ? "/usr/local/bin/p4" : "/usr/bin/p4";
}
return PerforceFileName;
}
///
public ValueTask DisposeAsync()
{
Dispose();
return new ValueTask(Task.CompletedTask);
}
///
public void Dispose()
{
if (ChildProcess != null)
{
ChildProcess.Dispose();
ChildProcess = null!;
}
if (ChildProcessGroup != null)
{
ChildProcessGroup.Dispose();
ChildProcessGroup = null!;
}
if (Scope != null)
{
Scope.Dispose();
Scope = null!;
}
if (TempFileName != null)
{
try { File.Delete(TempFileName); } catch { }
TempFileName = null;
}
}
///
public async Task ReadAsync(CancellationToken CancellationToken)
{
// Update the buffer contents
if (BufferEnd == Buffer.Length)
{
Array.Resize(ref Buffer, Math.Min(Buffer.Length + (32 * 1024 * 1024), Buffer.Length * 2));
}
// Try to read more data
int PrevBufferEnd = BufferEnd;
while (BufferEnd < Buffer.Length)
{
int Count = await ChildProcess!.ReadAsync(Buffer, BufferEnd, Buffer.Length - BufferEnd, CancellationToken);
if (Count == 0)
{
break;
}
BufferEnd += Count;
}
return BufferEnd > PrevBufferEnd;
}
///
public void Discard(int NumBytes)
{
if (NumBytes > 0)
{
Array.Copy(Buffer, NumBytes, Buffer, 0, BufferEnd - NumBytes);
BufferEnd -= NumBytes;
}
}
///
/// Reads all output from the child process as a string
///
/// Cancellation token to abort the read
/// Exit code and output from the process
public async Task> TryReadToEndAsync(CancellationToken CancellationToken)
{
using MemoryStream Stream = new MemoryStream();
while (await ReadAsync(CancellationToken))
{
ReadOnlyMemory DataCopy = Data;
Stream.Write(DataCopy.Span);
Discard(DataCopy.Length);
}
string String = Encoding.Default.GetString(Stream.ToArray());
return Tuple.Create(ChildProcess.ExitCode == 0, String);
}
}
}