mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 22:48:20 +01:00
DmbFactory and Provider
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard <see cref="IDmbFactory"/>
|
||||
/// </summary>
|
||||
sealed class DmbFactory : IDmbFactory, ICompileJobConsumer, IHostedService, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContext"/> for the <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContext databaseContext;
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
/// <summary>
|
||||
/// The <see cref="CancellationTokenSource"/> for <see cref="cleanupTask"/>
|
||||
/// </summary>
|
||||
readonly CancellationTokenSource cleanupCts;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Task"/> representing calls to <see cref="CleanJob(CompileJob)"/>
|
||||
/// </summary>
|
||||
Task cleanupTask;
|
||||
/// <summary>
|
||||
/// <see cref="TaskCompletionSource{TResult}"/> resulting in the latest <see cref="DmbProvider"/> yet to exist
|
||||
/// </summary>
|
||||
TaskCompletionSource<DmbProvider> newerDmbTcs;
|
||||
/// <summary>
|
||||
/// The latest <see cref="DmbProvider"/>
|
||||
/// </summary>
|
||||
DmbProvider nextDmbProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The value of <see cref="databaseContext"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
public DmbFactory(IDatabaseContext databaseContext, IIOManager ioManager)
|
||||
{
|
||||
this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
|
||||
cleanupCts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => cleanupCts.Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Delete the <see cref="Api.Models.Internal.CompileJob.DirectoryName"/> of <paramref name="job"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="CompileJob"/> to clean</param>
|
||||
void CleanJob(CompileJob job)
|
||||
{
|
||||
async Task HandleCleanup()
|
||||
{
|
||||
var deleteJob = ioManager.DeleteDirectory(job.DirectoryName.ToString(), cleanupCts.Token);
|
||||
Task otherTask;
|
||||
lock (this)
|
||||
otherTask = cleanupTask;
|
||||
await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(false);
|
||||
}
|
||||
lock (this)
|
||||
cleanupTask = HandleCleanup();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadCompileJob(CompileJob job)
|
||||
{
|
||||
if (job == null)
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
if (!job.DMApiValidated || job.Job.Cancelled || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null)
|
||||
throw new InvalidOperationException("Cannot load incomplete compile job!");
|
||||
lock (this)
|
||||
{
|
||||
var oldDmbProvider = nextDmbProvider;
|
||||
if (oldDmbProvider != null && oldDmbProvider.CompileJob.Job.StoppedAt < oldDmbProvider.CompileJob.Job.StoppedAt)
|
||||
throw new InvalidOperationException("Loaded compile job older than current job!");
|
||||
nextDmbProvider = new DmbProvider(job, ioManager, () => CleanJob(job));
|
||||
newerDmbTcs.SetResult(nextDmbProvider);
|
||||
newerDmbTcs = new TaskCompletionSource<DmbProvider>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IDmbProvider> LockNextDmb(CancellationToken cancellationToken)
|
||||
{
|
||||
Task<DmbProvider> task;
|
||||
lock (this)
|
||||
if (nextDmbProvider != null)
|
||||
return nextDmbProvider;
|
||||
else
|
||||
task = newerDmbTcs.Task;
|
||||
|
||||
return await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task OnNewerDmb()
|
||||
{
|
||||
lock (this)
|
||||
return newerDmbTcs.Task;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var cj = await databaseContext.CompileJobs.OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (cj == default(CompileJob))
|
||||
return;
|
||||
LoadCompileJob(cj);
|
||||
//delete all other compile jobs
|
||||
var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false);
|
||||
await Task.WhenAll(directories.Where(x => x != cj.Job.ToString()).Select(x => ioManager.DeleteDirectory(x, cancellationToken))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using (cancellationToken.Register(() => cleanupCts.Cancel()))
|
||||
await cleanupTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DmbProvider : IDmbProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string DmbName => String.Concat(CompileJob.DmeName, DreamMaker.DmbExtension);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string PrimaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.ADirectoryName));
|
||||
|
||||
/// <inheritdoc />
|
||||
public string SecondaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.BDirectoryName));
|
||||
|
||||
/// <inheritdoc />
|
||||
public RevisionInformation RevisionInformation => CompileJob.RevisionInformation;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/> for the <see cref="DmbProvider"/>
|
||||
/// </summary>
|
||||
public CompileJob CompileJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="DmbProvider"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
/// <summary>
|
||||
/// The <see cref="Action"/> to run when <see cref="Dispose"/> is called
|
||||
/// </summary>
|
||||
readonly Action onDispose;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DmbProvider"/>
|
||||
/// </summary>
|
||||
/// <param name="compileJob">The value of <see cref="CompileJob"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="onDispose">The value of <see cref="onDispose"/></param>
|
||||
public DmbProvider(CompileJob compileJob, IIOManager ioManager, Action onDispose)
|
||||
{
|
||||
CompileJob = compileJob ?? throw new ArgumentNullException(nameof(compileJob));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
|
||||
}
|
||||
|
||||
~DmbProvider() => Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
onDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,15 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// Name of the primary directory used for compilation
|
||||
/// </summary>
|
||||
const string ADirectoryName = "A";
|
||||
|
||||
public const string ADirectoryName = "A";
|
||||
/// <summary>
|
||||
/// Name of the secondary directory used for compilation
|
||||
/// </summary>
|
||||
public const string BDirectoryName = "B";
|
||||
public const string DmbExtension = ".dmb";
|
||||
|
||||
const string DmeExtension = ".dme";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
@@ -90,7 +97,7 @@ namespace Tgstation.Server.Host.Components
|
||||
ddTcs.SetResult(null);
|
||||
};
|
||||
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
|
||||
var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, ".dmb")))), interopInfo, true, cts.Token);
|
||||
var ddTestTask = dreamDaemonExecutor.RunDreamDaemon(launchParameters, null, dreamDaemonPath, new TemporaryDmbProvider(ioManager.ResolvePath(ioManager.GetDirectoryName(dirA)), ioManager.ResolvePath(ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmbExtension)))), interopInfo, true, cts.Token);
|
||||
|
||||
await Task.WhenAny(ddTcs.Task, ddTestTask).ConfigureAwait(false);
|
||||
|
||||
@@ -113,7 +120,7 @@ namespace Tgstation.Server.Host.Components
|
||||
using (var dm = new Process())
|
||||
{
|
||||
dm.StartInfo.FileName = dreamMakerPath;
|
||||
dm.StartInfo.Arguments = String.Format(CultureInfo.InvariantCulture, "-clean {0}.dme", job.DmeName);
|
||||
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;
|
||||
@@ -155,7 +162,7 @@ namespace Tgstation.Server.Host.Components
|
||||
async Task ModifyDme(Host.Models.CompileJob job, CancellationToken cancellationToken)
|
||||
{
|
||||
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
|
||||
var dmePath = ioManager.ConcatPath(dirA, String.Concat(job.DmeName, ".dme"));
|
||||
var dmePath = ioManager.ConcatPath(dirA, String.Concat(job.DmeName, DmeExtension));
|
||||
var dmeReadTask = ioManager.ReadAllBytes(dmePath, cancellationToken);
|
||||
|
||||
var dmeModificationsTask = configuration.CopyDMFilesTo(ioManager.ResolvePath(dirA), cancellationToken);
|
||||
@@ -198,7 +205,7 @@ namespace Tgstation.Server.Host.Components
|
||||
};
|
||||
await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false);
|
||||
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
|
||||
var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), "B");
|
||||
var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName);
|
||||
|
||||
async Task CleanupFailedCompile()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
interface ICompileJobConsumer
|
||||
{
|
||||
void LoadCompileJob(CompileJob job);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) => this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory)
|
||||
{
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.Compile)]
|
||||
|
||||
@@ -203,5 +203,18 @@ namespace Tgstation.Server.Host.Core
|
||||
using (var file = File.Open(path, FileMode.Create, FileAccess.Write))
|
||||
await file.WriteAsync(contents, 0, contents.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<string>> GetDirectories(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
var results = new List<string>();
|
||||
foreach(var I in Directory.EnumerateDirectories(path))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
results.Add(I);
|
||||
}
|
||||
return (IReadOnlyList<string>)results;
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,14 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <returns>A <see cref="Task"/> that results in the contents of a file at <paramref name="path"/></returns>
|
||||
Task<byte[]> ReadAllBytes(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Returns directory names in a given <paramref name="path"/>
|
||||
/// </summary>
|
||||
/// <param name="path">The path to search for directories</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the directories in <paramref name="path"/></returns>
|
||||
Task<IReadOnlyList<string>> GetDirectories(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Writes some <paramref name="contents"/> to a file at <paramref name="path"/> overwriting previous content
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user