From 720863d55e76db19a786db36c1ac10015e1f29c5 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 10:06:01 -0400 Subject: [PATCH 1/5] Remove timer shennanigans from repository. Fix null project name handling. Add IDatabaseContextFactory --- src/Tgstation.Server.Api/Models/Instance.cs | 6 + .../Models/Internal/DreamMakerSettings.cs | 6 - .../Models/Internal/RepositorySettings.cs | 6 - .../Rights/DreamMakerRights.cs | 6 +- .../Rights/InstanceManagerRights.cs | 6 +- .../Rights/RepositoryRights.cs | 10 +- .../Components/DmbFactory.cs | 16 +-- .../Components/DreamMaker.cs | 36 +++--- .../Components/IDreamMaker.cs | 6 +- .../Components/IInstance.cs | 3 + .../Components/IInstanceFactory.cs | 6 +- .../Components/IRepositoryManager.cs | 11 +- .../Components/Instance.cs | 110 +++++++++++++++++- .../Components/InstanceFactory.cs | 5 +- .../Components/RepositoryManager.cs | 82 +------------ .../Core/IDatabaseContextFactory.cs | 19 +++ 16 files changed, 181 insertions(+), 153 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 22bd31e1be..74910fdf68 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -41,6 +41,12 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = InstanceManagerRights.SetConfiguration)] public bool ConfigurationAllowed { get; set; } + /// + /// The time interval in minutes the repository is automatically pulled and compiles + /// + [Permissions(WriteRight = InstanceManagerRights.SetAutoUpdate)] + public int? AutoUpdateInterval { get; set; } + /// public Instance CloneMetadata() => new Instance { diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs index 2e07ee683a..154167ec82 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamMakerSettings.cs @@ -8,12 +8,6 @@ namespace Tgstation.Server.Api.Models.Internal [Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)] public class DreamMakerSettings { - /// - /// How often the automatically compiles in minutes - /// - [Permissions(WriteRight = DreamMakerRights.SetAutoCompile)] - public int? AutoCompileInterval { get; set; } - /// /// The .dme file tries to compile with without the extension /// diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs index 35a560f7da..5893293fa7 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs @@ -46,11 +46,5 @@ namespace Tgstation.Server.Api.Models.Internal /// [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)] public bool ShowTestMergeCommitters { get; set; } - - /// - /// How often the automatically updates in minutes - /// - [Permissions(WriteRight = RepositoryRights.ChangeAutoUpdate)] - public int? AutoUpdateInterval { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs index 661530c95a..bce942c165 100644 --- a/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs +++ b/src/Tgstation.Server.Api/Rights/DreamMakerRights.cs @@ -25,12 +25,8 @@ namespace Tgstation.Server.Api.Rights /// CancelCompile = 4, /// - /// User may modify - /// - SetAutoCompile = 8, - /// /// User may modify /// - SetDme = 16 + SetDme = 8 } } diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index 106eaa1ad1..fbec63f9d5 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -43,6 +43,10 @@ namespace Tgstation.Server.Api.Rights /// /// User can change /// - SetConfiguration = 128 + SetConfiguration = 128, + /// + /// User can change + /// + SetAutoUpdate = 256 } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index c418507036..23703fd446 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -37,20 +37,16 @@ namespace Tgstation.Server.Api.Rights /// ChangeTestMergeCommits = 64, /// - /// User may change - /// - ChangeAutoUpdate = 128, - /// /// User may read and change and /// - ChangeCredentials = 256, + ChangeCredentials = 128, /// /// User may set to another git reference (not a SHA) /// - SetReference = 512, + SetReference = 256, /// /// User may read all fields in the with the exception of /// - Read = 1024, + Read = 512, } } diff --git a/src/Tgstation.Server.Host/Components/DmbFactory.cs b/src/Tgstation.Server.Host/Components/DmbFactory.cs index cce071c63f..9d21f4747f 100644 --- a/src/Tgstation.Server.Host/Components/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/DmbFactory.cs @@ -14,9 +14,9 @@ namespace Tgstation.Server.Host.Components sealed class DmbFactory : IDmbFactory, ICompileJobConsumer { /// - /// The for the + /// The for the /// - readonly IDatabaseContext databaseContext; + readonly IDatabaseContextFactory databaseContextFactory; /// /// The for the /// @@ -42,11 +42,11 @@ namespace Tgstation.Server.Host.Components /// /// Construct a /// - /// The value of + /// The value of /// The value of - public DmbFactory(IDatabaseContext databaseContext, IIOManager ioManager) + public DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager) { - this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); cleanupCts = new CancellationTokenSource(); @@ -117,16 +117,16 @@ namespace Tgstation.Server.Host.Components } /// - public async Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) => { - var cj = await databaseContext.CompileJobs.OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var cj = await db.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) diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 441b45de40..f70df87e5e 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -23,8 +24,13 @@ namespace Tgstation.Server.Host.Components /// 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"; /// @@ -61,6 +67,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of + /// public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICompileJobConsumer compileJobConsumer) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -202,12 +209,12 @@ namespace Tgstation.Server.Host.Components } /// - public async Task Compile(string dmeName, IRepository repository, CancellationToken cancellationToken) + public async Task Compile(string projectName, IRepository repository, CancellationToken cancellationToken) { var job = new Host.Models.CompileJob { DirectoryName = Guid.NewGuid(), - DmeName = dmeName + DmeName = projectName }; await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); @@ -229,6 +236,16 @@ namespace Tgstation.Server.Host.Components using (repository) await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); + if (job.DmeName == null) + { + job.DmeName = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); + if (job.DmeName == default) + { + job.Output = "Unable to find any .dme!"; + return job; + } + } + await ModifyDme(job, cancellationToken).ConfigureAwait(false); //run compiler, verify api @@ -263,18 +280,5 @@ namespace Tgstation.Server.Host.Components throw; } } - - /// - public Task StartAsync(CancellationToken cancellationToken) - { - //THIS IS FOR AUTO COMPILE INTERVAL STUFF, STOP TRYING TO REMOVE IT - throw new NotImplementedException(); - } - - /// - public Task StopAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } } } diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 3e7c52eca5..215c1582ac 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -8,15 +8,15 @@ namespace Tgstation.Server.Host.Components /// /// For managing the compiler /// - interface IDreamMaker : IHostedService + interface IDreamMaker { /// /// Starts a compile /// - /// The .dme file to use without the extension + /// The name of the .dme to compile without the extension /// The to copy from /// The for the operation /// A resulting in the partially populated for the operation. In particular, note the field will only have it's field populated - Task Compile(string dmeName, IRepository repository, CancellationToken cancellationToken); + Task Compile(string projectName, IRepository repository, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 3735a5cc13..8105873fc5 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Hosting; +using System.Threading.Tasks; namespace Tgstation.Server.Host.Components { @@ -19,5 +20,7 @@ namespace Tgstation.Server.Host.Components Api.Models.Instance GetMetadata(); void Rename(string newName); + + Task SetAutoUpdateInterval(int? newInterval); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index 7dfd5a667d..5e4e3934f8 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -1,4 +1,4 @@ -using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { @@ -11,8 +11,8 @@ namespace Tgstation.Server.Host.Components /// Create an /// /// The - /// The for the operation + /// The for the operation /// A new - IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext); + IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContextFactory databaseContextFactory); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs index f778e46798..f7db73a8d7 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -7,8 +7,8 @@ namespace Tgstation.Server.Host.Components /// /// Factory for creating and loading s /// - interface IRepositoryManager : IHostedService - { + interface IRepositoryManager + { /// /// Attempt to load the from the default location /// @@ -24,12 +24,5 @@ namespace Tgstation.Server.Host.Components /// The for the operation /// The newly cloned Task CloneRepository(string url, string accessString, CancellationToken cancellationToken); - - /// - /// Change the interval in minutes at which the repository auto updates - /// - /// The new interval in minutes or to disable the auto update - /// A representing the running operation - Task SetAutoUpdateInterval(int? newInterval); } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 475c12e3d4..aea3891519 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -1,28 +1,58 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components { - sealed class Instance : IInstance + /// + sealed class Instance : IInstance, IDisposable { + /// public IRepositoryManager RepositoryManager { get; } + /// public IByond Byond { get; } + /// public IDreamMaker DreamMaker { get; } + /// public IDreamDaemon DreamDaemon { get; } + /// public IChat Chat { get; } + /// public IConfiguration Configuration { get; } + /// + /// The for the + /// readonly ICompileJobConsumer compileJobConsumer; + + /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + /// + /// The for the + /// readonly Api.Models.Instance metadata; - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer) + /// + /// The auto update + /// + Task timerTask; + /// + /// for + /// + CancellationTokenSource timerCts; + + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IDreamDaemon dreamDaemon, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); @@ -32,10 +62,50 @@ namespace Tgstation.Server.Host.Components Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); } + /// + public void Dispose() => timerCts?.Dispose(); + + /// + /// Pull the repository and compile for every set of given + /// + /// How many minutes the operation should repeat. Does not include running time + /// The for the operation + /// A representing the running operation + async Task TimerLoop(int minutes, CancellationToken cancellationToken) + { + try + { + while (true) + { + await Task.Delay(new TimeSpan(0, minutes, 0), cancellationToken).ConfigureAwait(false); + + string accessToken = null, projectName = null; + var dbTask = databaseContextFactory.UseContext(async (db) => + { + var instanceQuery = db.Instances.Where(x => x.Id == metadata.Id); + var projectNameTask = instanceQuery.Select(x => x.DreamMakerSettings.ProjectName).FirstOrDefaultAsync(cancellationToken); + accessToken = await instanceQuery.Select(x => x.RepositorySettings.AccessToken).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + projectName = await projectNameTask.ConfigureAwait(false); + }); + using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + await dbTask.ConfigureAwait(false); + await repo.FetchOrigin(accessToken, cancellationToken).ConfigureAwait(false); + await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); + await DreamMaker.Compile(projectName, repo, cancellationToken).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException) { } + } + + /// public Api.Models.Instance GetMetadata() => metadata.CloneMetadata(); + /// public void Rename(string newName) { if (String.IsNullOrWhiteSpace(newName)) @@ -43,8 +113,38 @@ namespace Tgstation.Server.Host.Components metadata.Name = newName; } - public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StartAsync(cancellationToken), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), DreamMaker.StartAsync(cancellationToken)); + /// + public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), DreamDaemon.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken)); - public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(RepositoryManager.StopAsync(cancellationToken), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), DreamMaker.StopAsync(cancellationToken)); + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken)); + + /// + public async Task SetAutoUpdateInterval(int? newInterval) + { + Task toWait; + lock (this) + { + if (timerTask != null) + { + timerCts.Cancel(); + toWait = timerTask; + } + else + toWait = Task.CompletedTask; + } + await toWait.ConfigureAwait(false); + if (!newInterval.HasValue) + return; + lock (this) + { + //race condition, just quit + if (timerTask != null) + return; + timerCts?.Dispose(); + timerCts = new CancellationTokenSource(); + timerTask = TimerLoop(newInterval.Value, timerCts.Token); + } + } } } diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index ed4955194e..00767fcabd 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -1,6 +1,5 @@ using System; using Tgstation.Server.Host.Core; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { @@ -19,7 +18,7 @@ namespace Tgstation.Server.Host.Components public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); /// - public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext) + public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContextFactory databaseContextFactory) { //Create the ioManager for the instance @@ -32,7 +31,7 @@ namespace Tgstation.Server.Host.Components var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); var codeModificationsIoMananger = new ResolvingIOManager(instanceIoManager, "CodeModifications"); - var dmbFactory = new DmbFactory(databaseContext, gameIoManager); + var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager); diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index 5982887adb..ffb8cf1049 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -25,16 +25,6 @@ namespace Tgstation.Server.Host.Components /// readonly SemaphoreSlim semaphore; - /// - /// for - /// - CancellationTokenSource timerCancellationTokenSource; - - /// - /// Represents the running update timer if any - /// - Task currentTimerTask; - /// /// Construct a /// @@ -48,49 +38,7 @@ namespace Tgstation.Server.Host.Components } /// - public void Dispose() - { - timerCancellationTokenSource?.Dispose(); - semaphore.Dispose(); - } - - /// - /// Stops and joins it - /// - /// A representing the running operation - async Task StopTimer() - { - if (currentTimerTask == null) - return; - timerCancellationTokenSource.Cancel(); - await currentTimerTask.ConfigureAwait(false); - currentTimerTask = null; - } - - /// - /// Asyncronously fetch and reset the current branch for each given amount of - /// - /// The delay of the timer - /// The accessString to use for fetch operations - /// The for the operation - /// A representing the running operation - async Task TimerLoop(int minutes, string accessString, CancellationToken cancellationToken) - { - try - { - while (true) - { - await Task.Delay(TimeSpan.FromMinutes(minutes), cancellationToken).ConfigureAwait(false); - using (var repo = await LoadRepository(cancellationToken).ConfigureAwait(false)) - { - //TODO: Find the unauthorized exception, catch it, and log it - await repo.FetchOrigin(accessString, cancellationToken).ConfigureAwait(false); - await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false); - } - } - } - catch (OperationCanceledException) { } - } + public void Dispose() => semaphore.Dispose(); /// public async Task CloneRepository(string url, string accessString, CancellationToken cancellationToken) @@ -134,33 +82,5 @@ namespace Tgstation.Server.Host.Components localSemaphore = null; }); } - - /// - public async Task SetAutoUpdateInterval(int? newInterval) - { - await StopTimer().ConfigureAwait(false); - if (!newInterval.HasValue) - return; - - string accessString = null; - - if (timerCancellationTokenSource != null) - timerCancellationTokenSource.Dispose(); - timerCancellationTokenSource = new CancellationTokenSource(); - - currentTimerTask = TimerLoop(repositorySettings.AutoUpdateInterval.Value, accessString, timerCancellationTokenSource.Token); - } - - /// - public Task StartAsync(CancellationToken cancellationToken) => SetAutoUpdateInterval(repositorySettings.AutoUpdateInterval); - - /// - public async Task StopAsync(CancellationToken cancellationToken) - { - var timerStopTask = StopTimer(); - var tcs = new TaskCompletionSource(); - using (cancellationToken.Register(() => tcs.SetCanceled())) - await Task.WhenAny(timerStopTask, tcs.Task).ConfigureAwait(false); - } } } diff --git a/src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs b/src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs new file mode 100644 index 0000000000..305092416c --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IDatabaseContextFactory.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Factory for scoping usage of s. Meant for use by + /// + interface IDatabaseContextFactory + { + /// + /// Run an in the scope of an + /// + /// The operation to run + /// A representing the running + Task UseContext(Func operation); + } +} From 6ad70948daa563799d1c416d8840a24bc183e588 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 10:10:56 -0400 Subject: [PATCH 2/5] Implement DatabaseContextFactory --- .../Core/DatabaseContextFactory.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs diff --git a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs new file mode 100644 index 0000000000..f3d64f355c --- /dev/null +++ b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class DatabaseContextFactory : IDatabaseContextFactory + { + /// + /// The for the + /// + readonly IServiceProvider serviceProvider; + + /// + /// Construct a + /// + /// The value of + public DatabaseContextFactory(IServiceProvider serviceProvider) => this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + + /// + public async Task UseContext(Func operation) + { + using (var scope = serviceProvider.CreateScope()) + await operation(scope.ServiceProvider.GetRequiredService()).ConfigureAwait(false); + } + } +} From 3e9a8475b1c3eb767ce5b84dc7a8a1714afa387f Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 10:27:49 -0400 Subject: [PATCH 3/5] It all comes together --- .../Components/IInstanceFactory.cs | 3 +- .../Components/IInstanceManager.cs | 6 +-- .../Components/IRepository.cs | 2 +- .../Components/IRepositoryManager.cs | 3 +- .../Components/InstanceFactory.cs | 14 +++++- .../Components/InstanceManager.cs | 47 +++++++++---------- .../Controllers/DreamMakerController.cs | 22 +++++++-- .../Models/DatabaseContext.cs | 7 ++- .../Models/IDatabaseContext.cs | 5 ++ 9 files changed, 64 insertions(+), 45 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs index 5e4e3934f8..d9d4f4cda8 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceFactory.cs @@ -11,8 +11,7 @@ namespace Tgstation.Server.Host.Components /// Create an /// /// The - /// The for the operation /// A new - IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContextFactory databaseContextFactory); + IInstance CreateInstance(Host.Models.Instance metadata); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index c087ad362a..d26d87489b 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -20,10 +20,9 @@ namespace Tgstation.Server.Host.Components /// Online an /// /// The of the desired - /// The for the operation /// The for the operation /// A representing the running operation - Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken); + Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken); /// /// Offline an @@ -37,10 +36,9 @@ namespace Tgstation.Server.Host.Components /// Move an /// /// The of the desired - /// The for the operation /// The new path of the . will have this set on if the operation completes successfully /// The for the operation /// A representing the running operation - Task MoveInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken); + Task MoveInstance(Host.Models.Instance metadata, string newPath, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/IRepository.cs b/src/Tgstation.Server.Host/Components/IRepository.cs index 7acd5053df..09acbb72c5 100644 --- a/src/Tgstation.Server.Host/Components/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/IRepository.cs @@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components /// /// Represents an on-disk git repository /// - interface IRepository : IDisposable + public interface IRepository : IDisposable { /// /// If the was cloned from GitHub.com diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs index f7db73a8d7..5f39b45733 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Hosting; -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 00767fcabd..62112fac9b 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -11,14 +11,24 @@ namespace Tgstation.Server.Host.Components /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + /// /// Construct an /// /// The value of - public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); + } /// - public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContextFactory databaseContextFactory) + public IInstance CreateInstance(Host.Models.Instance metadata) { //Create the ioManager for the instance diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 1b01e2b8e9..3d1a615f2c 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Core; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { @@ -16,18 +15,18 @@ namespace Tgstation.Server.Host.Components sealed class InstanceManager : IInstanceManager, IHostedService { /// - /// The for the + /// The for the /// readonly IInstanceFactory instanceFactory; /// - /// The for the - /// - readonly IServiceProvider serviceProvider; - /// - /// The for the + /// The for the /// readonly IIOManager ioManager; /// + /// The for the + /// + readonly IDatabaseContextFactory databaseContextFactory; + /// /// Map of s to respective s /// readonly Dictionary instances; @@ -36,13 +35,13 @@ namespace Tgstation.Server.Host.Components /// Construct an /// /// The value of - /// The value of /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IServiceProvider serviceProvider, IIOManager ioManager) + /// The value of + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); - this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); instances = new Dictionary(); } @@ -60,7 +59,7 @@ namespace Tgstation.Server.Host.Components } /// - public async Task MoveInstance(Host.Models.Instance instance, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken) + public async Task MoveInstance(Host.Models.Instance instance, string newPath, CancellationToken cancellationToken) { if (newPath == null) throw new ArgumentNullException(nameof(newPath)); @@ -72,14 +71,14 @@ namespace Tgstation.Server.Host.Components var oldPath = instance.Path; await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); instance.Path = ioManager.ResolvePath(newPath); - instanceOnlineTask = OnlineInstance(instance, databaseContext, default); + instanceOnlineTask = OnlineInstance(instance, default); await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); } finally { if (instance.Online) if (instanceOnlineTask == null) - await OnlineInstance(instance, databaseContext, default).ConfigureAwait(false); + await OnlineInstance(instance, default).ConfigureAwait(false); else await instanceOnlineTask.ConfigureAwait(false); } @@ -101,11 +100,11 @@ namespace Tgstation.Server.Host.Components } /// - public async Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken) + public async Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken) { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); - var instance = instanceFactory.CreateInstance(metadata, databaseContext); + var instance = instanceFactory.CreateInstance(metadata); lock (this) { if (instances.ContainsKey(metadata.Id)) @@ -116,18 +115,14 @@ namespace Tgstation.Server.Host.Components } /// - public async Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext => { - using(var scope = serviceProvider.CreateScope()) - { - var databaseContext = scope.ServiceProvider.GetRequiredService(); - await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); - var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); - var tasks = new List(); - await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, databaseContext, cancellationToken)), cancellationToken).ConfigureAwait(false); - await Task.WhenAll(tasks).ConfigureAwait(false); - } - } + await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); + var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable(); + var tasks = new List(); + await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); + }); /// public async Task StopAsync(CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index 56586f7bfd..fd39fdc931 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Controllers /// Controller for managing the compiler /// [Route("/DreamMaker")] - public sealed class DreamMakerController : ModelController + public sealed class DreamMakerController : ModelController { /// /// The for the @@ -38,7 +38,7 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.Compile)] - public override async Task Create([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken) + public override async Task Create([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) { var job = new Job { @@ -53,16 +53,30 @@ namespace Tgstation.Server.Host.Controllers /// [TgsAuthorize(DreamMakerRights.CancelCompile)] - public override async Task Delete([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken) + public override async Task Delete([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) { //alias for cancelling the latest job - var job = await DatabaseContext.Jobs.OrderByDescending(x => x.StartedAt).Select(x => new Job { Id = x.Id, StoppedAt = x.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); + var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Select(x => new Job { Id = x.Job.Id, StoppedAt = x.Job.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); if (job.StoppedAt != null) return StatusCode(HttpStatusCode.Gone); jobManager.CancelJob(job); return Ok(); } + /// + [TgsAuthorize(DreamMakerRights.SetDme)] + public override async Task Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) + { + var hostModel = new DreamMakerSettings + { + InstanceId = Instance.Id + }; + DatabaseContext.DreamMakerSettings.Attach(hostModel); + hostModel.ProjectName = model.ProjectName; + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + return Ok(); + } + /// /// Run the compile job and insert it into the database /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 3d848add3e..b97991c636 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -28,6 +28,9 @@ namespace Tgstation.Server.Host.Models /// public DbSet RevisionInformations { get; set; } + /// + public DbSet DreamMakerSettings { get; set; } + /// /// The for s /// @@ -50,10 +53,6 @@ namespace Tgstation.Server.Host.Models /// public DbSet DreamDaemonSettings { get; set; } /// - /// The in the - /// - public DbSet DreamMakerSettings { get; set; } - /// /// The s in the /// public DbSet Jobs { get; set; } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs index da1bea8f12..d027884bc4 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -34,6 +34,11 @@ namespace Tgstation.Server.Host.Models /// DbSet RevisionInformations { get; } + /// + /// The in the + /// + DbSet DreamMakerSettings { get; set; } + /// /// Get the in the /// From 18304741c52572c2a654f1abfb7ceca4f5f6c875 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 11:15:45 -0400 Subject: [PATCH 4/5] Pull the chains --- .../Models/CompilerStatus.cs | 26 +++- src/Tgstation.Server.Api/Models/Job.cs | 6 - .../Components/DreamMaker.cs | 136 +++++++++++------- .../Components/IByond.cs | 2 +- src/Tgstation.Server.Host/Components/IChat.cs | 10 +- .../Components/IConfiguration.cs | 2 +- .../Components/IDreamDaemon.cs | 2 +- .../Components/IDreamMaker.cs | 7 +- .../Components/IInstance.cs | 36 ++++- .../Components/IInstanceManager.cs | 3 +- .../Components/IInstanceShutdownHandler.cs | 14 +- .../Components/IRepositoryManager.cs | 9 +- .../Components/RepositoryManager.cs | 4 +- .../Components/ServerSideModifications.cs | 2 +- .../Controllers/DreamMakerController.cs | 27 +++- src/Tgstation.Server.Host/Core/IJobManager.cs | 5 +- src/Tgstation.Server.Host/Core/JobManager.cs | 14 +- .../Models/CompileJob.cs | 20 ++- .../Models/IApiConvertable.cs | 15 ++ src/Tgstation.Server.Host/Models/Job.cs | 22 ++- .../Models/RevisionInformation.cs | 11 +- src/Tgstation.Server.Host/Models/TestMerge.cs | 18 ++- src/Tgstation.Server.Host/Models/User.cs | 23 ++- 23 files changed, 321 insertions(+), 93 deletions(-) create mode 100644 src/Tgstation.Server.Host/Models/IApiConvertable.cs diff --git a/src/Tgstation.Server.Api/Models/CompilerStatus.cs b/src/Tgstation.Server.Api/Models/CompilerStatus.cs index 8ca0b48143..d29df195e6 100644 --- a/src/Tgstation.Server.Api/Models/CompilerStatus.cs +++ b/src/Tgstation.Server.Api/Models/CompilerStatus.cs @@ -12,16 +12,32 @@ /// Idle, /// - /// The directiory is being targeted - /// - Targeting, - /// /// The is being copied /// Copying, + /// + /// The .dme is having it's server side modifications applied + /// + Modifying, /// /// DreamMaker is running /// - Compiling + Compiling, + /// + /// The DMAPI is being verified + /// + Verifying, + /// + /// The compile results are being duplicated + /// + Duplicating, + /// + /// The configuration is being linked to the compile results + /// + Symlinking, + /// + /// A failed compile job is being erased + /// + Cleanup } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs index b1141f3400..2f413fff43 100644 --- a/src/Tgstation.Server.Api/Models/Job.cs +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -5,12 +5,6 @@ /// public sealed class Job : Internal.Job { - /// - /// If the has incremental progress, this will range from 1 - 100. 0 otherwise - /// - [Permissions(DenyWrite = true)] - public int Progress { get; set; } - /// /// The that started the job /// diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index f70df87e5e..ffd577f98b 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -33,6 +33,9 @@ namespace Tgstation.Server.Host.Components /// const string DmeExtension = ".dme"; + /// + public CompilerStatus Status { get; private set; } + /// /// The for /// @@ -172,6 +175,12 @@ namespace Tgstation.Server.Host.Components } } + /// + /// Adds server side includes to the .dme being compiled + /// + /// The for the operation + /// The for the operation + /// A representing the running operation async Task ModifyDme(Host.Models.CompileJob job, CancellationToken cancellationToken) { var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); @@ -211,73 +220,92 @@ namespace Tgstation.Server.Host.Components /// public async Task Compile(string projectName, IRepository repository, CancellationToken cancellationToken) { - var job = new Host.Models.CompileJob - { - DirectoryName = Guid.NewGuid(), - DmeName = projectName - }; - await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); - var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); - var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName); - - async Task CleanupFailedCompile() - { - try - { - await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); - } - catch { } - }; - try { - //copy the repository - var fullDirA = ioManager.ResolvePath(dirA); - using (repository) - await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - - if (job.DmeName == null) + Status = CompilerStatus.Copying; + var job = new Host.Models.CompileJob { - job.DmeName = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); - if (job.DmeName == default) + DirectoryName = Guid.NewGuid(), + DmeName = projectName + }; + await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); + var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); + var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName); + + async Task CleanupFailedCompile() + { + Status = CompilerStatus.Cleanup; + try { - job.Output = "Unable to find any .dme!"; - return job; + await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); } - } + catch { } + }; - await ModifyDme(job, cancellationToken).ConfigureAwait(false); - - //run compiler, verify api - var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + try { - await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + //copy the repository + var fullDirA = ioManager.ResolvePath(dirA); + using (repository) + await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); - return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); - }, true).ConfigureAwait(false); + Status = CompilerStatus.Modifying; - if (!ddVerified) - //server never validated or compile failed + if (job.DmeName == null) + { + job.DmeName = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); + if (job.DmeName == default) + { + job.Output = "Unable to find any .dme!"; + return job; + } + } + + await ModifyDme(job, cancellationToken).ConfigureAwait(false); + + Status = CompilerStatus.Compiling; + + //run compiler, verify api + var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) => + { + await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false); + + Status = CompilerStatus.Verifying; + + return job.ExitCode == 0 && await VerifyApi(dreamDaemonPath, job, cancellationToken).ConfigureAwait(false); + }, true).ConfigureAwait(false); + + if (!ddVerified) + //server never validated or compile failed + await CleanupFailedCompile().ConfigureAwait(false); + else + { + job.DMApiValidated = true; + + Status = CompilerStatus.Duplicating; + + //duplicate the dmb et al + await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); + + Status = CompilerStatus.Symlinking; + + //symlink in the static data + var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); + await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); + await symATask.ConfigureAwait(false); + } + compileJobConsumer.LoadCompileJob(job); + return job; + } + catch + { await CleanupFailedCompile().ConfigureAwait(false); - else - { - job.DMApiValidated = true; - - //duplicate the dmb et al - await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); - - //symlink in the static data - var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); - await configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken).ConfigureAwait(false); - await symATask.ConfigureAwait(false); + throw; } - compileJobConsumer.LoadCompileJob(job); - return job; } - catch + finally { - await CleanupFailedCompile().ConfigureAwait(false); - throw; + Status = CompilerStatus.Idle; } } } diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByond.cs index acf4f4c7cc..6af7e4627b 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByond.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing the BYOND installation /// - interface IByond + public interface IByond { /// /// Change the current BYOND version diff --git a/src/Tgstation.Server.Host/Components/IChat.cs b/src/Tgstation.Server.Host/Components/IChat.cs index bd43137bca..a1f494a1ff 100644 --- a/src/Tgstation.Server.Host/Components/IChat.cs +++ b/src/Tgstation.Server.Host/Components/IChat.cs @@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing connected chat services /// - interface IChat : IHostedService + public interface IChat : IHostedService { /// /// If the IRC client is connected @@ -47,6 +47,14 @@ namespace Tgstation.Server.Host.Components /// A representing the running operation Task SendMessage(string message, IEnumerable channelIds, CancellationToken cancellationToken); + /// + /// Start tracking json files for commands and channels + /// + /// The base path of the .jsons + /// The name of the chat channels json + /// The name of the chat commands json + /// The for the operation + /// A resulting in a tied to the lifetime of the json trackings Task TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index 3b31dda93e..da32288f4d 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing the Configuration directory /// - interface IConfiguration + public interface IConfiguration { /// /// Copies all files in the CodeModifications directory to diff --git a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs index b052ce9ead..dbd32b9d2a 100644 --- a/src/Tgstation.Server.Host/Components/IDreamDaemon.cs +++ b/src/Tgstation.Server.Host/Components/IDreamDaemon.cs @@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components /// /// For managing DreamDaemon /// - interface IDreamDaemon : IHostedService + public interface IDreamDaemon : IHostedService { /// /// If DreamDaemon is running diff --git a/src/Tgstation.Server.Host/Components/IDreamMaker.cs b/src/Tgstation.Server.Host/Components/IDreamMaker.cs index 215c1582ac..938aeb46b8 100644 --- a/src/Tgstation.Server.Host/Components/IDreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/IDreamMaker.cs @@ -8,8 +8,13 @@ namespace Tgstation.Server.Host.Components /// /// For managing the compiler /// - interface IDreamMaker + public interface IDreamMaker { + /// + /// The of + /// + Api.Models.CompilerStatus Status { get; } + /// /// Starts a compile /// diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 8105873fc5..a06c30c3a3 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -3,24 +3,58 @@ using System.Threading.Tasks; namespace Tgstation.Server.Host.Components { - interface IInstance : IHostedService + /// + /// For interacting with the instance services + /// + public interface IInstance : IHostedService { + /// + /// The for the + /// IRepositoryManager RepositoryManager { get; } + /// + /// The for the + /// IByond Byond { get; } + /// + /// The for the + /// IDreamMaker DreamMaker { get; } + /// + /// The for the + /// IDreamDaemon DreamDaemon { get; } + /// + /// The for the + /// IChat Chat { get; } + /// + /// The for the + /// IConfiguration Configuration { get; } + /// + /// Get the associated with the + /// + /// The associated with the Api.Models.Instance GetMetadata(); + /// + /// Rename the + /// + /// The new name for the void Rename(string newName); + /// + /// Change the for the + /// + /// The new auto update inteval + /// A representing the running operation Task SetAutoUpdateInterval(int? newInterval); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index d26d87489b..68a60ad247 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -1,13 +1,12 @@ using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Components { /// /// For managing s /// - interface IInstanceManager : IInstanceShutdownHandler + public interface IInstanceManager : IInstanceShutdownHandler { /// /// Get the associated with given diff --git a/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs b/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs index e07de394b6..87264ca110 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceShutdownHandler.cs @@ -3,8 +3,20 @@ using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components { - interface IInstanceShutdownHandler + /// + /// For handling shutdowns + /// + public interface IInstanceShutdownHandler { + //TODO + /// + /// OMG + /// + /// + /// + /// + /// + /// Task PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary); } } diff --git a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs index 5f39b45733..94fd3a2ac3 100644 --- a/src/Tgstation.Server.Host/Components/IRepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/IRepositoryManager.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; namespace Tgstation.Server.Host.Components @@ -6,7 +7,7 @@ namespace Tgstation.Server.Host.Components /// /// Factory for creating and loading s /// - interface IRepositoryManager + public interface IRepositoryManager { /// /// Attempt to load the from the default location @@ -18,10 +19,10 @@ namespace Tgstation.Server.Host.Components /// /// Delete the current and replaces it with a clone of the repository at /// - /// The location of the remote repository to clone + /// The of the remote repository to clone /// The access string to clone from /// The for the operation /// The newly cloned - Task CloneRepository(string url, string accessString, CancellationToken cancellationToken); + Task CloneRepository(Uri url, string accessString, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/RepositoryManager.cs index ffb8cf1049..e87d417cbd 100644 --- a/src/Tgstation.Server.Host/Components/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/RepositoryManager.cs @@ -41,7 +41,7 @@ namespace Tgstation.Server.Host.Components public void Dispose() => semaphore.Dispose(); /// - public async Task CloneRepository(string url, string accessString, CancellationToken cancellationToken) + public async Task CloneRepository(Uri url, string accessString, CancellationToken cancellationToken) { await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false); @@ -50,7 +50,7 @@ namespace Tgstation.Server.Host.Components string path = null; try { - path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url, accessString), ioManager.ResolvePath("."), new CloneOptions + path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url.ToString(), accessString), ioManager.ResolvePath("."), new CloneOptions { OnProgress = (a) => !cancellationToken.IsCancellationRequested, OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested, diff --git a/src/Tgstation.Server.Host/Components/ServerSideModifications.cs b/src/Tgstation.Server.Host/Components/ServerSideModifications.cs index a9edcb1ddc..e54faac733 100644 --- a/src/Tgstation.Server.Host/Components/ServerSideModifications.cs +++ b/src/Tgstation.Server.Host/Components/ServerSideModifications.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components /// /// Represents code modifications via configuration /// - sealed class ServerSideModifications + public sealed class ServerSideModifications { /// /// If the target dme was completely overwitten diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index fd39fdc931..f94a51537e 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -24,6 +24,10 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IInstanceManager instanceManager; /// /// Construct a @@ -31,9 +35,26 @@ 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) + /// The value of + public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + + /// + [TgsAuthorize(DreamMakerRights.Read)] + public override async Task Read(CancellationToken cancellationToken) + { + var instance = instanceManager.GetInstance(Instance); + var projectNameTask = DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).Select(x => x.ProjectName).FirstAsync(cancellationToken); + var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Include(x => x.Job).FirstAsync(cancellationToken).ConfigureAwait(false); + return Json(new Api.Models.DreamMaker + { + LastJob = job.ToApi(), + ProjectName = await projectNameTask.ConfigureAwait(false), + Status = instance.DreamMaker.Status + }); } /// @@ -59,7 +80,7 @@ namespace Tgstation.Server.Host.Controllers var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Select(x => new Job { Id = x.Job.Id, StoppedAt = x.Job.StoppedAt }).FirstAsync(cancellationToken).ConfigureAwait(false); if (job.StoppedAt != null) return StatusCode(HttpStatusCode.Gone); - jobManager.CancelJob(job); + await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } @@ -90,7 +111,7 @@ namespace Tgstation.Server.Host.Controllers var instanceManager = serviceProvider.GetRequiredService(); var databaseContext = serviceProvider.GetRequiredService(); - var projectName = await databaseContext.Instances.Where(x => x.Id == instanceModel.Id).Select(x => x.DreamMakerSettings.ProjectName).FirstAsync(cancellationToken).ConfigureAwait(false); + var projectName = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => x.ProjectName).FirstAsync(cancellationToken).ConfigureAwait(false); var instance = instanceManager.GetInstance(instanceModel); diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 0912a64890..f3bca3dd91 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -32,6 +32,9 @@ namespace Tgstation.Server.Host.Core /// Cancels a give /// /// The to cancel - void CancelJob(Job job); + /// The who cancelled the + /// The for the operation + /// A representing a running operation + Task CancelJob(Job job, User user, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 0676f2cc17..0c6e340011 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -145,6 +145,18 @@ namespace Tgstation.Server.Host.Core } /// - public void CancelJob(Job job) => CheckGetJob(job).Cancel(); + public async Task CancelJob(Job job, User user, CancellationToken cancellationToken) + { + if (user != null) + using (var scope = serviceProvider.CreateScope()) + { + var databaseContext = scope.ServiceProvider.GetRequiredService(); + job = new Job { Id = job.Id }; + databaseContext.Jobs.Attach(job); + job.CancelledBy = user; + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } + CheckGetJob(job).Cancel(); + } } } diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs index f15efd5859..6245765298 100644 --- a/src/Tgstation.Server.Host/Models/CompileJob.cs +++ b/src/Tgstation.Server.Host/Models/CompileJob.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class CompileJob : Api.Models.Internal.CompileJob - { + public sealed class CompileJob : Api.Models.Internal.CompileJob, IApiConvertable + { /// /// The of /// @@ -21,5 +22,18 @@ namespace Tgstation.Server.Host.Models /// [Required] public RevisionInformation RevisionInformation { get; set; } - } + + /// + public Api.Models.CompileJob ToApi() => new Api.Models.CompileJob + { + DirectoryName = DirectoryName, + DMApiValidated = DMApiValidated, + DmeName = DmeName, + ExitCode = ExitCode, + Id = Id, + Job = Job.ToApi(), + Output = Output, + RevisionInformation = RevisionInformation.ToApi() + }; + } } diff --git a/src/Tgstation.Server.Host/Models/IApiConvertable.cs b/src/Tgstation.Server.Host/Models/IApiConvertable.cs new file mode 100644 index 0000000000..3a53bb26c7 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/IApiConvertable.cs @@ -0,0 +1,15 @@ +namespace Tgstation.Server.Host.Models +{ + /// + /// For converting models to their API form + /// + /// Which of the this model backs + public interface IApiConvertable where TModel : class + { + /// + /// Convert the model to it's API form + /// + /// A new + TModel ToApi(); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 43d1fefba4..51a4b5d50c 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -3,12 +3,32 @@ namespace Tgstation.Server.Host.Models { /// - public sealed class Job : Api.Models.Internal.Job + public sealed class Job : Api.Models.Internal.Job, IApiConvertable { /// /// See /// [Required] public User StartedBy { get; set; } + + /// + /// See + /// + public User CancelledBy { get; set; } + + /// + public Api.Models.Job ToApi() => new Api.Models.Job + { + Id = Id, + StartedAt = StartedAt, + StoppedAt = StoppedAt, + Cancelled = Cancelled, + CancelledBy = CancelledBy.ToApi(), + CancelRight = CancelRight, + CancelRightsType = CancelRightsType, + Description = Description, + ExceptionDetails = ExceptionDetails, + StartedBy = StartedBy.ToApi() + }; } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 057cc7e680..2d05122e66 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using System.Linq; namespace Tgstation.Server.Host.Models { /// - public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation + public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation, IApiConvertable { /// /// The row Id @@ -14,5 +15,13 @@ namespace Tgstation.Server.Host.Models /// See /// public List TestMerges { get; set; } + + /// + public Api.Models.RevisionInformation ToApi() => new Api.Models.RevisionInformation + { + Commit = Commit, + OriginRevision = OriginRevision, + TestMerges = TestMerges.Select(x => x.ToApi()).ToList() + }; } } diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs index 05e201c04b..108f584086 100644 --- a/src/Tgstation.Server.Host/Models/TestMerge.cs +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class TestMerge : Api.Models.Internal.TestMerge + public sealed class TestMerge : Api.Models.Internal.TestMerge, IApiConvertable { /// /// See @@ -15,5 +16,20 @@ namespace Tgstation.Server.Host.Models /// The for the /// public RevisionInformation RevisionInformation { get; set; } + + /// + public Api.Models.TestMerge ToApi() => new Api.Models.TestMerge + { + Author = Author, + BodyAtMerge = BodyAtMerge, + MergedAt = MergedAt, + TitleAtMerge = TitleAtMerge, + Comment = Comment, + Id = Id, + MergedBy = MergedBy.ToApi(), + Number =Number, + PullRequestRevision = PullRequestRevision, + Url = Url + }; } } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index dec14852f9..93587647ef 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models { /// - public sealed class User : Api.Models.Internal.User + public sealed class User : Api.Models.Internal.User, IApiConvertable { /// /// The hash of the user's password @@ -24,5 +25,25 @@ namespace Tgstation.Server.Host.Models /// The s for the /// public List InstanceUsers { get; set; } + + /// + /// See + /// + /// If we should recurse on + /// A new + Api.Models.User ToApi(bool recursive) => new Api.Models.User + { + AdministrationRights = AdministrationRights, + CreatedAt = CreatedAt, + CreatedBy = recursive ? CreatedBy?.ToApi(false) : null, + Enabled = Enabled, + Id = Id, + InstanceManagerRights = InstanceManagerRights, + Name = Name, + SystemIdentifier = SystemIdentifier + }; + + /// + public Api.Models.User ToApi() => ToApi(true); } } From 64dc20e556a203b9f08f259e12fdccaa2978f28a Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Mon, 7 May 2018 11:50:00 -0400 Subject: [PATCH 5/5] That's the jobs controller --- .../Controllers/JobController.cs | 70 +++++++++++++++++++ src/Tgstation.Server.Host/Core/IJobManager.cs | 8 --- src/Tgstation.Server.Host/Core/JobManager.cs | 56 ++++++++------- .../Models/InstanceUser.cs | 10 +++ src/Tgstation.Server.Host/Models/Job.cs | 5 ++ 5 files changed, 114 insertions(+), 35 deletions(-) create mode 100644 src/Tgstation.Server.Host/Controllers/JobController.cs diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs new file mode 100644 index 0000000000..982fc72cd8 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// for s + /// + [Route("/Job")] + public sealed class JobController : ModelController + { + /// + /// The for the + /// + readonly IJobManager jobManager; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory) + { + this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + } + + /// + [TgsAuthorize] + public override async Task List(CancellationToken cancellationToken) + { + IQueryable query = DatabaseContext.Jobs; + if (Instance != null) + { + if (!AuthenticationContext.InstanceUser.AnyRights) + return Forbid(); + query = query.Where(x => x.Instance.Id == Instance.Id); + } + else + query = query.Where(x => x.Instance == null); + + var jobs = await query.Where(x => x.StoppedAt == null).ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(jobs.Select(x => x.ToApi())); + } + + /// + [TgsAuthorize] + public override async Task Delete([FromBody] Api.Models.Job model, CancellationToken cancellationToken) + { + //don't care if an instance post or not at this point + var job = await DatabaseContext.Jobs.Where(x => x.Id == model.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (job == default(Job)) + return NotFound(); + + if(job.StoppedAt != null) + return StatusCode(HttpStatusCode.Gone); + + await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); + return Ok(); + } + } +} diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index f3bca3dd91..3eabf70612 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -20,14 +20,6 @@ namespace Tgstation.Server.Host.Core /// A representing a running operation Task RegisterOperation(Job job, Func operation, CancellationToken cancellationToken); - /// - /// Wait for a given to complete - /// - /// The to wait for - /// The for the operation - /// A representing a running operation - Task WaitForJob(Job job, CancellationToken cancellationToken); - /// /// Cancels a give /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index 0c6e340011..03f12ef437 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -55,33 +55,45 @@ namespace Tgstation.Server.Host.Core /// A representing the running operation async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) { - using (var scope = serviceProvider.CreateScope()) + try { - IDatabaseContext databaseContext = null; - try + using (var scope = serviceProvider.CreateScope()) { - var oldJob = job; - job = new Job { Id = oldJob.Id }; + IDatabaseContext databaseContext = null; try { - await operation(job, scope.ServiceProvider, cancellationToken).ConfigureAwait(false); + var oldJob = job; + job = new Job { Id = oldJob.Id }; + try + { + await operation(job, scope.ServiceProvider, cancellationToken).ConfigureAwait(false); + } + finally + { + databaseContext = scope.ServiceProvider.GetRequiredService(); + databaseContext.Jobs.Attach(job); + } } - finally + catch (OperationCanceledException) { - databaseContext = scope.ServiceProvider.GetRequiredService(); - databaseContext.Jobs.Attach(job); + job.Cancelled = true; } + catch (Exception e) + { + job.ExceptionDetails = e.ToString(); + } + job.StoppedAt = DateTimeOffset.Now; + await databaseContext.Save(default).ConfigureAwait(false); } - catch (OperationCanceledException) + } + finally + { + lock (this) { - job.Cancelled = true; + var handler = jobs[job.Id]; + jobs.Remove(job.Id); + handler.Dispose(); } - catch (Exception e) - { - job.ExceptionDetails = e.ToString(); - } - job.StoppedAt = DateTimeOffset.Now; - await databaseContext.Save(default).ConfigureAwait(false); } } @@ -134,16 +146,6 @@ namespace Tgstation.Server.Host.Core jobs.Clear(); } - /// - public async Task WaitForJob(Job job, CancellationToken cancellationToken) - { - var handler = CheckGetJob(job); - await handler.Wait(cancellationToken).ConfigureAwait(false); - lock (this) - jobs.Remove(job.Id); - handler.Dispose(); - } - /// public async Task CancelJob(Job job, User user, CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs index 6566b2c891..8e2f3b9ec5 100644 --- a/src/Tgstation.Server.Host/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Host.Models { @@ -15,5 +16,14 @@ namespace Tgstation.Server.Host.Models /// [Required] public Instance Instance { get; set; } + + /// + /// If the has any instance rights + /// + public bool AnyRights => ByondRights != ByondRights.None || + ChatSettingsRights != ChatSettingsRights.None || + ConfigurationRights != ConfigurationRights.None || + DreamDaemonRights != DreamDaemonRights.None || + DreamMakerRights != DreamMakerRights.None; } } diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 51a4b5d50c..1c0cb88d3c 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -16,6 +16,11 @@ namespace Tgstation.Server.Host.Models /// public User CancelledBy { get; set; } + /// + /// The the job belongs to if any + /// + public Instance Instance { get; set; } + /// public Api.Models.Job ToApi() => new Api.Models.Job {