diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs index 7738cf0043..b4247221e1 100644 --- a/src/Tgstation.Server.Api/Models/Internal/Job.cs +++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs @@ -33,7 +33,8 @@ namespace Tgstation.Server.Api.Models.Internal /// When the was started /// [Permissions(DenyWrite = true)] - public DateTimeOffset StartedAt { get; set; } + [Required] + public DateTimeOffset? StartedAt { get; set; } /// /// When the stopped @@ -45,7 +46,8 @@ namespace Tgstation.Server.Api.Models.Internal /// If the was cancelled /// [Permissions(DenyWrite = true)] - public bool Cancelled { get; set; } + [Required] + public bool? Cancelled { get; set; } /// /// The of if it can be cancelled diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 41ae7207d3..3e5422c62d 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Text; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond @@ -12,7 +13,7 @@ namespace Tgstation.Server.Host.Components.Byond /// /// for windows systems /// - sealed class WindowsByondInstaller : IByondInstaller + sealed class WindowsByondInstaller : IByondInstaller, IDisposable { /// /// The URL format string for getting BYOND windows version {0}.{1} zipfile @@ -51,6 +52,11 @@ namespace Tgstation.Server.Host.Components.Byond /// readonly ILogger logger; + /// + /// The for the + /// + readonly SemaphoreSlim semaphore; + /// /// If DirectX was installed /// @@ -66,9 +72,13 @@ namespace Tgstation.Server.Host.Components.Byond this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + semaphore = new SemaphoreSlim(1); installedDirectX = false; } + /// + public void Dispose() => semaphore.Dispose(); + /// public async Task CleanCache(CancellationToken cancellationToken) { @@ -103,8 +113,8 @@ namespace Tgstation.Server.Host.Components.Byond var setNoPromptTrustedModeTask = SetNoPromptTrusted(); //after this version lummox made DD depend of directx lol - if (version.Major >= 512 && version.Minor >= 1427 && Monitor.TryEnter(this)) - try + if (version.Major >= 512 && version.Minor >= 1427 && !installedDirectX) + using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { if (!installedDirectX) //always install it, it's pretty fast and will do better redundancy checking than us @@ -143,10 +153,6 @@ namespace Tgstation.Server.Host.Components.Byond installedDirectX = true; } } - finally - { - Monitor.Exit(this); - } await setNoPromptTrustedModeTask.ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs index 08fffc4f0a..e994fddfae 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs @@ -107,7 +107,7 @@ namespace Tgstation.Server.Host.Components.Compiler { if (job == null) throw new ArgumentNullException(nameof(job)); - if (job.DMApiValidated != true || job.Job.Cancelled || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null) + if (job.DMApiValidated != true || job.Job.Cancelled.Value || job.Job.ExceptionDetails != null || job.Job.StoppedAt == null) throw new InvalidOperationException("Cannot load incomplete compile job!"); if (setAsStagedInDb) await databaseContextFactory.UseContext(async db => diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index d25f914517..c9acbb0d6a 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Rights; @@ -73,25 +72,6 @@ namespace Tgstation.Server.Host.Controllers return Json(job); } - /// - [TgsAuthorize(DreamMakerRights.CancelCompile)] - public override async Task Delete(long id, CancellationToken cancellationToken) - { - //alias for cancelling the latest job - 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((int)HttpStatusCode.Gone); - try - { - await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); - } - catch (InvalidOperationException) //job already stopped - { - return StatusCode((int)HttpStatusCode.Gone); - } - return Ok(); - } - /// [TgsAuthorize(DreamMakerRights.SetDme)] public override async Task Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs index 69680526fe..c9ba839646 100644 --- a/src/Tgstation.Server.Host/Controllers/ModelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs @@ -68,7 +68,7 @@ namespace Tgstation.Server.Host.Controllers /// The ID of the model to delete /// The for the operation /// A resulting in the of the operation - [HttpDelete] + [HttpDelete("{id}")] public virtual Task Delete(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound()); /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index c72590d5c9..4d8359e438 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -10,7 +10,7 @@ using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { /// - sealed class JobManager : IHostedService, IJobManager + sealed class JobManager : IHostedService, IJobManager, IDisposable { /// /// The for the @@ -31,6 +31,13 @@ namespace Tgstation.Server.Host.Core jobs = new Dictionary(); } + /// + public void Dispose() + { + foreach (var job in jobs) + job.Value.Dispose(); + } + /// /// Gets the for a given if it exists /// @@ -104,6 +111,7 @@ namespace Tgstation.Server.Host.Core { var databaseContext = scope.ServiceProvider.GetRequiredService(); job.StartedAt = DateTimeOffset.Now; + job.Cancelled = false; job.Instance = new Instance { Id = job.Instance.Id @@ -134,7 +142,7 @@ namespace Tgstation.Server.Host.Core await databaseContext.Initialize(cancellationToken).ConfigureAwait(false); //mark all jobs as cancelled - var enumerator = await databaseContext.Jobs.Where(y => !y.Cancelled && y.StoppedAt == null).Select(y => y.Id).ToAsyncEnumerable().ToList(cancellationToken).ConfigureAwait(false); + var enumerator = await databaseContext.Jobs.Where(y => !y.Cancelled.Value && !y.StoppedAt.HasValue).Select(y => y.Id).ToAsyncEnumerable().ToList(cancellationToken).ConfigureAwait(false); foreach(var I in enumerator) { var job = new Job { Id = I }; @@ -154,9 +162,6 @@ namespace Tgstation.Server.Host.Core return x.Value.Wait(cancellationToken); }); await Task.WhenAll(joinTasks).ConfigureAwait(false); - foreach (var job in jobs) - job.Value.Dispose(); - jobs.Clear(); } /// @@ -175,6 +180,7 @@ namespace Tgstation.Server.Host.Core user = new User { Id = user.Id }; databaseContext.Users.Attach(user); job.CancelledBy = user; + //let either startup or cancellation set job.cancelled await databaseContext.Save(cancellationToken).ConfigureAwait(false); } } diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 865411b1b6..c4b235b3d7 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -265,9 +265,27 @@ namespace Tgstation.Server.Host.IO /// public async Task DownloadFile(Uri url, CancellationToken cancellationToken) { + //DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it using (var wc = new WebClient()) - using (cancellationToken.Register(() => wc.CancelAsync())) - return await wc.DownloadDataTaskAsync(url).ConfigureAwait(false); + { + var tcs = new TaskCompletionSource(); + wc.DownloadDataCompleted += (a, b) => + { + if (b.Error != null) + tcs.TrySetException(b.Error); + else if (b.Cancelled) + tcs.TrySetCanceled(); + else + tcs.TrySetResult(b.Result); + }; + wc.DownloadDataAsync(url); + using (cancellationToken.Register(() => + { + wc.CancelAsync(); //cancelasync alone doesnt do it either! who wrote this!! + tcs.SetCanceled(); + })) + return await tcs.Task.ConfigureAwait(false); + } //ITS STILL FUCKING DOWNLOADING!!! } /// diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index d3692a376d..92ea78f73b 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -842,6 +842,49 @@ }, "response": [] }, + { + "name": "Set ID 1 to only #botbus", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 1,\n\t\"channels\": [\n\t\t{\n\t\t\t\"ircChannel\": \"#botbus\",\n\t\t\t\"isAdminChannel\": true,\n\t\t\t\"isWatchdogChannel\": true\n\t\t}\n\t\t]\n}" + }, + "url": { + "raw": "localhost:5000/Chat", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Chat" + ] + } + }, + "response": [] + }, { "name": "Create r4407 on Disord", "request": { @@ -1199,6 +1242,167 @@ ], "_postman_isSubFolder": true }, + { + "name": "Jobs", + "description": "", + "item": [ + { + "name": "List Ids", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": -1,\n\t\"dreamDaemonRights\": -1,\n\t\"dreamMakerRights\": -1,\n\t\"repositoryRights\": -1,\n\t\"chatSettingsRights\": -1,\n\t\"configurationRights\": -1\n}" + }, + "url": { + "raw": "localhost:5000/Job/List", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Job", + "List" + ] + } + }, + "response": [] + }, + { + "name": "Get ID", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": -1,\n\t\"dreamDaemonRights\": -1,\n\t\"dreamMakerRights\": -1,\n\t\"repositoryRights\": -1,\n\t\"chatSettingsRights\": -1,\n\t\"configurationRights\": -1\n}" + }, + "url": { + "raw": "localhost:5000/Job/10", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Job", + "10" + ] + } + }, + "response": [] + }, + { + "name": "Cancel ID", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "localhost:5000/Job/11", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Job", + "11" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "a3f3a2f7-5700-48a9-88bf-d18ac2a1855c", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "787a5a17-ad9c-4772-9052-85d81e87d4a9", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "_postman_isSubFolder": true + }, { "name": "Online Instance ID 1", "request": { @@ -1397,6 +1601,185 @@ } ] }, + { + "name": "Byond", + "description": "", + "item": [ + { + "name": "List installed versions", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Byond/List", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond", + "List" + ] + } + }, + "response": [] + }, + { + "name": "Read active version", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"online\": false\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + }, + { + "name": "Set 511.1385 active", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"version\": {\n \t\"major\": 511,\n \t\"minor\": 1385\n }\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + }, + { + "name": "Set 512.1441 active", + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "User-Agent", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "InstanceId", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"version\": {\n \t\"major\": 512,\n \t\"minor\": 1441\n }\n}" + }, + "url": { + "raw": "localhost:5000/Byond", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "Byond" + ] + } + }, + "response": [] + } + ] + }, { "name": "Login Admin Default", "request": {