mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-29 08:00:19 +01:00
Various things:
- Class design tips in CONTRIBUTING - Finish Instance manager and chat documentation - Fix Task.WhenAny cancellation gotcha - Add PullRequestsCommand - Fix JSON "Required" fields - Fix chat bot Hi! shennanigans
This commit is contained in:
@@ -61,6 +61,28 @@ Instead you can use object orientation, or simply placing repeated code in a fun
|
||||
### No magic numbers or strings
|
||||
This means stuff like having a "mode" variable for an object set to "1" or "2" with no clear indicator of what that means. Make these `const string`s with a name that more clearly states what it's for. This is clearer and enhances readability of your code! Get used to doing it!
|
||||
|
||||
### Class Design Guidelines
|
||||
|
||||
DO:
|
||||
|
||||
- Use the sealed keyword where possible
|
||||
- Use the readonly keyword where possible
|
||||
- Use 1 line bodies (void X() => Y();) where possible (Excluding constructors)
|
||||
- Use the factory pattern where reasonable
|
||||
- Use the const keyword where possible
|
||||
- Use the var keyword where possible
|
||||
- Use the static keyword on member functions where possible
|
||||
- Use CancellationTokens where possible
|
||||
- Throw appropriate ArgumentExceptions for public functions
|
||||
|
||||
DON'T:
|
||||
|
||||
- Use the private keyword
|
||||
- Use the internal keyword
|
||||
- Use the static keyword on fields where avoidable
|
||||
- Use the public keyword where avoidable
|
||||
- Handle Tasks in a synchronous fashion
|
||||
|
||||
### Versioning
|
||||
|
||||
The version format we use is 4.\<major\>.\<minor\>.\<patch\>. The first number never changes and TGS 1/2/3/4 are to be considered seperate products. The numbers that follow are the semver. The criteria for changing a version number is as follows
|
||||
|
||||
+40
-1
@@ -178,6 +178,45 @@ Instances are DreamDaemon server configurations, they live in their own director
|
||||
|
||||
PUT "/Instance" @ref Tgstation.Server.Api.Models.Instance => @ref Tgstation.Server.Api.Models.Instance
|
||||
|
||||
The path
|
||||
The user that creates an instance will be given full @ref Tgstation.Server.Api.Models.InstanceUser permission. The path must not exist at the time of creation. Support for attaching instances from backups is yet to come.
|
||||
|
||||
A specific Instance may be retrieved with:
|
||||
|
||||
GET "/Instances/{InstanceID}" => @ref Tgstation.Server.Api.Models.Instance
|
||||
|
||||
Instances start offline, regardless of what was specified during the create request. An offline instance will return 403 for all requests made to it.
|
||||
|
||||
To online or change other instance variables use the following request. Note that using this request (even with an empty object) will automatically give you the @ref Tgstation.Server.Api.Rights.InstanceUserRights.WriteUsers right for that instance if you don't have it
|
||||
|
||||
POST "/Instance" @ref Tgstation.Server.Api.Models.Instance => @ref Tgstation.Server.Api.Models.Instance
|
||||
|
||||
Note that onlining an offline instance will never automatically start DreamDaemon. That must be done as a seperate step.
|
||||
|
||||
Instances can be detached which will delete all meta knowledge of the instance (Compile metadata, job metadata, repository commit metadata, Test merge metadata, etc...) but leave the files intact. That can be done with this request:
|
||||
|
||||
DELETE "/Instance/{InstanceId}" => OK
|
||||
|
||||
@subsection api_chat Chat Bots
|
||||
|
||||
Each chat bot is represented by a @ref Tgstation.Server.Api.Models.ChatSettings object
|
||||
|
||||
Chat bots can be created/updated/deleted with the following requests respectively
|
||||
|
||||
I PUT "/Chat" @ref Tgstation.Server.Api.Models.ChatSettings => Tgstation.Server.Api.Models.ChatSettings
|
||||
I POST "/Chat" @ref Tgstation.Server.Api.Models.ChatSettings => Tgstation.Server.Api.Models.ChatSettings
|
||||
I DELETE "/Chat/{ChatSettingsId}" => OK
|
||||
|
||||
The @ref Tgstation.Server.Api.Models.Internal.ChatSettings.ConnectionString must differ based on what kind of chat bot you wish to create
|
||||
|
||||
For IRC chat bots it should be in the following format:
|
||||
`"<Server URL or IP address>;<Server Port>;<Bot nickname>;<1 to use SSL, 0 otherwise>[;<`The @ref Tgstation.Server.Api.Models.IrcPasswordType`;<The password>]"`
|
||||
|
||||
For Discord chat bots it should be the <a href="https://discordapp.com/developers/docs/topics/oauth2#bots">bot's Token</a>
|
||||
|
||||
A specific bot's settings may be retrieved with:
|
||||
|
||||
I GET "/Chat/{ChatSettingsId}" => @ref Tgstation.Server.Api.Models.ChatSettings
|
||||
|
||||
Also note that if the @ref Tgstation.Server.Api.Models.ChatSettings.Channels is present in a POST request, the list will fully replace any active channels
|
||||
|
||||
*/
|
||||
|
||||
@@ -100,6 +100,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
using (cancellationToken.Register(() => ourTcs.SetCanceled()))
|
||||
{
|
||||
await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return;
|
||||
}
|
||||
try
|
||||
|
||||
@@ -28,6 +28,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICommandFactory"/> for the <see cref="Chat"/>
|
||||
/// </summary>
|
||||
readonly ICommandFactory commandFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="Chat"/>
|
||||
/// </summary>
|
||||
@@ -94,19 +99,17 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="commandFactory">The <see cref="ICommandFactory"/> used to populate <see cref="builtinCommands"/></param>
|
||||
/// <param name="commandFactory">The value of <see cref="commandFactory"/></param>
|
||||
/// <param name="initialChatSettings">The <see cref="IEnumerable{T}"/> used to populate <see cref="initialChatSettings"/></param>
|
||||
public Chat(IProviderFactory providerFactory, IIOManager ioManager, ILogger<Chat> logger, ICommandFactory commandFactory, IEnumerable<Models.ChatSettings> initialChatSettings)
|
||||
public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, ILogger<Chat> logger, IEnumerable<Models.ChatSettings> initialChatSettings)
|
||||
{
|
||||
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.initialChatSettings = initialChatSettings?.ToList() ?? throw new ArgumentNullException(nameof(initialChatSettings));
|
||||
|
||||
builtinCommands = new Dictionary<string, ICommand>();
|
||||
foreach (var I in commandFactory?.GenerateCommands() ?? throw new ArgumentNullException(nameof(commandFactory)))
|
||||
builtinCommands.Add(I.Name.ToUpperInvariant(), I);
|
||||
|
||||
providers = new Dictionary<long, IProvider>();
|
||||
mappedChannels = new Dictionary<ulong, ChannelMapping>();
|
||||
trackingContexts = new List<IJsonTrackingContext>();
|
||||
@@ -189,7 +192,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
message.User.Channel.RealId = enumerable.First().Key;
|
||||
}
|
||||
|
||||
var splits = new List<string>(message.Content.TrimEnd().Split(' '));
|
||||
var splits = new List<string>(message.Content.Trim().Split(' '));
|
||||
var address = splits[0];
|
||||
if (address.Length > 1 && (address[address.Length - 1] == ':' || address[address.Length - 1] == ','))
|
||||
address = address.Substring(0, address.Length - 1);
|
||||
@@ -205,7 +208,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
if (addressed)
|
||||
splits.RemoveAt(0);
|
||||
|
||||
if ((splits.Count == 1 && (!message.User.Channel.IsPrivate || splits[0].Length == 0)) || splits.Count == 0)
|
||||
if (splits.Count == 0 || (!addressed && splits.Count == 1))
|
||||
{
|
||||
//just a mention
|
||||
await SendMessage("Hi!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
|
||||
@@ -461,6 +464,8 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var I in commandFactory.GenerateCommands())
|
||||
builtinCommands.Add(I.Name.ToUpperInvariant(), I);
|
||||
await Task.WhenAll(initialChatSettings.Select(x => ChangeSettings(x, cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(initialChatSettings.Select(x => ChangeChannels(x.Id, x.Channels, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
@@ -45,6 +45,6 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChat CreateChat(IEnumerable<Models.ChatSettings> initialChatSettings) => new Chat(providerFactory, ioManager, loggerFactory.CreateLogger<Chat>(), commandFactory, initialChatSettings);
|
||||
public IChat CreateChat(IEnumerable<Models.ChatSettings> initialChatSettings) => new Chat(providerFactory, ioManager, commandFactory, loggerFactory.CreateLogger<Chat>(), initialChatSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
@@ -18,23 +20,66 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
/// </summary>
|
||||
readonly IByondManager byondManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IRepositoryManager"/> for the <see cref="CommandFactory"/>
|
||||
/// </summary>
|
||||
readonly IRepositoryManager repositoryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="CommandFactory"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance"/> for the <see cref="CommandFactory"/>
|
||||
/// </summary>
|
||||
readonly Models.Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for the <see cref="CommandFactory"/>
|
||||
/// </summary>
|
||||
IWatchdog watchdog;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="CommandFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="application">The value of <see cref="application"/></param>
|
||||
/// <param name="byondManager">The value of <see cref="byondManager"/></param>
|
||||
public CommandFactory(IApplication application, IByondManager byondManager)
|
||||
/// <param name="repositoryManager">The value of <see cref="repositoryManager"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public CommandFactory(IApplication application, IByondManager byondManager, IRepositoryManager repositoryManager, IDatabaseContextFactory databaseContextFactory, Models.Instance instance)
|
||||
{
|
||||
this.application = application ?? throw new ArgumentNullException(nameof(application));
|
||||
this.byondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager));
|
||||
this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a <paramref name="watchdog"/> for the <see cref="CommandFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="watchdog">The <see cref="IWatchdog"/> to set</param>
|
||||
public void SetWatchdog(IWatchdog watchdog)
|
||||
{
|
||||
if (this.watchdog != null)
|
||||
throw new InvalidOperationException("SetWatchdog has already been called!");
|
||||
this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ICommand> GenerateCommands() => new List<ICommand>
|
||||
public IReadOnlyList<ICommand> GenerateCommands()
|
||||
{
|
||||
new VersionCommand(application),
|
||||
new ByondCommand(byondManager),
|
||||
new KekCommand()
|
||||
};
|
||||
if (watchdog == null)
|
||||
throw new InvalidOperationException("SetWatchdog has not been called!");
|
||||
return new List<ICommand>
|
||||
{
|
||||
new VersionCommand(application),
|
||||
new ByondCommand(byondManager),
|
||||
new PullRequestsCommand(watchdog, repositoryManager, databaseContextFactory, instance),
|
||||
new KekCommand()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Command for reading the active <see cref="Api.Models.TestMerge"/>s
|
||||
/// </summary>
|
||||
sealed class PullRequestsCommand : ICommand
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "prs";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string HelpText => "Display live test merge pull request numbers. Add --repo to view repository test merges";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AdminOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for the <see cref="PullRequestsCommand"/>
|
||||
/// </summary>
|
||||
readonly IWatchdog watchdog;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IRepositoryManager"/> for the <see cref="PullRequestsCommand"/>
|
||||
/// </summary>
|
||||
readonly IRepositoryManager repositoryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="PullRequestsCommand"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance"/> for the <see cref="PullRequestsCommand"/>
|
||||
/// </summary>
|
||||
readonly Models.Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="PullRequestsCommand"/>
|
||||
/// </summary>
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/></param>
|
||||
/// <param name="repositoryManager">The value of <see cref="repositoryManager"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public PullRequestsCommand(IWatchdog watchdog, IRepositoryManager repositoryManager, IDatabaseContextFactory databaseContextFactory, Models.Instance instance)
|
||||
{
|
||||
this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> Invoke(string arguments, User user, CancellationToken cancellationToken)
|
||||
{
|
||||
IEnumerable<Models.TestMerge> results = null;
|
||||
if (arguments.Split(' ').Any(x => x.ToUpperInvariant() == "--REPO"))
|
||||
{
|
||||
string head;
|
||||
using (var repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (repo == null)
|
||||
return "Repository unavailable!";
|
||||
head = repo.Head;
|
||||
}
|
||||
await databaseContextFactory.UseContext(async db => results = await db.RevisionInformations.Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
|
||||
.SelectMany(x => x.ActiveTestMerges)
|
||||
.Select(x => x.TestMerge)
|
||||
.Select(x => new Models.TestMerge
|
||||
{
|
||||
Number = x.Number,
|
||||
PullRequestRevision = x.PullRequestRevision
|
||||
}).ToListAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!watchdog.Running)
|
||||
return "Server offline!";
|
||||
results = watchdog.ActiveCompileJob?.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList() ?? new List<Models.TestMerge>();
|
||||
}
|
||||
|
||||
if (!results.Any())
|
||||
return "None!";
|
||||
|
||||
return String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} as {1}", x.Number, x.PullRequestRevision.Substring(0, 7))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// </summary>
|
||||
Task listenTask;
|
||||
|
||||
/// <summary>
|
||||
/// If we are disconnecting
|
||||
/// </summary>
|
||||
bool disconnecting;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="IrcProvider"/>
|
||||
/// </summary>
|
||||
@@ -130,13 +135,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
channelIdMap = new Dictionary<ulong, string>();
|
||||
queryChannelIdMap = new Dictionary<ulong, string>();
|
||||
channelIdCounter = 1;
|
||||
disconnecting = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dispose()
|
||||
{
|
||||
if(Connected)
|
||||
if (Connected)
|
||||
{
|
||||
disconnecting = true;
|
||||
client.Disconnect(); //just closes the socket
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -209,6 +218,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <inheritdoc />
|
||||
public override Task<bool> Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
disconnecting = false;
|
||||
lock (this)
|
||||
try
|
||||
{
|
||||
@@ -276,9 +286,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
|
||||
listenTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
while (client.IsConnected)
|
||||
while (!disconnecting && client.IsConnected)
|
||||
{
|
||||
client.ListenOnce(true);
|
||||
if (disconnecting || !client.IsConnected)
|
||||
break;
|
||||
client.Listen(false);
|
||||
//ensure we have the correct nick
|
||||
if (client.Nickname != nickname && client.GetIrcUser(nickname) == null)
|
||||
@@ -304,7 +316,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
try
|
||||
{
|
||||
client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); //priocritical otherwise Disconnect will hard block
|
||||
client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); //priocritical otherwise it wont go through
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
var cancelTcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
|
||||
await Task.WhenAny(nextMessage.Task, cancelTcs.Task).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
lock (messageQueue)
|
||||
{
|
||||
var result = messageQueue.Dequeue();
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
var timeoutTask = Task.Delay(timeoutAt - DateTimeOffset.Now, cancellationToken);
|
||||
|
||||
await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (!controller.Lifetime.IsCompleted)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -57,11 +57,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly IExecutor executor;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICommandFactory"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
readonly ICommandFactory commandFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISynchronousIOManager"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
@@ -98,13 +93,12 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="serverUpdater">The value of <see cref="serverUpdater"/></param>
|
||||
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
|
||||
/// <param name="executor">The value of <see cref="executor"/></param>
|
||||
/// <param name="commandFactory">The value of <see cref="commandFactory"/></param>
|
||||
/// <param name="synchronousIOManager">The value of <see cref="synchronousIOManager"/></param>
|
||||
/// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/></param>
|
||||
/// <param name="byondInstaller">The value of <see cref="byondInstaller"/></param>
|
||||
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
|
||||
/// <param name="scriptExecutor">The value of <see cref="scriptExecutor"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ICommandFactory commandFactory, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor)
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, IExecutor executor, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IScriptExecutor scriptExecutor)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
@@ -114,7 +108,6 @@ namespace Tgstation.Server.Host.Components
|
||||
this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater));
|
||||
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite ));
|
||||
this.executor = executor ?? throw new ArgumentNullException(nameof(executor));
|
||||
this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
|
||||
this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager));
|
||||
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
|
||||
this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller));
|
||||
@@ -145,7 +138,7 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger<ByondManager>());
|
||||
|
||||
var commandFactory = new CommandFactory(application, byond);
|
||||
var commandFactory = new CommandFactory(application, byond, repoManager, databaseContextFactory, metadata);
|
||||
var chatFactory = new ChatFactory(instanceIoManager, loggerFactory, commandFactory, providerFactory);
|
||||
|
||||
var chat = chatFactory.CreateChat(metadata.ChatSettings);
|
||||
@@ -156,6 +149,7 @@ namespace Tgstation.Server.Host.Components
|
||||
var watchdogFactory = new WatchdogFactory(chat, sessionControllerFactory, serverUpdater, loggerFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, metadata.CloneMetadata());
|
||||
var watchdog = watchdogFactory.CreateWatchdog(dmbFactory, metadata.DreamDaemonSettings);
|
||||
eventConsumer.SetWatchdog(watchdog);
|
||||
commandFactory.SetWatchdog(watchdog);
|
||||
try
|
||||
{
|
||||
var dreamMaker = new DreamMaker(byond, ioManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, loggerFactory.CreateLogger<DreamMaker>());
|
||||
|
||||
@@ -21,6 +21,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
bool AlphaIsActive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/> currently running on the server
|
||||
/// </summary>
|
||||
Models.CompileJob ActiveCompileJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The latest <see cref="LaunchResult"/> of the twin servers
|
||||
/// </summary>
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
public bool AlphaIsActive { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Models.CompileJob ActiveCompileJob => (AlphaIsActive ? alphaServer : bravoServer)?.Dmb.CompileJob;
|
||||
|
||||
/// <inheritdoc />
|
||||
public LaunchResult LastLaunchResult { get; private set; }
|
||||
|
||||
@@ -494,6 +497,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (monitorState.RebootingInactiveServer)
|
||||
toWaitOn = Task.WhenAny(toWaitOn, inactiveServerStartup);
|
||||
await toWaitOn.ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
var chatTask = Task.CompletedTask;
|
||||
@@ -680,6 +684,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var cancelTcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
|
||||
await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
//update the live and staged jobs in the db
|
||||
await databaseContextFactory.UseContext(async db =>
|
||||
|
||||
@@ -170,9 +170,22 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if(ModelState?.IsValid == false)
|
||||
{
|
||||
var errorMessages = ModelState.SelectMany(x => x.Value.Errors).Select(x => x.ErrorMessage);
|
||||
await BadRequest(new ErrorMessage { Message = String.Join(Environment.NewLine, errorMessages) }).ExecuteResultAsync(context).ConfigureAwait(false);
|
||||
return;
|
||||
var errorMessages = ModelState.SelectMany(x => x.Value.Errors).Select(x => x.ErrorMessage).ToList();
|
||||
//do some fuckery to remove RequiredAttribute errors
|
||||
for (var I = 0; I < errorMessages.Count; ++I)
|
||||
{
|
||||
var message = errorMessages[I];
|
||||
if (message.StartsWith("The ", StringComparison.Ordinal) && message.EndsWith(" field is required.", StringComparison.Ordinal))
|
||||
{
|
||||
errorMessages.RemoveAt(I);
|
||||
--I;
|
||||
}
|
||||
}
|
||||
if (errorMessages.Count > 0)
|
||||
{
|
||||
await BadRequest(new ErrorMessage { Message = String.Join(Environment.NewLine, errorMessages) }).ExecuteResultAsync(context).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInformation("Request made by User ID {0}. Api version: {1}. User-Agent: {2}", AuthenticationContext?.User.Id.ToString(CultureInfo.InvariantCulture) ?? "NULL", ApiHeaders.ApiVersion, ApiHeaders.UserAgent);
|
||||
|
||||
@@ -12,7 +12,6 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
using Octokit;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
@@ -23,7 +22,6 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Chat.Commands;
|
||||
using Tgstation.Server.Host.Components.StaticFiles;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
@@ -151,6 +149,7 @@ namespace Tgstation.Server.Host.Core
|
||||
options.AllowInputFormatterExceptionMessages = true;
|
||||
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
||||
options.SerializerSettings.CheckAdditionalContent = true;
|
||||
options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
|
||||
});
|
||||
|
||||
var databaseConfiguration = databaseConfigurationSection.Get<DatabaseConfiguration>();
|
||||
@@ -205,7 +204,6 @@ namespace Tgstation.Server.Host.Core
|
||||
}
|
||||
|
||||
services.AddSingleton<IExecutor, Executor>();
|
||||
services.AddSingleton<ICommandFactory, CommandFactory>();
|
||||
services.AddSingleton<IScriptExecutor, ScriptExecutor>();
|
||||
services.AddSingleton<IProviderFactory, ProviderFactory>();
|
||||
services.AddSingleton<IByondTopicSender>(new ByondTopicSender
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Tgstation.Server.Host.Core
|
||||
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => tcs.SetCanceled()))
|
||||
await Task.WhenAny(tcs.Task, task).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user