mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 07:04:57 +01:00
Merge pull request #514 from Cyberboss/SeeIfWeCanGetThisBitchRunning
Like building a deathstar with no exhaust port
This commit is contained in:
@@ -12,16 +12,32 @@
|
||||
/// </summary>
|
||||
Idle,
|
||||
/// <summary>
|
||||
/// The directiory is being targeted
|
||||
/// </summary>
|
||||
Targeting,
|
||||
/// <summary>
|
||||
/// The <see cref="Repository"/> is being copied
|
||||
/// </summary>
|
||||
Copying,
|
||||
/// <summary>
|
||||
/// The .dme is having it's server side modifications applied
|
||||
/// </summary>
|
||||
Modifying,
|
||||
/// <summary>
|
||||
/// DreamMaker is running
|
||||
/// </summary>
|
||||
Compiling
|
||||
Compiling,
|
||||
/// <summary>
|
||||
/// The DMAPI is being verified
|
||||
/// </summary>
|
||||
Verifying,
|
||||
/// <summary>
|
||||
/// The compile results are being duplicated
|
||||
/// </summary>
|
||||
Duplicating,
|
||||
/// <summary>
|
||||
/// The configuration is being linked to the compile results
|
||||
/// </summary>
|
||||
Symlinking,
|
||||
/// <summary>
|
||||
/// A failed compile job is being erased
|
||||
/// </summary>
|
||||
Cleanup
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,12 @@ namespace Tgstation.Server.Api.Models
|
||||
[Permissions(WriteRight = InstanceManagerRights.SetConfiguration)]
|
||||
public bool ConfigurationAllowed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time interval in minutes the repository is automatically pulled and compiles
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = InstanceManagerRights.SetAutoUpdate)]
|
||||
public int? AutoUpdateInterval { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Instance CloneMetadata() => new Instance
|
||||
{
|
||||
|
||||
@@ -8,12 +8,6 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
[Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)]
|
||||
public class DreamMakerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// How often the <see cref="DreamMakerSettings"/> automatically compiles in minutes
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = DreamMakerRights.SetAutoCompile)]
|
||||
public int? AutoCompileInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The .dme file <see cref="DreamMakerSettings"/> tries to compile with without the extension
|
||||
/// </summary>
|
||||
|
||||
@@ -46,11 +46,5 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)]
|
||||
public bool ShowTestMergeCommitters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How often the <see cref="Repository"/> automatically updates in minutes
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeAutoUpdate)]
|
||||
public int? AutoUpdateInterval { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
/// </summary>
|
||||
public sealed class Job : Internal.Job
|
||||
{
|
||||
/// <summary>
|
||||
/// If the <see cref="Job"/> has incremental progress, this will range from 1 - 100. 0 otherwise
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public int Progress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> that started the job
|
||||
/// </summary>
|
||||
|
||||
@@ -25,12 +25,8 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
CancelCompile = 4,
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.Internal.DreamMakerSettings.AutoCompileInterval"/>
|
||||
/// </summary>
|
||||
SetAutoCompile = 8,
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.Internal.DreamMakerSettings.ProjectName"/>
|
||||
/// </summary>
|
||||
SetDme = 16
|
||||
SetDme = 8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Instance.ConfigurationAllowed"/>
|
||||
/// </summary>
|
||||
SetConfiguration = 128
|
||||
SetConfiguration = 128,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Instance.AutoUpdateInterval"/>
|
||||
/// </summary>
|
||||
SetAutoUpdate = 256
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,20 +37,16 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
ChangeTestMergeCommits = 64,
|
||||
/// <summary>
|
||||
/// User may change <see cref="Models.Internal.RepositorySettings.AutoUpdateInterval"/>
|
||||
/// </summary>
|
||||
ChangeAutoUpdate = 128,
|
||||
/// <summary>
|
||||
/// User may read and change <see cref="Models.Internal.RepositorySettings.AccessUser"/> and <see cref="Models.Internal.RepositorySettings.AccessToken"/>
|
||||
/// </summary>
|
||||
ChangeCredentials = 256,
|
||||
ChangeCredentials = 128,
|
||||
/// <summary>
|
||||
/// User may set <see cref="Models.Repository.Reference"/> to another git reference (not a SHA)
|
||||
/// </summary>
|
||||
SetReference = 512,
|
||||
SetReference = 256,
|
||||
/// <summary>
|
||||
/// User may read all fields in the <see cref="Models.Repository"/> with the exception of <see cref="Models.Internal.RepositorySettings.AccessToken"/>
|
||||
/// </summary>
|
||||
Read = 1024,
|
||||
Read = 512,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ namespace Tgstation.Server.Host.Components
|
||||
sealed class DmbFactory : IDmbFactory, ICompileJobConsumer
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContext"/> for the <see cref="DmbFactory"/>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContext databaseContext;
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
@@ -42,11 +42,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DmbFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The value of <see cref="databaseContext"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
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
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
|
||||
@@ -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,10 +24,18 @@ namespace Tgstation.Server.Host.Components
|
||||
/// Name of the secondary directory used for compilation
|
||||
/// </summary>
|
||||
public const string BDirectoryName = "B";
|
||||
/// <summary>
|
||||
/// Extension for .dmbs
|
||||
/// </summary>
|
||||
public const string DmbExtension = ".dmb";
|
||||
|
||||
/// <summary>
|
||||
/// Extension for .dmes
|
||||
/// </summary>
|
||||
const string DmeExtension = ".dme";
|
||||
|
||||
/// <inheritdoc />
|
||||
public CompilerStatus Status { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
@@ -61,6 +70,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="byond">The value of <see cref="byond"/></param>
|
||||
/// <param name="interop">The value of <see cref="interop"/></param>
|
||||
/// <param name="compileJobConsumer">The value of <see cref="compileJobConsumer"/></param>
|
||||
///
|
||||
public DreamMaker(IIOManager ioManager, IConfiguration configuration, IDreamDaemonExecutor dreamDaemonExecutor, IByond byond, IInterop interop, ICompileJobConsumer compileJobConsumer)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
@@ -165,6 +175,12 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds server side includes to the .dme being compiled
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Host.Models.CompileJob"/> for the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task ModifyDme(Host.Models.CompileJob job, CancellationToken cancellationToken)
|
||||
{
|
||||
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
|
||||
@@ -202,79 +218,95 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Host.Models.CompileJob> Compile(string dmeName, IRepository repository, CancellationToken cancellationToken)
|
||||
public async Task<Host.Models.CompileJob> Compile(string projectName, IRepository repository, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = new Host.Models.CompileJob
|
||||
{
|
||||
DirectoryName = Guid.NewGuid(),
|
||||
DmeName = dmeName
|
||||
};
|
||||
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);
|
||||
|
||||
await ModifyDme(job, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//run compiler, verify api
|
||||
var ddVerified = await byond.UseExecutables(async (dreamMakerPath, dreamDaemonPath) =>
|
||||
Status = CompilerStatus.Copying;
|
||||
var job = new Host.Models.CompileJob
|
||||
{
|
||||
await RunDreamMaker(dreamMakerPath, job, cancellationToken).ConfigureAwait(false);
|
||||
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);
|
||||
|
||||
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
|
||||
async Task CleanupFailedCompile()
|
||||
{
|
||||
job.DMApiValidated = true;
|
||||
Status = CompilerStatus.Cleanup;
|
||||
try
|
||||
{
|
||||
await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
};
|
||||
|
||||
//duplicate the dmb et al
|
||||
await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
//copy the repository
|
||||
var fullDirA = ioManager.ResolvePath(dirA);
|
||||
using (repository)
|
||||
await repository.CopyTo(fullDirA, 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);
|
||||
Status = CompilerStatus.Modifying;
|
||||
|
||||
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);
|
||||
throw;
|
||||
}
|
||||
compileJobConsumer.LoadCompileJob(job);
|
||||
return job;
|
||||
}
|
||||
catch
|
||||
finally
|
||||
{
|
||||
await CleanupFailedCompile().ConfigureAwait(false);
|
||||
throw;
|
||||
Status = CompilerStatus.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
//THIS IS FOR AUTO COMPILE INTERVAL STUFF, STOP TRYING TO REMOVE IT
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// For managing the BYOND installation
|
||||
/// </summary>
|
||||
interface IByond
|
||||
public interface IByond
|
||||
{
|
||||
/// <summary>
|
||||
/// Change the current BYOND version
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// For managing connected chat services
|
||||
/// </summary>
|
||||
interface IChat : IHostedService
|
||||
public interface IChat : IHostedService
|
||||
{
|
||||
/// <summary>
|
||||
/// If the IRC client is connected
|
||||
@@ -47,6 +47,14 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SendMessage(string message, IEnumerable<long> channelIds, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Start tracking json files for commands and channels
|
||||
/// </summary>
|
||||
/// <param name="basePath">The base path of the .jsons</param>
|
||||
/// <param name="channelsJsonName">The name of the chat channels json</param>
|
||||
/// <param name="commandsJsonName">The name of the chat commands json</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IDisposable"/> tied to the lifetime of the json trackings</returns>
|
||||
Task<IDisposable> TrackJsons(string basePath, string channelsJsonName, string commandsJsonName, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// For managing the Configuration directory
|
||||
/// </summary>
|
||||
interface IConfiguration
|
||||
public interface IConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Copies all files in the CodeModifications directory to <paramref name="destination"/>
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// For managing DreamDaemon
|
||||
/// </summary>
|
||||
interface IDreamDaemon : IHostedService
|
||||
public interface IDreamDaemon : IHostedService
|
||||
{
|
||||
/// <summary>
|
||||
/// If DreamDaemon is running
|
||||
|
||||
@@ -8,15 +8,20 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// For managing the compiler
|
||||
/// </summary>
|
||||
interface IDreamMaker : IHostedService
|
||||
public interface IDreamMaker
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.CompilerStatus"/> of <see cref="IDreamMaker"/>
|
||||
/// </summary>
|
||||
Api.Models.CompilerStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts a compile
|
||||
/// </summary>
|
||||
/// <param name="dmeName">The .dme file to use without the extension</param>
|
||||
/// <param name="projectName">The name of the .dme to compile without the extension</param>
|
||||
/// <param name="repository">The <see cref="IRepository"/> to copy from</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the partially populated <see cref="CompileJob"/> for the operation. In particular, note the <see cref="CompileJob.RevisionInformation"/> field will only have it's <see cref="Api.Models.Internal.RevisionInformation.Commit"/> field populated</returns>
|
||||
Task<CompileJob> Compile(string dmeName, IRepository repository, CancellationToken cancellationToken);
|
||||
Task<CompileJob> Compile(string projectName, IRepository repository, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,60 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
interface IInstance : IHostedService
|
||||
/// <summary>
|
||||
/// For interacting with the instance services
|
||||
/// </summary>
|
||||
public interface IInstance : IHostedService
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IRepositoryManager"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IRepositoryManager RepositoryManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IByond"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IByond Byond { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDreamMaker"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IDreamMaker DreamMaker { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDreamDaemon"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IDreamDaemon DreamDaemon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IChat"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IChat Chat { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IConfiguration"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
IConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Api.Models.Instance"/> associated with the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Api.Models.Instance"/> associated with the <see cref="IInstance"/></returns>
|
||||
Api.Models.Instance GetMetadata();
|
||||
|
||||
/// <summary>
|
||||
/// Rename the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="newName">The new name for the <see cref="IInstance"/></param>
|
||||
void Rename(string newName);
|
||||
|
||||
/// <summary>
|
||||
/// Change the <see cref="Api.Models.Instance.AutoUpdateInterval"/> for the <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="newInterval">The new auto update inteval</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SetAutoUpdateInterval(int? newInterval);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Core;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
@@ -11,8 +11,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// Create an <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Host.Models.Instance"/></param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation</param>
|
||||
/// <returns>A new <see cref="IInstance"/></returns>
|
||||
IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext);
|
||||
IInstance CreateInstance(Host.Models.Instance metadata);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="IInstance"/>s
|
||||
/// </summary>
|
||||
interface IInstanceManager : IInstanceShutdownHandler
|
||||
public interface IInstanceManager : IInstanceShutdownHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="IInstance"/> associated with given <paramref name="metadata"/>
|
||||
@@ -20,10 +19,9 @@ namespace Tgstation.Server.Host.Components
|
||||
/// Online an <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Host.Models.Instance"/> of the desired <see cref="IInstance"/></param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task OnlineInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, CancellationToken cancellationToken);
|
||||
Task OnlineInstance(Host.Models.Instance metadata, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Offline an <see cref="IInstance"/>
|
||||
@@ -37,10 +35,9 @@ namespace Tgstation.Server.Host.Components
|
||||
/// Move an <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Host.Models.Instance"/> of the desired <see cref="IInstance"/></param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation</param>
|
||||
/// <param name="newPath">The new path of the <see cref="IInstance"/>. <paramref name="metadata"/> will have this set on <see cref="Api.Models.Instance.Path"/> if the operation completes successfully</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task MoveInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext, string newPath, CancellationToken cancellationToken);
|
||||
Task MoveInstance(Host.Models.Instance metadata, string newPath, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,20 @@ using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
interface IInstanceShutdownHandler
|
||||
/// <summary>
|
||||
/// For handling <see cref="IInstance"/> shutdowns
|
||||
/// </summary>
|
||||
public interface IInstanceShutdownHandler
|
||||
{
|
||||
//TODO
|
||||
/// <summary>
|
||||
/// OMG
|
||||
/// </summary>
|
||||
/// <param name="launchParameters"></param>
|
||||
/// <param name="accessToken"></param>
|
||||
/// <param name="pid"></param>
|
||||
/// <param name="primary"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> PreserveActiveExecutablesIfNecessary(DreamDaemonLaunchParameters launchParameters, string accessToken, int pid, bool primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// Represents an on-disk git repository
|
||||
/// </summary>
|
||||
interface IRepository : IDisposable
|
||||
public interface IRepository : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// If the <see cref="IRepository"/> was cloned from GitHub.com
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// Factory for creating and loading <see cref="IRepository"/>s
|
||||
/// </summary>
|
||||
interface IRepositoryManager : IHostedService
|
||||
{
|
||||
public interface IRepositoryManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempt to load the <see cref="IRepository"/> from the default location
|
||||
/// </summary>
|
||||
@@ -19,17 +19,10 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// Delete the current <see cref="IRepository"/> and replaces it with a clone of the repository at <paramref name="url"/>
|
||||
/// </summary>
|
||||
/// <param name="url">The location of the remote repository to clone</param>
|
||||
/// <param name="url">The <see cref="Uri"/> of the remote repository to clone</param>
|
||||
/// <param name="accessString">The access string to clone from <paramref name="url"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>The newly cloned <see cref="IRepository"/></returns>
|
||||
Task<IRepository> CloneRepository(string url, string accessString, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Change the interval in minutes at which the repository auto updates
|
||||
/// </summary>
|
||||
/// <param name="newInterval">The new interval in minutes or <see langword="null"/> to disable the auto update</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SetAutoUpdateInterval(int? newInterval);
|
||||
Task<IRepository> CloneRepository(Uri url, string accessString, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <inheritdoc />
|
||||
sealed class Instance : IInstance, IDisposable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IRepositoryManager RepositoryManager { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IByond Byond { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDreamMaker DreamMaker { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDreamDaemon DreamDaemon { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChat Chat { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICompileJobConsumer"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
readonly ICompileJobConsumer compileJobConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
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)
|
||||
/// <summary>
|
||||
/// The auto update <see cref="Task"/>
|
||||
/// </summary>
|
||||
Task timerTask;
|
||||
/// <summary>
|
||||
/// <see cref="CancellationTokenSource"/> for <see cref="timerTask"/>
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => timerCts?.Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Pull the repository and compile for every set of given <paramref name="minutes"/>
|
||||
/// </summary>
|
||||
/// <param name="minutes">How many minutes the operation should repeat. Does not include running time</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
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) { }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Api.Models.Instance GetMetadata() => metadata.CloneMetadata();
|
||||
|
||||
/// <inheritdoc />
|
||||
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));
|
||||
/// <inheritdoc />
|
||||
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));
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), DreamDaemon.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken));
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
@@ -12,14 +11,24 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
public InstanceFactory(IIOManager ioManager) => this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstance CreateInstance(Host.Models.Instance metadata, IDatabaseContext databaseContext)
|
||||
public IInstance CreateInstance(Host.Models.Instance metadata)
|
||||
{
|
||||
//Create the ioManager for the instance
|
||||
|
||||
@@ -32,7 +41,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);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceFactory"/> for the <see cref="IInstanceManager"/>
|
||||
/// The <see cref="IInstanceFactory"/> for the <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
readonly IInstanceFactory instanceFactory;
|
||||
/// <summary>
|
||||
/// The <see cref="IServiceProvider"/> for the <see cref="IInstanceManager"/>
|
||||
/// </summary>
|
||||
readonly IServiceProvider serviceProvider;
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="IInstanceManager"/>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
/// <summary>
|
||||
/// Map of <see cref="Api.Models.Instance.Id"/>s to respective <see cref="IInstance"/>s
|
||||
/// </summary>
|
||||
readonly Dictionary<long, IInstance> instances;
|
||||
@@ -36,13 +35,13 @@ namespace Tgstation.Server.Host.Components
|
||||
/// Construct an <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceFactory">The value of <see cref="instanceFactory"/></param>
|
||||
/// <param name="serviceProvider">The value of <paramref name="serviceProvider"/></param>
|
||||
/// <param name="ioManager">The value of <paramref name="ioManager"/></param>
|
||||
public InstanceManager(IInstanceFactory instanceFactory, IServiceProvider serviceProvider, IIOManager ioManager)
|
||||
/// <param name="databaseContextFactory">The value of <paramref name="databaseContextFactory"/></param>
|
||||
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<long, IInstance>();
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<IDatabaseContext>();
|
||||
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<Task>();
|
||||
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<Task>();
|
||||
await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
|
||||
@@ -25,16 +25,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly SemaphoreSlim semaphore;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CancellationTokenSource"/> for <see cref="currentTimerTask"/>
|
||||
/// </summary>
|
||||
CancellationTokenSource timerCancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the running update timer if any
|
||||
/// </summary>
|
||||
Task currentTimerTask;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="RepositoryManager"/>
|
||||
/// </summary>
|
||||
@@ -48,52 +38,10 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
timerCancellationTokenSource?.Dispose();
|
||||
semaphore.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops <see cref="currentTimerTask"/> and joins it
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task StopTimer()
|
||||
{
|
||||
if (currentTimerTask == null)
|
||||
return;
|
||||
timerCancellationTokenSource.Cancel();
|
||||
await currentTimerTask.ConfigureAwait(false);
|
||||
currentTimerTask = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asyncronously fetch and reset the current branch for each given amount of <paramref name="minutes"/>
|
||||
/// </summary>
|
||||
/// <param name="minutes">The delay of the timer</param>
|
||||
/// <param name="accessString">The accessString to use for fetch operations</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
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();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IRepository> CloneRepository(string url, string accessString, CancellationToken cancellationToken)
|
||||
public async Task<IRepository> CloneRepository(Uri url, string accessString, CancellationToken cancellationToken)
|
||||
{
|
||||
await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -102,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,
|
||||
@@ -134,33 +82,5 @@ namespace Tgstation.Server.Host.Components
|
||||
localSemaphore = null;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken) => SetAutoUpdateInterval(repositorySettings.AutoUpdateInterval);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var timerStopTask = StopTimer();
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => tcs.SetCanceled()))
|
||||
await Task.WhenAny(timerStopTask, tcs.Task).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <summary>
|
||||
/// Represents code modifications via configuration
|
||||
/// </summary>
|
||||
sealed class ServerSideModifications
|
||||
public sealed class ServerSideModifications
|
||||
{
|
||||
/// <summary>
|
||||
/// If the target dme was completely overwitten
|
||||
|
||||
@@ -18,12 +18,16 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// Controller for managing the compiler
|
||||
/// </summary>
|
||||
[Route("/DreamMaker")]
|
||||
public sealed class DreamMakerController : ModelController<Api.Models.CompileJob>
|
||||
public sealed class DreamMakerController : ModelController<Api.Models.DreamMaker>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="DreamMakerController"/>
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceManager"/> for the <see cref="DreamMakerController"/>
|
||||
/// </summary>
|
||||
readonly IInstanceManager instanceManager;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="HomeController"/>
|
||||
@@ -31,14 +35,31 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory)
|
||||
/// <param name="instanceManager">The value of <see cref="instanceManager"/></param>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.Read)]
|
||||
public override async Task<IActionResult> 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
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.Compile)]
|
||||
public override async Task<IActionResult> Create([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken)
|
||||
public override async Task<IActionResult> Create([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = new Job
|
||||
{
|
||||
@@ -53,13 +74,27 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.CancelCompile)]
|
||||
public override async Task<IActionResult> Delete([FromBody] Api.Models.CompileJob model, CancellationToken cancellationToken)
|
||||
public override async Task<IActionResult> 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);
|
||||
await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.SetDme)]
|
||||
public override async Task<IActionResult> 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();
|
||||
}
|
||||
|
||||
@@ -76,7 +111,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var instanceManager = serviceProvider.GetRequiredService<IInstanceManager>();
|
||||
var databaseContext = serviceProvider.GetRequiredService<IDatabaseContext>();
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ModelController{TModel}"/> for <see cref="Api.Models.Job"/>s
|
||||
/// </summary>
|
||||
[Route("/Job")]
|
||||
public sealed class JobController : ModelController<Api.Models.Job>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="JobController"/>
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory)
|
||||
{
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize]
|
||||
public override async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
IQueryable<Job> 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()));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize]
|
||||
public override async Task<IActionResult> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DatabaseContextFactory : IDatabaseContextFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IServiceProvider"/> for the <see cref="DatabaseContextFactory"/>
|
||||
/// </summary>
|
||||
readonly IServiceProvider serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DatabaseContextFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The value of <see cref="serviceProvider"/></param>
|
||||
public DatabaseContextFactory(IServiceProvider serviceProvider) => this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UseContext(Func<IDatabaseContext, Task> operation)
|
||||
{
|
||||
using (var scope = serviceProvider.CreateScope())
|
||||
await operation(scope.ServiceProvider.GetRequiredService<IDatabaseContext>()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for scoping usage of <see cref="IDatabaseContext"/>s. Meant for use by <see cref="Components"/>
|
||||
/// </summary>
|
||||
interface IDatabaseContextFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Run an <paramref name="operation"/> in the scope of an <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="operation">The operation to run</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running <paramref name="operation"/></returns>
|
||||
Task UseContext(Func<IDatabaseContext, Task> operation);
|
||||
}
|
||||
}
|
||||
@@ -20,18 +20,13 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <returns>A <see cref="Task"/> representing a running operation</returns>
|
||||
Task RegisterOperation(Job job, Func<Job, IServiceProvider, CancellationToken, Task> operation, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Wait for a given <paramref name="job"/> to complete
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/> to wait for</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing a running operation</returns>
|
||||
Task WaitForJob(Job job, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Cancels a give <paramref name="job"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/> to cancel</param>
|
||||
void CancelJob(Job job);
|
||||
/// <param name="user">The <see cref="User"/> who cancelled the <paramref name="job"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing a running operation</returns>
|
||||
Task CancelJob(Job job, User user, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,33 +55,45 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task RunJob(Job job, Func<Job, IServiceProvider, CancellationToken, Task> 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<IDatabaseContext>();
|
||||
databaseContext.Jobs.Attach(job);
|
||||
}
|
||||
}
|
||||
finally
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
databaseContext = scope.ServiceProvider.GetRequiredService<IDatabaseContext>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,16 +147,18 @@ namespace Tgstation.Server.Host.Core
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task WaitForJob(Job job, CancellationToken cancellationToken)
|
||||
public async Task CancelJob(Job job, User user, CancellationToken cancellationToken)
|
||||
{
|
||||
var handler = CheckGetJob(job);
|
||||
await handler.Wait(cancellationToken).ConfigureAwait(false);
|
||||
lock (this)
|
||||
jobs.Remove(job.Id);
|
||||
handler.Dispose();
|
||||
if (user != null)
|
||||
using (var scope = serviceProvider.CreateScope())
|
||||
{
|
||||
var databaseContext = scope.ServiceProvider.GetRequiredService<IDatabaseContext>();
|
||||
job = new Job { Id = job.Id };
|
||||
databaseContext.Jobs.Attach(job);
|
||||
job.CancelledBy = user;
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
CheckGetJob(job).Cancel();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CancelJob(Job job) => CheckGetJob(job).Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class CompileJob : Api.Models.Internal.CompileJob
|
||||
{
|
||||
public sealed class CompileJob : Api.Models.Internal.CompileJob, IApiConvertable<Api.Models.CompileJob>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Internal.Job.Id"/> of <see cref="Job"/>
|
||||
/// </summary>
|
||||
@@ -21,5 +22,18 @@ namespace Tgstation.Server.Host.Models
|
||||
/// </summary>
|
||||
[Required]
|
||||
public RevisionInformation RevisionInformation { get; set; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace Tgstation.Server.Host.Models
|
||||
/// <inheritdoc />
|
||||
public DbSet<RevisionInformation> RevisionInformations { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DbSet<DreamMakerSettings> DreamMakerSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DbSet{TEntity}"/> for <see cref="Log"/>s
|
||||
/// </summary>
|
||||
@@ -50,10 +53,6 @@ namespace Tgstation.Server.Host.Models
|
||||
/// </summary>
|
||||
public DbSet<DreamDaemonSettings> DreamDaemonSettings { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamMakerSettings"/> in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<DreamMakerSettings> DreamMakerSettings { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<Job> Jobs { get; set; }
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// For converting models to their API form
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">Which of the <see cref="Api.Models"/> this model backs</typeparam>
|
||||
public interface IApiConvertable<TModel> where TModel : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert the model to it's API form
|
||||
/// </summary>
|
||||
/// <returns>A new <typeparamref name="TModel"/></returns>
|
||||
TModel ToApi();
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,11 @@ namespace Tgstation.Server.Host.Models
|
||||
/// </summary>
|
||||
DbSet<RevisionInformation> RevisionInformations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DreamMakerSettings"/> in the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
DbSet<DreamMakerSettings> DreamMakerSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ServerSettings"/> in the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="InstanceUser"/> has any instance rights
|
||||
/// </summary>
|
||||
public bool AnyRights => ByondRights != ByondRights.None ||
|
||||
ChatSettingsRights != ChatSettingsRights.None ||
|
||||
ConfigurationRights != ConfigurationRights.None ||
|
||||
DreamDaemonRights != DreamDaemonRights.None ||
|
||||
DreamMakerRights != DreamMakerRights.None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,37 @@
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class Job : Api.Models.Internal.Job
|
||||
public sealed class Job : Api.Models.Internal.Job, IApiConvertable<Api.Models.Job>
|
||||
{
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.Job.StartedBy"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public User StartedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.Job.CancelledBy"/>
|
||||
/// </summary>
|
||||
public User CancelledBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> the job belongs to if any
|
||||
/// </summary>
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
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()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation
|
||||
public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation, IApiConvertable<Api.Models.RevisionInformation>
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
@@ -14,5 +15,13 @@ namespace Tgstation.Server.Host.Models
|
||||
/// See <see cref="Api.Models.RevisionInformation.TestMerges"/>
|
||||
/// </summary>
|
||||
public List<TestMerge> TestMerges { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Api.Models.RevisionInformation ToApi() => new Api.Models.RevisionInformation
|
||||
{
|
||||
Commit = Commit,
|
||||
OriginRevision = OriginRevision,
|
||||
TestMerges = TestMerges.Select(x => x.ToApi()).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class TestMerge : Api.Models.Internal.TestMerge
|
||||
public sealed class TestMerge : Api.Models.Internal.TestMerge, IApiConvertable<Api.Models.TestMerge>
|
||||
{
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.TestMerge.MergedBy"/>
|
||||
@@ -15,5 +16,20 @@ namespace Tgstation.Server.Host.Models
|
||||
/// The <see cref="Models.RevisionInformation"/> for the <see cref="TestMerge"/>
|
||||
/// </summary>
|
||||
public RevisionInformation RevisionInformation { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class User : Api.Models.Internal.User
|
||||
public sealed class User : Api.Models.Internal.User, IApiConvertable<Api.Models.User>
|
||||
{
|
||||
/// <summary>
|
||||
/// The hash of the user's password
|
||||
@@ -24,5 +25,25 @@ namespace Tgstation.Server.Host.Models
|
||||
/// The <see cref="InstanceUser"/>s for the <see cref="User"/>
|
||||
/// </summary>
|
||||
public List<InstanceUser> InstanceUsers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="ToApi()"/>
|
||||
/// </summary>
|
||||
/// <param name="recursive">If we should recurse on <see cref="CreatedBy"/></param>
|
||||
/// <returns>A new <see cref="Api.Models.User"/></returns>
|
||||
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
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public Api.Models.User ToApi() => ToApi(true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user