diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs
new file mode 100644
index 0000000000..2fd12b08a2
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs
@@ -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
+{
+ ///
+ /// Standard
+ ///
+ sealed class DmbFactory : IDmbFactory, ICompileJobConsumer, IHostedService, IDisposable
+ {
+ ///
+ /// The for the
+ ///
+ readonly IDatabaseContext databaseContext;
+ ///
+ /// The for the
+ ///
+ readonly IIOManager ioManager;
+ ///
+ /// The for
+ ///
+ readonly CancellationTokenSource cleanupCts;
+
+ ///
+ /// representing calls to
+ ///
+ Task cleanupTask;
+ ///
+ /// resulting in the latest yet to exist
+ ///
+ TaskCompletionSource newerDmbTcs;
+ ///
+ /// The latest
+ ///
+ DmbProvider nextDmbProvider;
+
+ ///
+ /// Construct a
+ ///
+ /// The value of
+ /// The value of
+ 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();
+ }
+
+ ///
+ public void Dispose() => cleanupCts.Dispose();
+
+ ///
+ /// Delete the of
+ ///
+ /// The to clean
+ 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();
+ }
+
+ ///
+ 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();
+ }
+ }
+
+ ///
+ public async Task LockNextDmb(CancellationToken cancellationToken)
+ {
+ Task task;
+ lock (this)
+ if (nextDmbProvider != null)
+ return nextDmbProvider;
+ else
+ task = newerDmbTcs.Task;
+
+ return await task.ConfigureAwait(false);
+ }
+
+ ///
+ public Task OnNewerDmb()
+ {
+ lock (this)
+ return newerDmbTcs.Task;
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ using (cancellationToken.Register(() => cleanupCts.Cancel()))
+ await cleanupTask.ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/DmbProvider.cs b/src/Tgstation.Server.Host/Components/DmbProvider.cs
new file mode 100644
index 0000000000..0cd3f758f1
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/DmbProvider.cs
@@ -0,0 +1,58 @@
+using System;
+using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ sealed class DmbProvider : IDmbProvider
+ {
+ ///
+ public string DmbName => String.Concat(CompileJob.DmeName, DreamMaker.DmbExtension);
+
+ ///
+ public string PrimaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.ADirectoryName));
+
+ ///
+ public string SecondaryDirectory => ioManager.ResolvePath(ioManager.ConcatPath(CompileJob.DirectoryName.ToString(), DreamMaker.BDirectoryName));
+
+ ///
+ public RevisionInformation RevisionInformation => CompileJob.RevisionInformation;
+
+ ///
+ /// The for the
+ ///
+ public CompileJob CompileJob { get; }
+
+ ///
+ /// The for the
+ ///
+ readonly IIOManager ioManager;
+ ///
+ /// The to run when is called
+ ///
+ readonly Action onDispose;
+
+ ///
+ /// Construct a
+ ///
+ /// The value of
+ /// The value of
+ /// The value of
+ 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();
+
+ ///
+ public void Dispose()
+ {
+ onDispose();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs
index f43598ccdb..8469838b66 100644
--- a/src/Tgstation.Server.Host/Components/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs
@@ -18,8 +18,15 @@ namespace Tgstation.Server.Host.Components
///
/// Name of the primary directory used for compilation
///
- const string ADirectoryName = "A";
-
+ public const string ADirectoryName = "A";
+ ///
+ /// Name of the secondary directory used for compilation
+ ///
+ public const string BDirectoryName = "B";
+ public const string DmbExtension = ".dmb";
+
+ const string DmeExtension = ".dme";
+
///
/// The for
///
@@ -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()
{
diff --git a/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs
new file mode 100644
index 0000000000..0877e9516c
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/ICompileJobConsumer.cs
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
index 6b849d8636..56586f7bfd 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
@@ -31,7 +31,10 @@ namespace Tgstation.Server.Host.Controllers
/// The for the
/// The for the
/// The value of
- 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));
+ }
///
[TgsAuthorize(DreamMakerRights.Compile)]
diff --git a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs
index f0644d6f33..f1cc847bc4 100644
--- a/src/Tgstation.Server.Host/Core/DefaultIOManager.cs
+++ b/src/Tgstation.Server.Host/Core/DefaultIOManager.cs
@@ -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);
}
+
+ ///
+ public Task> GetDirectories(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
+ {
+ path = ResolvePath(path);
+ var results = new List();
+ foreach(var I in Directory.EnumerateDirectories(path))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ results.Add(I);
+ }
+ return (IReadOnlyList)results;
+ }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
}
diff --git a/src/Tgstation.Server.Host/Core/IIOManager.cs b/src/Tgstation.Server.Host/Core/IIOManager.cs
index 2083ad84aa..38df374e63 100644
--- a/src/Tgstation.Server.Host/Core/IIOManager.cs
+++ b/src/Tgstation.Server.Host/Core/IIOManager.cs
@@ -56,6 +56,14 @@ namespace Tgstation.Server.Host.Core
/// A that results in the contents of a file at
Task ReadAllBytes(string path, CancellationToken cancellationToken);
+ ///
+ /// Returns directory names in a given
+ ///
+ /// The path to search for directories
+ /// The for the operation
+ /// A resulting in the directories in
+ Task> GetDirectories(string path, CancellationToken cancellationToken);
+
///
/// Writes some to a file at overwriting previous content
///