using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Components.Watchdog;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.Components.Compiler
{
///
sealed class DreamMaker : IDreamMaker
{
///
/// Name of the primary directory used for compilation
///
public const string ADirectoryName = "A";
///
/// Name of the secondary directory used for compilation
///
public const string BDirectoryName = "B";
///
/// Extension for .dmbs
///
public const string DmbExtension = ".dmb";
///
/// Extension for .dmes
///
const string DmeExtension = ".dme";
///
public CompilerStatus Status { get; private set; }
///
/// The for
///
readonly IByondManager byond;
///
/// The for
///
readonly IIOManager ioManager;
///
/// The for
///
readonly StaticFiles.IConfiguration configuration;
///
/// The for
///
readonly ISessionControllerFactory sessionControllerFactory;
///
/// The for
///
readonly ICompileJobConsumer compileJobConsumer;
///
/// The for
///
readonly IApplication application;
///
/// The for
///
readonly IEventConsumer eventConsumer;
///
/// The for
///
readonly ILogger logger;
///
/// Construct
///
/// The value of
/// The value of
/// The value of
/// The value of
/// The value of
/// The value of
/// The value of
/// The value of
public DreamMaker(IByondManager byond, IIOManager ioManager, StaticFiles.IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger logger)
{
this.byond = byond;
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
///
/// Run a quick DD instance to test the DMAPI is installed on the target code
///
/// The timeout in seconds for validation
/// The for the operation
/// The current
/// The for the operation
/// A resulting in if the DMAPI was successfully validated, otherwise
async Task VerifyApi(uint timeout, Models.CompileJob job, IByondExecutableLock byondLock, CancellationToken cancellationToken)
{
var launchParameters = new DreamDaemonLaunchParameters
{
AllowWebClient = false,
PrimaryPort = 0, //pick any port
SecurityLevel = DreamDaemonSecurity.Safe, //all it needs to read the file and exit
StartupTimeout = timeout
};
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
var provider = new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension))));
var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout);
using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false))
{
var timeoutTask = Task.Delay(timeoutAt - DateTimeOffset.Now, cancellationToken);
await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
if (!controller.Lifetime.IsCompleted)
return false;
return controller.ApiValidated;
}
}
///
/// Compiles a .dme with DreamMaker
///
/// The path to the DreamMaker executable
/// The for the operation
/// The for the operation
/// A representing the running operation
async Task RunDreamMaker(string dreamMakerPath, Models.CompileJob job, CancellationToken cancellationToken)
{
using (var dm = new Process())
{
dm.StartInfo.FileName = dreamMakerPath;
dm.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "-clean {0}{1}", job.DmeName, DmeExtension);
dm.StartInfo.WorkingDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName));
dm.StartInfo.RedirectStandardOutput = true;
dm.StartInfo.RedirectStandardError = true;
var OutputList = new StringBuilder();
var eventHandler = new DataReceivedEventHandler(
delegate (object sender, DataReceivedEventArgs e)
{
OutputList.Append(Environment.NewLine);
OutputList.Append(e.Data);
}
);
dm.OutputDataReceived += eventHandler;
dm.ErrorDataReceived += eventHandler;
dm.EnableRaisingEvents = true;
var dmTcs = new TaskCompletionSource