diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml
index 1efe65423b..483a030d50 100644
--- a/.github/workflows/ci-pipeline.yml
+++ b/.github/workflows/ci-pipeline.yml
@@ -203,7 +203,7 @@ jobs:
- name: Create TGS Deployment
run: |
cd $HOME/OpenDream
- dotnet run -c Release --project OpenDreamPackageTool -- --tgs -o tgs_deploy
+ dotnet run -c Release --project OpenDreamPackageTool --property WarningLevel=0 -- --tgs -o tgs_deploy
- name: Build DMAPI
run: |
diff --git a/src/Tgstation.Server.Api/Models/EngineVersion.cs b/src/Tgstation.Server.Api/Models/EngineVersion.cs
index d4681faa60..adaa90bcbf 100644
--- a/src/Tgstation.Server.Api/Models/EngineVersion.cs
+++ b/src/Tgstation.Server.Api/Models/EngineVersion.cs
@@ -38,7 +38,7 @@ namespace Tgstation.Server.Api.Models
public int? CustomIteration { get; set; }
///
- /// Parses a stringified .
+ /// Attempts to parse a stringified .
///
/// The input .
/// The output .
@@ -110,6 +110,23 @@ namespace Tgstation.Server.Api.Models
return true;
}
+ ///
+ /// Parses a stringified .
+ ///
+ /// The input .
+ /// The output .
+ /// If the is not a valid stringified .
+ public static EngineVersion Parse(string input)
+ {
+ if (input == null)
+ throw new ArgumentNullException(nameof(input));
+
+ if (TryParse(input, out var engineVersion))
+ return engineVersion!;
+
+ throw new InvalidOperationException($"Invalid engine version: {input}");
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs
index 886816346a..28f39a7638 100644
--- a/src/Tgstation.Server.Api/Models/ErrorCode.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs
@@ -150,10 +150,10 @@ namespace Tgstation.Server.Api.Models
InstanceLimitReached,
///
- /// Attempted to create an with a whitespace .
+ /// Attempted to create an with a whitespace or .
///
- [Description("Instance names cannot be whitespace!")]
- InstanceWhitespaceName,
+ [Description("Instance names and paths cannot be whitespace!")]
+ InstanceWhitespaceNameOrPath,
///
/// The header was required but not set.
diff --git a/src/Tgstation.Server.Api/Models/JobCode.cs b/src/Tgstation.Server.Api/Models/JobCode.cs
index e82600b302..be5db8d5a6 100644
--- a/src/Tgstation.Server.Api/Models/JobCode.cs
+++ b/src/Tgstation.Server.Api/Models/JobCode.cs
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Api.Models
///
/// When the instance is being moved.
///
- [Description("Instance move")]
+ [Description("Move instance")]
Move,
///
diff --git a/src/Tgstation.Server.Api/Models/Response/ServerUpdateResponse.cs b/src/Tgstation.Server.Api/Models/Response/ServerUpdateResponse.cs
index 3578e6cd0a..48c9b99e6a 100644
--- a/src/Tgstation.Server.Api/Models/Response/ServerUpdateResponse.cs
+++ b/src/Tgstation.Server.Api/Models/Response/ServerUpdateResponse.cs
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Api.Models.Response
/// The value of .
/// The optional value of .
[JsonConstructor]
- public ServerUpdateResponse(Version newVersion, string fileTicket)
+ public ServerUpdateResponse(Version newVersion, string? fileTicket)
{
NewVersion = newVersion ?? throw new ArgumentNullException(nameof(newVersion));
FileTicket = fileTicket;
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs
index d7430d7c69..19581b09bb 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChannelMapping.cs
@@ -1,4 +1,6 @@
-namespace Tgstation.Server.Host.Components.Chat
+using System;
+
+namespace Tgstation.Server.Host.Components.Chat
{
///
/// Represents a mapping of a .
@@ -38,6 +40,15 @@
///
/// The with the mapped Id.
///
- public ChannelRepresentation Channel { get; set; }
+ public ChannelRepresentation Channel { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ public ChannelMapping(ChannelRepresentation channel)
+ {
+ Channel = channel ?? throw new ArgumentNullException(nameof(channel));
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChannelRepresentation.cs b/src/Tgstation.Server.Host/Components/Chat/ChannelRepresentation.cs
index a24f416ec1..b1d4aa3ad2 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChannelRepresentation.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChannelRepresentation.cs
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Components.Chat
///
/// Backing field for . Represented as a to avoid BYOND percision loss.
///
- public string Id { get; set; }
+ public string Id { get; private set; }
///
/// The channel Id.
@@ -30,12 +30,12 @@ namespace Tgstation.Server.Host.Components.Chat
///
/// The user friendly name of the .
///
- public string FriendlyName { get; set; }
+ public string FriendlyName { get; }
///
/// The name of the connection the belongs to.
///
- public string ConnectionName { get; set; }
+ public string ConnectionName { get; }
///
/// If this is considered a channel for admin commands.
@@ -45,16 +45,30 @@ namespace Tgstation.Server.Host.Components.Chat
///
/// If this is a 1-to-1 chat channel.
///
- public bool IsPrivateChannel { get; set; }
+ public bool IsPrivateChannel { get; init; }
///
/// For user use.
///
- public string Tag { get; set; }
+ public string? Tag { get; set; }
///
/// If this channel supports embeds.
///
- public bool EmbedsSupported { get; set; }
+ public bool EmbedsSupported { get; init; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ /// The value of /.
+ public ChannelRepresentation(string connectionName, string friendlyName, ulong id)
+ {
+ ConnectionName = connectionName ?? throw new ArgumentNullException(nameof(connectionName));
+ FriendlyName = friendlyName ?? throw new ArgumentNullException(nameof(friendlyName));
+ Id = null!;
+ RealId = id;
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
index 7e3daa6646..d53e997b25 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
@@ -24,8 +24,7 @@ using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Chat
{
///
- // TODO: Decomplexify
-#pragma warning disable CA1506
+#pragma warning disable CA1506 // TODO: Decomplexify
sealed class ChatManager : IChatManager, IRestartHandler
{
///
@@ -69,7 +68,7 @@ namespace Tgstation.Server.Host.Components.Chat
readonly Dictionary providers;
///
- /// Map of s used to guard concurrent access to , keyed by .
+ /// Map of s used to guard concurrent access to , keyed by .
///
readonly ConcurrentDictionary changeChannelSemaphores;
@@ -101,17 +100,17 @@ namespace Tgstation.Server.Host.Components.Chat
///
/// The for the .
///
- ICustomCommandHandler customCommandHandler;
+ ICustomCommandHandler? customCommandHandler;
///
/// The that monitors incoming chat messages.
///
- Task chatHandler;
+ Task? chatHandler;
///
/// A that represents the s initial connection.
///
- Task initialProviderConnectionsTask;
+ Task? initialProviderConnectionsTask;
///
/// A that represents all sent messages.
@@ -234,7 +233,7 @@ namespace Tgstation.Server.Host.Components.Chat
var newMappings = results.SelectMany(
kvp => kvp.Value.Select(
- channelRepresentation => new ChannelMapping
+ channelRepresentation => new ChannelMapping(channelRepresentation)
{
IsWatchdogChannel = kvp.Key.IsWatchdogChannel == true,
IsUpdatesChannel = kvp.Key.IsUpdatesChannel == true,
@@ -242,7 +241,6 @@ namespace Tgstation.Server.Host.Components.Chat
IsSystemChannel = kvp.Key.IsSystemChannel == true,
ProviderChannelId = channelRepresentation.RealId,
ProviderId = connectionId,
- Channel = channelRepresentation,
}));
ulong baseId;
@@ -255,7 +253,7 @@ namespace Tgstation.Server.Host.Components.Chat
lock (mappedChannels)
{
lock (providers)
- if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) // aborted again
+ if (!providers.TryGetValue(connectionId, out var verify) || verify != provider) // aborted again
return;
foreach (var newMapping in newMappings)
{
@@ -269,7 +267,7 @@ namespace Tgstation.Server.Host.Components.Chat
// we only want to update contexts if everything at startup has connected once already
// otherwise we could send an incomplete channel set to the DMAPI, which will then spout all its queued messages into it instead of all relevant chatbots
// The watchdog can call this if it needs to after starting up
- if (initialProviderConnectionsTask.IsCompleted)
+ if (initialProviderConnectionsTask!.IsCompleted)
await UpdateTrackingContexts(cancellationToken);
}
finally
@@ -287,23 +285,25 @@ namespace Tgstation.Server.Host.Components.Chat
logger.LogTrace("ChangeSettings...");
Task disconnectTask;
- IProvider provider = null;
+ IProvider? provider = null;
+ var newSettingsId = Models.ModelExtensions.Require(newSettings, x => x.Id);
+ var newSettingsEnabled = Models.ModelExtensions.Require(newSettings, x => x.Enabled);
lock (providers)
{
// raw settings changes forces a rebuild of the provider
- if (providers.ContainsKey(newSettings.Id.Value))
- disconnectTask = DeleteConnection(newSettings.Id.Value, cancellationToken);
+ if (providers.ContainsKey(newSettingsId))
+ disconnectTask = DeleteConnection(newSettingsId, cancellationToken);
else
disconnectTask = Task.CompletedTask;
- if (newSettings.Enabled.Value)
+ if (newSettingsEnabled)
{
provider = providerFactory.CreateProvider(newSettings);
- providers.Add(newSettings.Id.Value, provider);
+ providers.Add(newSettingsId, provider);
}
}
lock (mappedChannels)
- foreach (var oldMappedChannelId in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key).ToList())
+ foreach (var oldMappedChannelId in mappedChannels.Where(x => x.Value.ProviderId == newSettingsId).Select(x => x.Key).ToList())
mappedChannels.Remove(oldMappedChannelId);
await disconnectTask;
@@ -317,8 +317,8 @@ namespace Tgstation.Server.Host.Components.Chat
}
var reconnectionUpdateTask = provider?.SetReconnectInterval(
- newSettings.ReconnectionInterval.Value,
- newSettings.Enabled.Value)
+ Models.ModelExtensions.Require(newSettings, x => x.ReconnectionInterval),
+ newSettingsEnabled)
?? Task.CompletedTask;
lock (activeChatBots)
{
@@ -326,7 +326,7 @@ namespace Tgstation.Server.Host.Components.Chat
if (originalChatBot != null)
activeChatBots.Remove(originalChatBot);
- activeChatBots.Add(new Models.ChatBot
+ activeChatBots.Add(new Models.ChatBot(newSettings.Channels)
{
Id = newSettings.Id,
ConnectionString = newSettings.ConnectionString,
@@ -334,7 +334,6 @@ namespace Tgstation.Server.Host.Components.Chat
Name = newSettings.Name,
ReconnectionInterval = newSettings.ReconnectionInterval,
Provider = newSettings.Provider,
- Channels = newSettings.Channels,
});
}
@@ -357,7 +356,7 @@ namespace Tgstation.Server.Host.Components.Chat
message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message);
- if (!initialProviderConnectionsTask.IsCompleted)
+ if (!initialProviderConnectionsTask!.IsCompleted)
logger.LogTrace("Waiting for initial provider connections before sending watchdog message...");
// Reimplementing QueueMessage
@@ -376,12 +375,12 @@ namespace Tgstation.Server.Host.Components.Chat
}
///
- public Func> QueueDeploymentMessage(
+ public Func> QueueDeploymentMessage(
Models.RevisionInformation revisionInformation,
EngineVersion engineVersion,
DateTimeOffset? estimatedCompletionTime,
- string gitHubOwner,
- string gitHubRepo,
+ string? gitHubOwner,
+ string? gitHubRepo,
bool localCommitPushed)
{
List wdChannels;
@@ -390,17 +389,17 @@ namespace Tgstation.Server.Host.Components.Chat
logger.LogTrace("Sending deployment message for RevisionInformation: {revisionInfoId}", revisionInformation.Id);
- var callbacks = new List>>>();
+ var callbacks = new List>>>();
var task = Task.WhenAll(
wdChannels.Select(
async x =>
{
- ChannelMapping channelMapping;
+ ChannelMapping? channelMapping;
lock (mappedChannels)
if (!mappedChannels.TryGetValue(x, out channelMapping))
return;
- IProvider provider;
+ IProvider? provider;
lock (providers)
if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
return;
@@ -431,8 +430,8 @@ namespace Tgstation.Server.Host.Components.Chat
AddMessageTask(task);
Task callbackTask;
- Func finalUpdateAction = null;
- async Task CallbackTask(string errorMessage, string dreamMakerOutput)
+ Func? finalUpdateAction = null;
+ async Task CallbackTask(string? errorMessage, string dreamMakerOutput)
{
await task;
var callbackResults = await ValueTaskExtensions.WhenAll(
@@ -457,7 +456,7 @@ namespace Tgstation.Server.Host.Components.Chat
return;
}
- AddMessageTask(finalUpdateAction(active));
+ AddMessageTask(finalUpdateAction!(active));
}
return (errorMessage, dreamMakerOutput) =>
@@ -495,7 +494,7 @@ namespace Tgstation.Server.Host.Components.Chat
if (customCommandHandler == null)
throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!");
- IChatTrackingContext context = null;
+ IChatTrackingContext context = null!;
lock (mappedChannels)
context = new ChatTrackingContext(
customCommandHandler,
@@ -524,7 +523,7 @@ namespace Tgstation.Server.Host.Components.Chat
await channelSink.UpdateChannels(channels, cancellationToken);
}
- var waitingForInitialConnection = !initialProviderConnectionsTask.IsCompleted;
+ var waitingForInitialConnection = !initialProviderConnectionsTask!.IsCompleted;
if (waitingForInitialConnection)
{
logger.LogTrace("Waiting for initial chat bot connections before updating tracking contexts...");
@@ -562,7 +561,7 @@ namespace Tgstation.Server.Host.Components.Chat
? semaphore
: null)
using (hasSemaphore
- ? await SemaphoreSlimContext.Lock(semaphore, cancellationToken)
+ ? await SemaphoreSlimContext.Lock(semaphore!, cancellationToken)
: null)
{
var provider = await RemoveProviderChannels(connectionId, true, cancellationToken);
@@ -589,7 +588,7 @@ namespace Tgstation.Server.Host.Components.Chat
}
///
- public ValueTask HandleRestart(Version updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
+ public ValueTask HandleRestart(Version? updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
{
var message = updateVersion == null
? $"TGS: {(handlerMayDelayShutdownWithExtremelyLongRunningTasks ? "Graceful shutdown" : "Going down")}..."
@@ -618,10 +617,10 @@ namespace Tgstation.Server.Host.Components.Chat
/// If the provider should be removed from and should be update.
/// The for the operation.
/// A resulting in the being removed if it exists, otherwise.
- async ValueTask RemoveProviderChannels(long connectionId, bool removeProvider, CancellationToken cancellationToken)
+ async ValueTask RemoveProviderChannels(long connectionId, bool removeProvider, CancellationToken cancellationToken)
{
logger.LogTrace("RemoveProviderChannels {connectionId}...", connectionId);
- IProvider provider;
+ IProvider? provider;
lock (providers)
{
if (!providers.TryGetValue(connectionId, out provider))
@@ -663,7 +662,7 @@ namespace Tgstation.Server.Host.Components.Chat
async ValueTask RemapProvider(IProvider provider, CancellationToken cancellationToken)
{
logger.LogTrace("Remapping channels for provider reconnection...");
- IEnumerable channelsToMap;
+ IEnumerable? channelsToMap;
long providerId;
lock (providers)
providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
@@ -684,7 +683,7 @@ namespace Tgstation.Server.Host.Components.Chat
/// The for the operation.
/// A representing the running operation.
#pragma warning disable CA1502
- async ValueTask ProcessMessage(IProvider provider, Message message, bool recursed, CancellationToken cancellationToken)
+ async ValueTask ProcessMessage(IProvider provider, Message? message, bool recursed, CancellationToken cancellationToken)
#pragma warning restore CA1502
{
if (!provider.Connected)
@@ -765,11 +764,10 @@ namespace Tgstation.Server.Host.Components.Chat
message.User.Channel.ConnectionName,
message.User.FriendlyName,
newId);
- mappedChannels.Add(newId, new ChannelMapping
+ mappedChannels.Add(newId, new ChannelMapping(message.User.Channel)
{
ProviderChannelId = message.User.Channel.RealId,
ProviderId = providerId,
- Channel = message.User.Channel,
});
logger.LogTrace(
@@ -799,7 +797,7 @@ namespace Tgstation.Server.Host.Components.Chat
var mappingChannelRepresentation = mappedChannel.Value.Value.Channel;
- message.User.Channel.Id = mappingChannelRepresentation.Id;
+ message.User.Channel.RealId = mappingChannelRepresentation.RealId;
message.User.Channel.Tag = mappingChannelRepresentation.Tag;
message.User.Channel.IsAdminChannel = mappingChannelRepresentation.IsAdminChannel;
}
@@ -813,11 +811,9 @@ namespace Tgstation.Server.Host.Components.Chat
if (address.Length > 1 && (address.Last() == ':' || address.Last() == ','))
address = address[0..^1];
- address = address.ToUpperInvariant();
-
var addressed =
- address == CommonMention.ToUpperInvariant()
- || address == provider.BotMention.ToUpperInvariant();
+ address.Equals(CommonMention, StringComparison.OrdinalIgnoreCase)
+ || address.Equals(provider.BotMention, StringComparison.OrdinalIgnoreCase);
// no mention
if (!addressed && !message.User.Channel.IsPrivateChannel)
@@ -843,16 +839,16 @@ namespace Tgstation.Server.Host.Components.Chat
splits.RemoveAt(0);
var arguments = String.Join(" ", splits);
- Tuple GetCommand()
+ Tuple? GetCommand()
{
if (!builtinCommands.TryGetValue(command, out var handler))
return trackingContexts
.Where(trackingContext => trackingContext.Active)
- .SelectMany(trackingContext => trackingContext.CustomCommands.Select(customCommand => Tuple.Create(customCommand, trackingContext)))
+ .SelectMany(trackingContext => trackingContext.CustomCommands.Select(customCommand => Tuple.Create(customCommand, trackingContext)))
.Where(tuple => tuple.Item1.Name.Equals(command, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
- return Tuple.Create(handler, null);
+ return Tuple.Create(handler, null);
}
const string UnknownCommandMessage = "TGS: Unknown command! Type '?' or 'help' for available commands.";
@@ -935,11 +931,11 @@ namespace Tgstation.Server.Host.Components.Chat
async Task MonitorMessages(CancellationToken cancellationToken)
{
logger.LogTrace("Starting processing loop...");
- var messageTasks = new Dictionary>();
+ var messageTasks = new Dictionary>();
ValueTask activeProcessingTask = ValueTask.CompletedTask;
try
{
- Task updatedTask = null;
+ Task? updatedTask = null;
while (!cancellationToken.IsCancellationRequested)
{
if (updatedTask?.IsCompleted != false)
@@ -1025,7 +1021,7 @@ namespace Tgstation.Server.Host.Components.Chat
/// The to send.
/// The for the operation.
/// A representing the running operation.
- ValueTask SendMessage(IEnumerable channelIds, Message replyTo, MessageContent message, CancellationToken cancellationToken)
+ ValueTask SendMessage(IEnumerable channelIds, Message? replyTo, MessageContent message, CancellationToken cancellationToken)
{
var channelIdsList = channelIds.ToList();
@@ -1035,17 +1031,17 @@ namespace Tgstation.Server.Host.Components.Chat
message.Embed != null ? " (with embed)" : String.Empty,
String.Join(", ", channelIdsList));
- if (!channelIdsList.Any())
+ if (channelIdsList.Count == 0)
return ValueTask.CompletedTask;
return ValueTaskExtensions.WhenAll(
channelIdsList.Select(x =>
{
- ChannelMapping channelMapping;
+ ChannelMapping? channelMapping;
lock (mappedChannels)
if (!mappedChannels.TryGetValue(x, out channelMapping))
return ValueTask.CompletedTask;
- IProvider provider;
+ IProvider? provider;
lock (providers)
if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
return ValueTask.CompletedTask;
@@ -1102,7 +1098,7 @@ namespace Tgstation.Server.Host.Components.Chat
{
var cancellationToken = handlerCts.Token;
if (waitForConnections)
- await initialProviderConnectionsTask.WaitAsync(cancellationToken);
+ await initialProviderConnectionsTask!.WaitAsync(cancellationToken);
await SendMessage(
channelIdsFactory(),
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManagerFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManagerFactory.cs
index 12427da6be..a1697a9711 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatManagerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatManagerFactory.cs
@@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
using Tgstation.Server.Host.Components.Chat.Commands;
using Tgstation.Server.Host.Components.Chat.Providers;
using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components.Chat
{
@@ -54,6 +55,6 @@ namespace Tgstation.Server.Host.Components.Chat
serverControl,
loggerFactory,
loggerFactory.CreateLogger(),
- initialChatBots.Where(x => x.Enabled.Value));
+ initialChatBots.Where(x => x.Require(y => y.Enabled)));
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs
index 03c9ee2e54..02b6675323 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatTrackingContext.cs
@@ -7,16 +7,17 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Host.Components.Chat.Commands;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Chat
{
///
- sealed class ChatTrackingContext : IChatTrackingContext
+ sealed class ChatTrackingContext : DisposeInvoker, IChatTrackingContext
{
///
public bool Active
{
- get => active && onDispose != null;
+ get => active && !IsDisposed;
set
{
if (active == value)
@@ -61,25 +62,20 @@ namespace Tgstation.Server.Host.Components.Chat
readonly ILogger logger;
///
- /// for modifying , , and .
+ /// for modifying and calling .
///
readonly object synchronizationLock;
+ ///
+ /// The if any.
+ ///
+ volatile IChannelSink? channelSink;
+
///
/// Backing field for .
///
IReadOnlyCollection customCommands;
- ///
- /// The if any.
- ///
- IChannelSink channelSink;
-
- ///
- /// The to run when d.
- ///
- Action onDispose;
-
///
/// Backing field for .
///
@@ -91,45 +87,31 @@ namespace Tgstation.Server.Host.Components.Chat
/// The value of .
/// The initial value of .
/// The value of .
- /// The value of .
+ /// The action for the .
public ChatTrackingContext(
ICustomCommandHandler customCommandHandler,
IEnumerable initialChannels,
ILogger logger,
- Action onDispose)
+ Action disposeAction)
+ : base(disposeAction)
{
this.customCommandHandler = customCommandHandler ?? throw new ArgumentNullException(nameof(customCommandHandler));
Channels = initialChannels?.ToList() ?? throw new ArgumentNullException(nameof(initialChannels));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
- this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
synchronizationLock = new object();
Active = true;
customCommands = Array.Empty();
}
- ///
- public void Dispose()
- {
- lock (synchronizationLock)
- {
- onDispose?.Invoke();
- onDispose = null;
- }
- }
-
///
public void SetChannelSink(IChannelSink channelSink)
{
ArgumentNullException.ThrowIfNull(channelSink);
- lock (synchronizationLock)
- {
- if (this.channelSink != null)
- throw new InvalidOperationException("channelSink already set!");
-
- this.channelSink = channelSink;
- }
+ var originalValue = Interlocked.CompareExchange(ref this.channelSink, channelSink, null);
+ if (originalValue != null)
+ throw new InvalidOperationException("channelSink already set!");
}
///
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatUser.cs b/src/Tgstation.Server.Host/Components/Chat/ChatUser.cs
index 6de07109ea..69757279b0 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatUser.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatUser.cs
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Components.Chat
///
/// Backing field for . Represented as a to avoid BYOND percision loss.
///
- public string Id { get; set; }
+ public string Id { get; private set; }
///
/// The internal user id.
@@ -21,23 +21,40 @@ namespace Tgstation.Server.Host.Components.Chat
[JsonIgnore]
public ulong RealId
{
- get => UInt64.Parse(Id, CultureInfo.InvariantCulture);
- set => Id = value.ToString(CultureInfo.InvariantCulture);
+ get => UInt64.Parse(Id!, CultureInfo.InvariantCulture);
+ private set => Id = value.ToString(CultureInfo.InvariantCulture);
}
///
/// The friendly name of the user.
///
- public string FriendlyName { get; set; }
+ public string FriendlyName { get; }
///
/// The text to mention the user.
///
- public string Mention { get; set; }
+ public string Mention { get; }
///
/// The the user spoke from.
///
- public ChannelRepresentation Channel { get; set; }
+ public ChannelRepresentation Channel { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ public ChatUser(ChannelRepresentation channel, string friendlyName, string mention, ulong realId)
+ {
+ Channel = channel ?? throw new ArgumentNullException(nameof(channel));
+ FriendlyName = friendlyName ?? throw new ArgumentNullException(nameof(friendlyName));
+ Mention = mention ?? throw new ArgumentNullException(nameof(mention));
+
+ Id = null!;
+ RealId = realId;
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs
index d4fda66603..a575ec9c87 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs
@@ -46,7 +46,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
///
/// The for the .
///
- IWatchdog watchdog;
+ IWatchdog? watchdog;
///
/// Initializes a new instance of the class.
diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs
index 617b1dba48..9492e925d7 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CustomCommand.cs
@@ -12,18 +12,31 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
public sealed class CustomCommand : ICommand
{
///
- public string Name { get; set; }
+ public string Name { get; }
///
- public string HelpText { get; set; }
+ public string HelpText { get; }
///
- public bool AdminOnly { get; set; }
+ public bool AdminOnly { get; }
///
/// The for the .
///
- ICustomCommandHandler handler;
+ ICustomCommandHandler? handler;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ public CustomCommand(string name, string helpText, bool adminOnly)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ HelpText = helpText ?? throw new ArgumentNullException(nameof(helpText));
+ AdminOnly = adminOnly;
+ }
///
/// Set a new .
diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/EngineCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/EngineCommand.cs
index 9e92f3f02f..c0bf80ea59 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Commands/EngineCommand.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Commands/EngineCommand.cs
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
///
public ValueTask Invoke(string arguments, ChatUser user, CancellationToken cancellationToken)
{
- EngineVersion engineVersion;
+ EngineVersion? engineVersion;
if (arguments.Split(' ').Any(x => x.Equals("--active", StringComparison.OrdinalIgnoreCase)))
engineVersion = engineManager.ActiveVersion;
else
@@ -67,8 +67,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
Text = "None!",
});
- if (!EngineVersion.TryParse(watchdog.ActiveCompileJob.EngineVersion, out engineVersion))
- throw new InvalidOperationException($"Invalid engine version: {watchdog.ActiveCompileJob.EngineVersion}");
+ engineVersion = EngineVersion.Parse(watchdog.ActiveCompileJob.EngineVersion);
}
string text;
@@ -76,10 +75,10 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
text = "None!";
else
{
- text = engineVersion.Engine.Value switch
+ text = engineVersion.Engine!.Value switch
{
EngineType.OpenDream => $"OpenDream: {engineVersion.SourceSHA}",
- EngineType.Byond => $"BYOND {engineVersion.Version.Major}.{engineVersion.Version.Minor}",
+ EngineType.Byond => $"BYOND {engineVersion.Version!.Major}.{engineVersion.Version.Minor}",
_ => throw new InvalidOperationException($"Invalid EngineType: {engineVersion.Engine.Value}"),
};
diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs
index 39a99bcac4..3fd8c9e64b 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs
@@ -81,7 +81,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
#pragma warning disable CA1506
public async ValueTask Invoke(string arguments, ChatUser user, CancellationToken cancellationToken)
{
- IEnumerable results = null;
+ IEnumerable results;
var splits = arguments.Split(' ');
var hasRepo = splits.Any(x => x.Equals("--repo", StringComparison.OrdinalIgnoreCase));
var hasStaged = splits.Any(x => x.Equals("--staged", StringComparison.OrdinalIgnoreCase));
@@ -112,12 +112,13 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
head = repo.Head;
}
+ results = null!;
await databaseContextFactory.UseContext(
async db => results = await db
.RevisionInformations
.AsQueryable()
- .Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
- .SelectMany(x => x.ActiveTestMerges)
+ .Where(x => x.Instance!.Id == instance.Id && x.CommitSha == head)
+ .SelectMany(x => x.ActiveTestMerges!)
.Select(x => x.TestMerge)
.Select(x => new Models.TestMerge
{
@@ -143,7 +144,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
compileJobToUse = null;
}
- results = compileJobToUse?.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList() ?? Enumerable.Empty();
+ results = compileJobToUse?.RevisionInformation.ActiveTestMerges?.Select(x => x.TestMerge).ToList() ?? Enumerable.Empty();
}
return new MessageContent
@@ -153,7 +154,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
: String.Join(
", ",
results.Select(
- x => $"#{x.Number} at {x.TargetCommitSha[..7]}")),
+ x => $"#{x.Number} at {x.TargetCommitSha![..7]}")),
};
}
#pragma warning restore CA1506
diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs
index aceff96435..39ed128d59 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs
@@ -75,7 +75,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
{
Text = "Server offline!",
};
- result = watchdog.ActiveCompileJob?.RevisionInformation.OriginCommitSha;
+ result = watchdog.ActiveCompileJob?.RevisionInformation.OriginCommitSha!;
}
return new MessageContent
diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs
index ae4aefb8fb..8a0aa33fe4 100644
--- a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs
@@ -67,12 +67,12 @@ namespace Tgstation.Server.Host.Components.Chat
/// The repository GitHub name, if any.
/// if the local deployment commit was pushed to the remote repository.
/// A to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. Returns an to call to mark the deployment as active/inactive. Parameter: If the deployment is being activated or inactivated.
- Func> QueueDeploymentMessage(
+ Func> QueueDeploymentMessage(
Models.RevisionInformation revisionInformation,
Api.Models.EngineVersion engineVersion,
DateTimeOffset? estimatedCompletionTime,
- string gitHubOwner,
- string gitHubRepo,
+ string? gitHubOwner,
+ string? gitHubRepo,
bool localCommitPushed);
///
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordMessage.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordMessage.cs
index b0b3fbb56e..565d78d654 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordMessage.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordMessage.cs
@@ -12,5 +12,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// The of the source .
///
public Optional MessageReference { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ public DiscordMessage(ChatUser user, string content, Optional messageReference)
+ : base(
+ user,
+ content)
+ {
+ MessageReference = messageReference;
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
index bda2228065..6f7ad59c5a 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
@@ -58,13 +58,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// The s supported by the for mapping.
///
- static readonly ChannelType[] SupportedGuildChannelTypes = new[]
- {
+ static readonly ChannelType[] SupportedGuildChannelTypes =
+ [
ChannelType.GuildText,
ChannelType.GuildAnnouncement,
ChannelType.PrivateThread,
ChannelType.PublicThread,
- };
+ ];
///
/// The for the .
@@ -104,17 +104,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// The for the .
///
- CancellationTokenSource gatewayCts;
+ CancellationTokenSource? gatewayCts;
///
/// The for the initial gateway connection event.
///
- TaskCompletionSource gatewayReadyTcs;
+ TaskCompletionSource? gatewayReadyTcs;
///
/// The representing the lifetime of the client.
///
- Task gatewayTask;
+ Task? gatewayTask;
///
/// The bot's .
@@ -157,8 +157,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
mappedChannels = new List();
connectDisconnectLock = new object();
- var csb = new DiscordConnectionStringBuilder(chatBot.ConnectionString);
- var botToken = csb.BotToken;
+ var csb = new DiscordConnectionStringBuilder(chatBot.ConnectionString!);
+ var botToken = csb.BotToken!;
outputDisplayType = csb.DMOutputDisplay;
deploymentBranding = csb.DeploymentBranding;
@@ -194,7 +194,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
///
- public override async ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken)
+ public override async ValueTask SendMessage(Message? replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(message);
@@ -218,6 +218,22 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var channelsClient = serviceProvider.GetRequiredService();
async ValueTask SendToChannel(Snowflake channelId)
{
+ if (message.Text == null)
+ {
+ Logger.LogWarning(
+ "Failed to send to channel {channelId}: Message was null!",
+ channelId);
+
+ await channelsClient.CreateMessageAsync(
+ channelId,
+ "TGS: Could not send message to Discord. Message was `null`!",
+ messageReference: replyToReference,
+ allowedMentions: allowedMentions,
+ ct: cancellationToken);
+
+ return;
+ }
+
var result = await channelsClient.CreateMessageAsync(
channelId,
message.Text,
@@ -285,25 +301,23 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
///
- public override async ValueTask>>> SendUpdateMessage(
+ public override async ValueTask>>> SendUpdateMessage(
Models.RevisionInformation revisionInformation,
EngineVersion engineVersion,
DateTimeOffset? estimatedCompletionTime,
- string gitHubOwner,
- string gitHubRepo,
+ string? gitHubOwner,
+ string? gitHubRepo,
ulong channelId,
bool localCommitPushed,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(revisionInformation);
ArgumentNullException.ThrowIfNull(engineVersion);
- ArgumentNullException.ThrowIfNull(gitHubOwner);
- ArgumentNullException.ThrowIfNull(gitHubRepo);
localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
var fields = BuildUpdateEmbedFields(revisionInformation, engineVersion, gitHubOwner, gitHubRepo, localCommitPushed);
- var author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix)
+ Optional author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix)
{
Url = "https://github.com/tgstation/tgstation-server",
IconUrl = "https://cdn.discordapp.com/attachments/1114451486374637629/1151650846019432448/tgs.png",
@@ -323,7 +337,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
Logger.LogTrace("Attempting to post deploy embed to channel {channelId}...", channelId);
var channelsClient = serviceProvider.GetRequiredService();
- var prefix = GetEngineCompilerPrefix(engineVersion.Engine.Value);
+ var prefix = GetEngineCompilerPrefix(engineVersion.Engine!.Value);
var messageResponse = await channelsClient.CreateMessageAsync(
new Snowflake(channelId),
$"{prefix}: Deployment in progress...",
@@ -386,7 +400,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var updatedMessageText = errorMessage == null ? $"{prefix}: Deployment pending reboot..." : $"{prefix}: Deployment failed!";
- IMessage updatedMessage = null;
+ IMessage? updatedMessage = null;
async ValueTask CreateUpdatedMessage()
{
var createUpdatedMessageResponse = await channelsClient.CreateMessageAsync(
@@ -532,27 +546,23 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
messageGuildResponse.LogFormat());
}
- var result = new DiscordMessage
- {
- MessageReference = messageReference,
- Content = content,
- User = new ChatUser
- {
- RealId = messageCreateEvent.Author.ID.Value,
- Channel = new ChannelRepresentation
+ var result = new DiscordMessage(
+ new ChatUser(
+ new ChannelRepresentation(
+ pm ? messageCreateEvent.Author.Username : guildName,
+ channelResponse.Entity.Name.Value!,
+ messageCreateEvent.ChannelID.Value)
{
- RealId = messageCreateEvent.ChannelID.Value,
IsPrivateChannel = pm,
- ConnectionName = pm ? messageCreateEvent.Author.Username : guildName,
- FriendlyName = channelResponse.Entity.Name.Value,
EmbedsSupported = true,
// isAdmin and Tag populated by manager
},
- FriendlyName = messageCreateEvent.Author.Username,
- Mention = NormalizeMentions($"<@{messageCreateEvent.Author.ID}>"),
- },
- };
+ messageCreateEvent.Author.Username,
+ NormalizeMentions($"<@{messageCreateEvent.Author.ID}>"),
+ messageCreateEvent.Author.ID.Value),
+ content,
+ messageReference);
EnqueueMessage(result);
return Result.FromSuccess();
@@ -635,8 +645,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
CancellationTokenSource localGatewayCts;
lock (connectDisconnectLock)
{
- localGatewayTask = gatewayTask;
- localGatewayCts = gatewayCts;
+ localGatewayTask = gatewayTask!;
+ localGatewayCts = gatewayCts!;
gatewayTask = null;
gatewayCts = null;
if (localGatewayTask == null)
@@ -662,7 +672,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var guildsClient = serviceProvider.GetRequiredService();
var guildTasks = new ConcurrentDictionary>>();
- async ValueTask>> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB)
+ async ValueTask>?> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB)
{
if (!channelFromDB.DiscordChannelId.HasValue)
throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
@@ -719,12 +729,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var connectionName = guildsResponse.Entity.Name;
- var channelModel = new ChannelRepresentation
+ var channelModel = new ChannelRepresentation(
+ guildsResponse.Entity.Name,
+ discordChannelResponse.Entity.Name.Value!,
+ channelId)
{
- RealId = channelId,
IsAdminChannel = channelFromDB.IsAdminChannel == true,
- ConnectionName = guildsResponse.Entity.Name,
- FriendlyName = discordChannelResponse.Entity.Name.Value,
IsPrivateChannel = false,
Tag = channelFromDB.Tag,
EmbedsSupported = true,
@@ -742,8 +752,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var channelTuples = await ValueTaskExtensions.WhenAll(tasks.ToList());
- var enumerator = channelTuples
+ var list = channelTuples
.Where(x => x != null)
+ .Cast>>() // NRT my beloathed
.ToList();
var channelIdZeroModel = channels.FirstOrDefault(x => x.DiscordChannelId == 0);
@@ -752,7 +763,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
Logger.LogInformation("Mapping ALL additional accessible text channels");
var allAccessibleChannels = await GetAllAccessibleTextChannels(cancellationToken);
var unmappedTextChannels = allAccessibleChannels
- .Where(x => !tasks.Any(task => task.Result != null && new Snowflake(task.Result.Item1.DiscordChannelId.Value) == x.ID));
+ .Where(x => !tasks.Any(task => task.Result != null && new Snowflake(task.Result.Item1.DiscordChannelId!.Value) == x.ID));
async ValueTask>> CreateMappingsForUnmappedChannels()
{
@@ -773,32 +784,36 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
.ToList();
// Add catch-all channel
- unmappedTasks.Add(Task.FromResult(
- new ChannelRepresentation
+ unmappedTasks.Add(Task.FromResult(
+ new ChannelRepresentation(
+ "(Unknown Discord Guilds)",
+ "(Unknown Discord Channels)",
+ 0)
{
- IsAdminChannel = channelIdZeroModel.IsAdminChannel.Value,
- ConnectionName = "(Unknown Discord Guilds)",
+ IsAdminChannel = channelIdZeroModel.IsAdminChannel!.Value,
EmbedsSupported = true,
- FriendlyName = "(Unknown Discord Channels)",
- RealId = 0,
Tag = channelIdZeroModel.Tag,
}));
await Task.WhenAll(unmappedTasks);
return Tuple.Create>(
channelIdZeroModel,
- unmappedTasks.Select(x => x.Result).Where(x => x != null).ToList());
+ unmappedTasks
+ .Select(x => x.Result)
+ .Where(x => x != null)
+ .Cast() // NRT my beloathed
+ .ToList());
}
var task = CreateMappingsForUnmappedChannels();
var tuple = await task;
- enumerator.Add(tuple);
+ list.Add(tuple);
}
lock (mappedChannels)
{
mappedChannels.Clear();
- mappedChannels.AddRange(enumerator.SelectMany(x => x.Item2).Select(x => x.RealId));
+ mappedChannels.AddRange(list.SelectMany(x => x.Item2).Select(x => x.RealId));
}
if (remapRequired)
@@ -807,7 +822,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
EnqueueMessage(null);
}
- return new Dictionary>(enumerator.Select(x => new KeyValuePair>(x.Item1, x.Item2)));
+ return new Dictionary>(list.Select(x => new KeyValuePair>(x.Item1, x.Item2)));
}
///
@@ -881,46 +896,55 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
List BuildUpdateEmbedFields(
Models.RevisionInformation revisionInformation,
EngineVersion engineVersion,
- string gitHubOwner,
- string gitHubRepo,
+ string? gitHubOwner,
+ string? gitHubRepo,
bool localCommitPushed)
{
bool gitHub = gitHubOwner != null && gitHubRepo != null;
- var engineField = engineVersion.Engine.Value switch
+ var engineField = engineVersion.Engine!.Value switch
{
EngineType.Byond => new EmbedField(
"BYOND Version",
- $"{engineVersion.Version.Major}.{engineVersion.Version.Minor}{(engineVersion.CustomIteration.HasValue ? $".{engineVersion.CustomIteration.Value}" : String.Empty)}",
+ $"{engineVersion.Version!.Major}.{engineVersion.Version.Minor}{(engineVersion.CustomIteration.HasValue ? $".{engineVersion.CustomIteration.Value}" : String.Empty)}",
true),
EngineType.OpenDream => new EmbedField(
"OpenDream Version",
- $"[{engineVersion.SourceSHA[..7]}]({generalConfiguration.OpenDreamGitUrl}/commit/{engineVersion.SourceSHA})",
+ $"[{engineVersion.SourceSHA![..7]}]({generalConfiguration.OpenDreamGitUrl}/commit/{engineVersion.SourceSHA})",
true),
_ => throw new InvalidOperationException($"Invaild EngineType: {engineVersion.Engine.Value}"),
};
+ var revisionSha = revisionInformation.CommitSha!;
+ var revisionOriginSha = revisionInformation.OriginCommitSha!;
var fields = new List
{
engineField,
+ };
+
+ if (gitHubOwner == null || gitHubRepo == null)
+ return fields;
+
+ fields.Add(
new EmbedField(
"Local Commit",
localCommitPushed && gitHub
- ? $"[{revisionInformation.CommitSha[..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.CommitSha})"
- : revisionInformation.CommitSha[..7],
- true),
+ ? $"[{revisionSha[..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionSha})"
+ : revisionSha[..7],
+ true));
+
+ fields.Add(
new EmbedField(
"Branch Commit",
gitHub
- ? $"[{revisionInformation.OriginCommitSha[..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.OriginCommitSha})"
- : revisionInformation.OriginCommitSha[..7],
- true),
- };
+ ? $"[{revisionOriginSha[..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionOriginSha})"
+ : revisionOriginSha[..7],
+ true));
fields.AddRange((revisionInformation.ActiveTestMerges ?? Enumerable.Empty())
.Select(x => x.TestMerge)
.Select(x => new EmbedField(
$"#{x.Number}",
- $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha[..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}",
+ $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha![..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}",
false)));
return fields;
@@ -932,7 +956,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// The to convert.
/// The parameter for sending a single .
#pragma warning disable CA1502
- Optional> ConvertEmbed(ChatEmbed embed)
+ Optional> ConvertEmbed(ChatEmbed? embed)
{
if (embed == null)
return default;
@@ -955,7 +979,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
embed.Author = null;
}
- List fields = null;
+ List? fields = null;
if (embed.Fields != null)
{
fields = new List();
@@ -987,7 +1011,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (invalid)
continue;
- fields.Add(new EmbedField(field.Name, field.Value)
+ fields.Add(new EmbedField(field.Name!, field.Value!)
{
IsInline = field.IsInline ?? default(Optional),
});
@@ -1026,7 +1050,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var discordEmbed = new Embed
{
Author = embed.Author != null
- ? new EmbedAuthor(embed.Author.Name)
+ ? new EmbedAuthor(embed.Author.Name!)
{
IconUrl = embed.Author.IconUrl ?? default(Optional),
ProxyIconUrl = embed.Author.ProxyIconUrl ?? default(Optional),
@@ -1037,14 +1061,14 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
Description = embed.Description ?? default(Optional),
Fields = fields ?? default(Optional>),
Footer = embed.Footer != null
- ? new EmbedFooter(embed.Footer.Text)
+ ? (Optional)new EmbedFooter(embed.Footer.Text!)
{
IconUrl = embed.Footer.IconUrl ?? default(Optional),
ProxyIconUrl = embed.Footer.ProxyIconUrl ?? default(Optional),
}
: default,
Image = embed.Image != null
- ? new EmbedImage(embed.Image.Url)
+ ? new EmbedImage(embed.Image.Url!)
{
Width = embed.Image.Width ?? default(Optional),
Height = embed.Image.Height ?? default(Optional),
@@ -1059,7 +1083,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
: default(Optional),
Thumbnail = embed.Thumbnail != null
- ? new EmbedThumbnail(embed.Thumbnail.Url)
+ ? new EmbedThumbnail(embed.Thumbnail.Url!)
{
Width = embed.Thumbnail.Width ?? default(Optional),
Height = embed.Thumbnail.Height ?? default(Optional),
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
index 22028aefc7..845892d9e0 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
@@ -45,7 +45,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// The for the operation.
/// A resulting in the next available or if the needed to reconnect.
/// Note that private messages will come in the form of s not returned in .
- Task NextMessage(CancellationToken cancellationToken);
+ Task NextMessage(CancellationToken cancellationToken);
///
/// Gracefully disconnects the provider. Permanently stops the reconnection timer.
@@ -65,12 +65,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// Send a message to the .
///
- /// The to reply to.
+ /// The optional to reply to.
/// The .
/// The to send to.
/// The for the operation.
/// A representing the running operation.
- ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken);
+ ValueTask SendMessage(Message? replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken);
///
/// Set the interval at which the provider starts jobs to try to reconnect.
@@ -92,12 +92,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// if the local deployment commit was pushed to the remote repository.
/// The for the operation.
/// A resulting in a to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. Returns another callback which should be called to mark the deployment as active.
- ValueTask>>> SendUpdateMessage(
+ ValueTask>>> SendUpdateMessage(
Models.RevisionInformation revisionInformation,
Api.Models.EngineVersion engineVersion,
DateTimeOffset? estimatedCompletionTime,
- string gitHubOwner,
- string gitHubRepo,
+ string? gitHubOwner,
+ string? gitHubRepo,
ulong channelId,
bool localCommitPushed,
CancellationToken cancellationToken);
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
index e4fe37a93e..ad83a80b18 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
@@ -75,23 +75,23 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// Map of s to channel names.
///
- readonly Dictionary channelIdMap;
+ readonly Dictionary channelIdMap;
///
/// Map of s to query users.
///
readonly Dictionary queryChannelIdMap;
+ ///
+ /// The used for .
+ ///
+ Task? listenTask;
+
///
/// Id counter for .
///
ulong channelIdCounter;
- ///
- /// The used for .
- ///
- Task listenTask;
-
///
/// If we are disconnecting.
///
@@ -119,11 +119,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (builder == null || !builder.Valid || builder is not IrcConnectionStringBuilder ircBuilder)
throw new InvalidOperationException("Invalid ChatConnectionStringBuilder!");
- address = ircBuilder.Address;
- port = ircBuilder.Port.Value;
- nickname = ircBuilder.Nickname;
+ address = ircBuilder.Address!;
+ port = ircBuilder.Port!.Value;
+ nickname = ircBuilder.Nickname!;
- password = ircBuilder.Password;
+ password = ircBuilder.Password!;
passwordType = ircBuilder.PasswordType;
client = new IrcFeatures
@@ -138,7 +138,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
ActiveChannelSyncing = true,
AutoNickHandling = true,
CtcpVersion = assemblyInformationProvider.VersionString,
- UseSsl = ircBuilder.UseSsl.Value,
+ UseSsl = ircBuilder.UseSsl!.Value,
};
if (ircBuilder.UseSsl.Value)
client.ValidateServerCertificate = true; // dunno if it defaults to that or what
@@ -149,7 +149,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/*client.OnReadLine += (sender, e) => Logger.LogTrace("READ: {line}", e.Line);
client.OnWriteLine += (sender, e) => Logger.LogTrace("WRITE: {line}", e.Line);*/
- channelIdMap = new Dictionary();
+ channelIdMap = new Dictionary();
queryChannelIdMap = new Dictionary();
channelIdCounter = 1;
}
@@ -164,7 +164,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
///
- public override async ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken)
+ public override async ValueTask SendMessage(Message? replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(message);
@@ -218,12 +218,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
///
- public override async ValueTask>>> SendUpdateMessage(
+ public override async ValueTask>>> SendUpdateMessage(
Models.RevisionInformation revisionInformation,
EngineVersion engineVersion,
DateTimeOffset? estimatedCompletionTime,
- string gitHubOwner,
- string gitHubRepo,
+ string? gitHubOwner,
+ string? gitHubRepo,
ulong channelId,
bool localCommitPushed,
CancellationToken cancellationToken)
@@ -233,7 +233,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
ArgumentNullException.ThrowIfNull(gitHubOwner);
ArgumentNullException.ThrowIfNull(gitHubRepo);
- var commitInsert = revisionInformation.CommitSha[..7];
+ var commitInsert = revisionInformation.CommitSha![..7];
string remoteCommitInsert;
if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha)
{
@@ -241,7 +241,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
remoteCommitInsert = String.Empty;
}
else
- remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha[..7]);
+ remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha![..7]);
var testmergeInsert = (revisionInformation.ActiveTestMerges?.Count ?? 0) == 0
? String.Empty
@@ -251,17 +251,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
String.Join(
", ",
revisionInformation
- .ActiveTestMerges
+ .ActiveTestMerges!
.Select(x => x.TestMerge)
.Select(x =>
{
- var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.TargetCommitSha[..7]);
+ var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.TargetCommitSha![..7]);
if (x.Comment != null)
result += String.Format(CultureInfo.InvariantCulture, " ({0})", x.Comment);
return result;
})));
- var prefix = GetEngineCompilerPrefix(engineVersion.Engine.Value);
+ var prefix = GetEngineCompilerPrefix(engineVersion.Engine!.Value);
await SendMessage(
null,
new MessageContent
@@ -351,14 +351,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
dbChannel,
new List
{
- new()
+ new(address, channelName, id!.Value)
{
- RealId = id.Value,
- IsAdminChannel = dbChannel.IsAdminChannel == true,
- ConnectionName = address,
- FriendlyName = channelIdMap[id.Value],
- IsPrivateChannel = false,
Tag = dbChannel.Tag,
+ IsAdminChannel = dbChannel.IsAdminChannel == true,
+ IsPrivateChannel = false,
EmbedsSupported = false,
},
});
@@ -503,7 +500,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var username = e.Data.Nick;
var channelName = isPrivate ? username : e.Data.Channel;
- ulong MapAndGetChannelId(Dictionary dicToCheck)
+ ulong MapAndGetChannelId(Dictionary dicToCheck)
{
ulong? resultId = null;
if (!dicToCheck.Any(x =>
@@ -520,36 +517,31 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
channelIdMap.Add(resultId.Value, null);
}
- return resultId.Value;
+ return resultId!.Value;
}
ulong userId, channelId;
lock (client)
{
- userId = MapAndGetChannelId(queryChannelIdMap);
+ userId = MapAndGetChannelId(new Dictionary(queryChannelIdMap
+ .Cast>())); // NRT my beloathed
channelId = isPrivate ? userId : MapAndGetChannelId(channelIdMap);
}
- var message = new Message
- {
- Content = e.Data.Message,
- User = new ChatUser
- {
- Channel = new ChannelRepresentation
+ var channelFriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName;
+ var message = new Message(
+ new ChatUser(
+ new ChannelRepresentation(address, channelFriendlyName, channelId)
{
- ConnectionName = address,
- FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName,
- RealId = channelId,
IsPrivateChannel = isPrivate,
EmbedsSupported = false,
// isAdmin and Tag populated by manager
},
- FriendlyName = username,
- RealId = userId,
- Mention = username,
- },
- };
+ username,
+ username,
+ userId),
+ e.Data.Message);
EnqueueMessage(message);
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Message.cs
index 9510113f62..c9084657fc 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/Message.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Message.cs
@@ -1,4 +1,6 @@
-namespace Tgstation.Server.Host.Components.Chat.Providers
+using System;
+
+namespace Tgstation.Server.Host.Components.Chat.Providers
{
///
/// Represents a message received by a .
@@ -8,11 +10,22 @@
///
/// The text of the message.
///
- public string Content { get; set; }
+ public string Content { get; }
///
/// The who sent the .
///
- public ChatUser User { get; set; }
+ public ChatUser User { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ public Message(ChatUser user, string content)
+ {
+ User = user ?? throw new ArgumentNullException(nameof(user));
+ Content = content ?? throw new ArgumentNullException(nameof(content));
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
index 7da3b29930..6be6b71521 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
@@ -42,7 +42,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// of received s.
///
- readonly Queue messageQueue;
+ readonly Queue messageQueue;
///
/// The backing for .
@@ -62,12 +62,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// The auto reconnect .
///
- Task reconnectTask;
+ Task? reconnectTask;
///
/// for .
///
- CancellationTokenSource reconnectCts;
+ CancellationTokenSource? reconnectCts;
///
/// Get the prefix for messages about deployments.
@@ -96,7 +96,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
ChatBot = chatBot ?? throw new ArgumentNullException(nameof(chatBot));
- messageQueue = new Queue();
+ if (chatBot.Instance == null)
+ throw new ArgumentException("chatBot must have Instance!", nameof(chatBot));
+
+ messageQueue = new Queue();
nextMessage = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
initialConnectionTcs = new TaskCompletionSource();
reconnectTaskLock = new object();
@@ -157,7 +160,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
///
- public async Task NextMessage(CancellationToken cancellationToken)
+ public async Task NextMessage(CancellationToken cancellationToken)
{
while (true)
{
@@ -191,15 +194,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
///
- public abstract ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken);
+ public abstract ValueTask SendMessage(Message? replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken);
///
- public abstract ValueTask>>> SendUpdateMessage(
+ public abstract ValueTask>>> SendUpdateMessage(
RevisionInformation revisionInformation,
Api.Models.EngineVersion engineVersion,
DateTimeOffset? estimatedCompletionTime,
- string gitHubOwner,
- string gitHubRepo,
+ string? gitHubOwner,
+ string? gitHubRepo,
ulong channelId,
bool localCommitPushed,
CancellationToken cancellationToken);
@@ -232,7 +235,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// Queues a for .
///
/// The to queue. A value of indicates the channel mappings are out of date.
- protected void EnqueueMessage(Message message)
+ protected void EnqueueMessage(Message? message)
{
if (message == null)
Logger.LogTrace("Requesting channel remap...");
@@ -257,7 +260,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
reconnectCts.Cancel();
reconnectCts.Dispose();
reconnectCts = null;
- var reconnectTask = this.reconnectTask;
+ var reconnectTask = this.reconnectTask!;
this.reconnectTask = null;
return reconnectTask;
}
@@ -286,7 +289,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
connectNow = false;
if (!Connected)
{
- var job = Job.Create(Api.Models.JobCode.ReconnectChatBot, null, ChatBot.Instance, ChatBotRights.WriteEnabled);
+ var job = Job.Create(Api.Models.JobCode.ReconnectChatBot, null, ChatBot.Instance!, ChatBotRights.WriteEnabled);
job.Description += $": {ChatBot.Name}";
await jobManager.RegisterOperation(
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
index de36092c6b..3b6695313b 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
@@ -15,6 +16,7 @@ using Tgstation.Server.Host.Components.Events;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Deployment
{
@@ -34,6 +36,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
///
+ [MemberNotNullWhen(true, nameof(nextDmbProvider))]
public bool DmbAvailable => nextDmbProvider != null;
///
@@ -74,7 +77,7 @@ namespace Tgstation.Server.Host.Components.Deployment
///
/// Map of s to locks on them.
///
- readonly IDictionary jobLockCounts;
+ readonly Dictionary jobLockCounts;
///
/// resulting in the latest yet to exist.
@@ -89,7 +92,7 @@ namespace Tgstation.Server.Host.Components.Deployment
///
/// The latest .
///
- IDmbProvider nextDmbProvider;
+ IDmbProvider? nextDmbProvider;
///
/// If the is "started" via .
@@ -130,7 +133,7 @@ namespace Tgstation.Server.Host.Components.Deployment
public void Dispose() => cleanupCts.Dispose(); // we don't dispose nextDmbProvider here, since it might be the only thing we have
///
- public async ValueTask LoadCompileJob(CompileJob job, Action activationAction, CancellationToken cancellationToken)
+ public async ValueTask LoadCompileJob(CompileJob job, Action? activationAction, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(job);
@@ -173,8 +176,8 @@ namespace Tgstation.Server.Host.Components.Deployment
throw new ArgumentOutOfRangeException(nameof(lockCount), lockCount, "lockCount must be greater than or equal to 0!");
lock (jobLockCounts)
{
- var jobId = nextDmbProvider.CompileJob.Id;
- var incremented = jobLockCounts[jobId.Value] += lockCount;
+ var jobId = nextDmbProvider.CompileJob.Require(x => x.Id);
+ var incremented = jobLockCounts[jobId] += lockCount;
logger.LogTrace("Compile job {jobId} lock count now: {lockCount}", jobId, incremented);
return nextDmbProvider;
}
@@ -183,16 +186,15 @@ namespace Tgstation.Server.Host.Components.Deployment
///
public async Task StartAsync(CancellationToken cancellationToken)
{
- CompileJob cj = null;
- await databaseContextFactory.UseContext(async (db) =>
- {
- cj = await db
- .CompileJobs
- .AsQueryable()
- .Where(x => x.Job.Instance.Id == metadata.Id)
- .OrderByDescending(x => x.Job.StoppedAt)
- .FirstOrDefaultAsync(cancellationToken);
- });
+ CompileJob? cj = null;
+ await databaseContextFactory.UseContext(
+ async (db) =>
+ cj = await db
+ .CompileJobs
+ .AsQueryable()
+ .Where(x => x.Job.Instance!.Id == metadata.Id)
+ .OrderByDescending(x => x.Job.StoppedAt)
+ .FirstOrDefaultAsync(cancellationToken));
try
{
@@ -227,35 +229,39 @@ namespace Tgstation.Server.Host.Components.Deployment
///
#pragma warning disable CA1506 // TODO: Decomplexify
- public async ValueTask FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken)
+ public async ValueTask FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(compileJob);
// ensure we have the entire metadata tree
- logger.LogTrace("Loading compile job {id}...", compileJob.Id);
+ var compileJobId = compileJob.Require(x => x.Id);
+ logger.LogTrace("Loading compile job {id}...", compileJobId);
await databaseContextFactory.UseContext(
async db => compileJob = await db
.CompileJobs
.AsQueryable()
- .Where(x => x.Id == compileJob.Id)
- .Include(x => x.Job)
+ .Where(x => x!.Id == compileJobId)
+ .Include(x => x.Job!)
.ThenInclude(x => x.StartedBy)
- .Include(x => x.Job)
+ .Include(x => x.Job!)
.ThenInclude(x => x.Instance)
- .Include(x => x.RevisionInformation)
- .ThenInclude(x => x.PrimaryTestMerge)
- .ThenInclude(x => x.MergedBy)
- .Include(x => x.RevisionInformation)
- .ThenInclude(x => x.ActiveTestMerges)
- .ThenInclude(x => x.TestMerge)
- .ThenInclude(x => x.MergedBy)
+ .Include(x => x.RevisionInformation!)
+ .ThenInclude(x => x.PrimaryTestMerge!)
+ .ThenInclude(x => x.MergedBy)
+ .Include(x => x.RevisionInformation!)
+ .ThenInclude(x => x.ActiveTestMerges!)
+ .ThenInclude(x => x.TestMerge!)
+ .ThenInclude(x => x.MergedBy)
.FirstAsync(cancellationToken)); // can't wait to see that query
- if (!EngineVersion.TryParse(compileJob.EngineVersion, out var engineVersion))
+ EngineVersion engineVersion;
+ if (!EngineVersion.TryParse(compileJob.EngineVersion, out var engineVersionNullable))
{
- logger.LogWarning("Error loading compile job, bad engine version: {0}", compileJob.EngineVersion);
+ logger.LogWarning("Error loading compile job, bad engine version: {engineVersion}", compileJob.EngineVersion);
return null; // omae wa mou shinderu
}
+ else
+ engineVersion = engineVersionNullable!;
if (!compileJob.Job.StoppedAt.HasValue)
{
@@ -274,7 +280,7 @@ namespace Tgstation.Server.Host.Components.Deployment
CleanRegisteredCompileJob(compileJob);
}
- var newProvider = new DmbProvider(compileJob, engineVersion, ioManager, CleanupAction);
+ var newProvider = new DmbProvider(compileJob, engineVersion, ioManager, new DisposeInvoker(CleanupAction));
try
{
const string LegacyADirectoryName = "A";
@@ -311,22 +317,22 @@ namespace Tgstation.Server.Host.Components.Deployment
// rebuild the provider because it's using the legacy style directories
// Don't dispose it
logger.LogDebug("Creating legacy two folder .dmb provider targeting {aDirName} directory...", LegacyADirectoryName);
- newProvider = new DmbProvider(compileJob, engineVersion, ioManager, CleanupAction, Path.DirectorySeparatorChar + LegacyADirectoryName);
+ newProvider = new DmbProvider(compileJob, engineVersion, ioManager, new DisposeInvoker(CleanupAction), Path.DirectorySeparatorChar + LegacyADirectoryName);
}
lock (jobLockCounts)
{
- if (!jobLockCounts.TryGetValue(compileJob.Id.Value, out int value))
+ if (!jobLockCounts.TryGetValue(compileJobId, out int value))
{
value = 1;
- jobLockCounts.Add(compileJob.Id.Value, 1);
+ jobLockCounts.Add(compileJobId, 1);
}
else
- jobLockCounts[compileJob.Id.Value] = ++value;
+ jobLockCounts[compileJobId] = ++value;
providerSubmitted = true;
- logger.LogTrace("Compile job {id} lock count now: {lockCount}", compileJob.Id, value);
+ logger.LogTrace("Compile job {id} lock count now: {lockCount}", compileJobId, value);
return newProvider;
}
}
@@ -348,10 +354,10 @@ namespace Tgstation.Server.Host.Components.Deployment
lock (jobLockCounts)
jobIdsToSkip = jobLockCounts.Keys.ToList();
- List jobUidsToNotErase = null;
+ List? jobUidsToNotErase = null;
// find the uids of locked directories
- if (jobIdsToSkip.Any())
+ if (jobIdsToSkip.Count > 0)
{
await databaseContextFactory.UseContext(async db =>
{
@@ -359,9 +365,9 @@ namespace Tgstation.Server.Host.Components.Deployment
.CompileJobs
.AsQueryable()
.Where(
- x => x.Job.Instance.Id == metadata.Id
- && jobIdsToSkip.Contains(x.Id.Value))
- .Select(x => x.DirectoryName.Value)
+ x => x.Job.Instance!.Id == metadata.Id
+ && jobIdsToSkip.Contains(x.Id!.Value))
+ .Select(x => x.DirectoryName!.Value)
.ToListAsync(cancellationToken))
.Select(x => x.ToString())
.ToList();
@@ -370,7 +376,7 @@ namespace Tgstation.Server.Host.Components.Deployment
else
jobUidsToNotErase = new List();
- jobUidsToNotErase.Add(SwappableDmbProvider.LiveGameDirectory);
+ jobUidsToNotErase!.Add(SwappableDmbProvider.LiveGameDirectory);
logger.LogTrace("We will not clean the following directories: {directoriesToNotClean}", String.Join(", ", jobUidsToNotErase));
@@ -405,7 +411,7 @@ namespace Tgstation.Server.Host.Components.Deployment
#pragma warning restore CA1506
///
- public CompileJob LatestCompileJob()
+ public CompileJob? LatestCompileJob()
{
if (!DmbAvailable)
return null;
@@ -426,7 +432,7 @@ namespace Tgstation.Server.Host.Components.Deployment
// DCT: None available
var deploymentJob = remoteDeploymentManager.MarkInactive(job, CancellationToken.None);
- var deleteTask = DeleteCompileJobContent(job.DirectoryName.ToString(), cleanupCts.Token);
+ var deleteTask = DeleteCompileJobContent(job.DirectoryName!.Value.ToString(), cleanupCts.Token);
var otherTask = cleanupTask;
async Task WrapThrowableTasks()
@@ -445,20 +451,23 @@ namespace Tgstation.Server.Host.Components.Deployment
}
lock (jobLockCounts)
- if (jobLockCounts.TryGetValue(job.Id.Value, out var currentVal))
+ {
+ var jobId = job.Require(x => x.Id);
+ if (jobLockCounts.TryGetValue(jobId, out var currentVal))
if (currentVal == 1)
{
- jobLockCounts.Remove(job.Id.Value);
- logger.LogDebug("Cleaning lock-free compile job {id} => {dirName}", job.Id, job.DirectoryName);
+ jobLockCounts.Remove(jobId);
+ logger.LogDebug("Cleaning lock-free compile job {id} => {dirName}", jobId, job.DirectoryName);
cleanupTask = HandleCleanup();
}
else
{
- var decremented = --jobLockCounts[job.Id.Value];
- logger.LogTrace("Compile job {id} lock count now: {lockCount}", job.Id, decremented);
+ var decremented = --jobLockCounts[jobId];
+ logger.LogTrace("Compile job {id} lock count now: {lockCount}", jobId, decremented);
}
else
- logger.LogError("Extra Dispose of DmbProvider for CompileJob {compileJobId}!", job.Id);
+ logger.LogError("Extra Dispose of DmbProvider for CompileJob {compileJobId}!", jobId);
+ }
}
///
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs
index 37c44d9fab..be47aeb6b3 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DmbProvider.cs
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.IO;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Deployment
{
@@ -11,7 +12,7 @@ namespace Tgstation.Server.Host.Components.Deployment
sealed class DmbProvider : DmbProviderBase, IDmbProvider
{
///
- public override string Directory => ioManager.ResolvePath(CompileJob.DirectoryName.ToString() + directoryAppend);
+ public override string Directory => ioManager.ResolvePath(CompileJob.DirectoryName!.Value.ToString() + directoryAppend);
///
public override Models.CompileJob CompileJob { get; }
@@ -32,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Deployment
///
/// The to run when is called.
///
- Action onDispose;
+ DisposeInvoker? onDispose;
///
/// Initializes a new instance of the class.
@@ -42,7 +43,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// The value of .
/// The value of .
/// The optional value of .
- public DmbProvider(Models.CompileJob compileJob, EngineVersion engineVersion, IIOManager ioManager, Action onDispose, string directoryAppend = null)
+ public DmbProvider(Models.CompileJob compileJob, EngineVersion engineVersion, IIOManager ioManager, DisposeInvoker onDispose, string? directoryAppend = null)
{
CompileJob = compileJob ?? throw new ArgumentNullException(nameof(compileJob));
EngineVersion = engineVersion ?? throw new ArgumentNullException(nameof(engineVersion));
@@ -54,7 +55,7 @@ namespace Tgstation.Server.Host.Components.Deployment
///
public override ValueTask DisposeAsync()
{
- onDispose?.Invoke();
+ onDispose?.Dispose();
return ValueTask.CompletedTask;
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbProviderBase.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbProviderBase.cs
index 5f1248463d..5929b74167 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DmbProviderBase.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DmbProviderBase.cs
@@ -13,11 +13,11 @@ namespace Tgstation.Server.Host.Components.Deployment
///
public string DmbName => String.Concat(
CompileJob.DmeName,
- EngineVersion.Engine.Value switch
+ EngineVersion.Engine switch
{
EngineType.Byond => ".dmb",
EngineType.OpenDream => ".json",
- _ => throw new InvalidOperationException($"Invalid EngineType: {EngineVersion.Engine.Value}"),
+ _ => throw new InvalidOperationException($"Invalid EngineType: {EngineVersion.Engine}"),
});
///
diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
index 3a8a256fd1..f72d848082 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
@@ -113,12 +113,12 @@ namespace Tgstation.Server.Host.Components.Deployment
///
/// The active callback from .
///
- Func> currentChatCallback;
+ Func>? currentChatCallback;
///
/// Cached for .
///
- string currentDreamMakerOutput;
+ string? currentDreamMakerOutput;
///
/// If a compile job is running.
@@ -207,18 +207,18 @@ namespace Tgstation.Server.Host.Components.Deployment
currentChatCallback = null;
currentDreamMakerOutput = null;
- Models.CompileJob compileJob = null;
+ Models.CompileJob? compileJob = null;
try
{
- string repoOwner = null;
- string repoName = null;
+ string? repoOwner = null;
+ string? repoName = null;
TimeSpan? averageSpan = null;
- Models.RepositorySettings repositorySettings = null;
- Models.DreamDaemonSettings ddSettings = null;
- Models.DreamMakerSettings dreamMakerSettings = null;
- IRepository repo = null;
- IRemoteDeploymentManager remoteDeploymentManager = null;
- Models.RevisionInformation revInfo = null;
+ Models.RepositorySettings? repositorySettings = null;
+ Models.DreamDaemonSettings? ddSettings = null;
+ Models.DreamMakerSettings? dreamMakerSettings = null;
+ IRepository? repo = null;
+ IRemoteDeploymentManager? remoteDeploymentManager = null;
+ Models.RevisionInformation? revInfo = null;
await databaseContextFactory.UseContext(
async databaseContext =>
{
@@ -268,7 +268,7 @@ namespace Tgstation.Server.Host.Components.Deployment
throw new JobException(ErrorCode.RepoMissing);
remoteDeploymentManager = remoteDeploymentManagerFactory
- .CreateRemoteDeploymentManager(metadata, repo.RemoteGitProvider.Value);
+ .CreateRemoteDeploymentManager(metadata, repo.RemoteGitProvider!.Value);
var repoSha = repo.Head;
repoOwner = repo.RemoteRepositoryOwner;
@@ -276,13 +276,13 @@ namespace Tgstation.Server.Host.Components.Deployment
revInfo = await databaseContext
.RevisionInformations
.AsQueryable()
- .Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id)
- .Include(x => x.ActiveTestMerges)
- .ThenInclude(x => x.TestMerge)
- .ThenInclude(x => x.MergedBy)
+ .Where(x => x.CommitSha == repoSha && x.InstanceId == metadata.Id)
+ .Include(x => x.ActiveTestMerges!)
+ .ThenInclude(x => x.TestMerge!)
+ .ThenInclude(x => x.MergedBy)
.FirstOrDefaultAsync(cancellationToken);
- if (revInfo == default)
+ if (revInfo == null)
{
revInfo = new Models.RevisionInformation
{
@@ -310,16 +310,17 @@ namespace Tgstation.Server.Host.Components.Deployment
});
var likelyPushedTestMergeCommit =
- repositorySettings.PushTestMergeCommits.Value
+ repositorySettings!.PushTestMergeCommits!.Value
&& repositorySettings.AccessToken != null
&& repositorySettings.AccessUser != null;
using (repo)
compileJob = await Compile(
- revInfo,
- dreamMakerSettings,
- ddSettings,
- repo,
- remoteDeploymentManager,
+ job,
+ revInfo!,
+ dreamMakerSettings!,
+ ddSettings!,
+ repo!,
+ remoteDeploymentManager!,
progressReporter,
averageSpan,
likelyPushedTestMergeCommit,
@@ -332,11 +333,11 @@ namespace Tgstation.Server.Host.Components.Deployment
async databaseContext =>
{
var fullJob = compileJob.Job;
- compileJob.Job = new Models.Job(job.Id.Value);
+ compileJob.Job = new Models.Job(job.Require(x => x.Id));
var fullRevInfo = compileJob.RevisionInformation;
compileJob.RevisionInformation = new Models.RevisionInformation
{
- Id = revInfo.Id,
+ Id = revInfo!.Id,
};
databaseContext.Jobs.Attach(compileJob.Job);
@@ -348,7 +349,7 @@ namespace Tgstation.Server.Host.Components.Deployment
logger.LogTrace("Created CompileJob {compileJobId}", compileJob.Id);
try
{
- var chatNotificationAction = currentChatCallback(null, compileJob.Output);
+ var chatNotificationAction = currentChatCallback!(null, compileJob.Output!);
await compileJobConsumer.LoadCompileJob(compileJob, chatNotificationAction, cancellationToken);
}
catch
@@ -367,11 +368,11 @@ namespace Tgstation.Server.Host.Components.Deployment
}
catch (Exception ex)
{
- await CleanupFailedCompile(compileJob, remoteDeploymentManager, ex);
+ await CleanupFailedCompile(compileJob, remoteDeploymentManager!, ex);
throw;
}
- var commentsTask = remoteDeploymentManager.PostDeploymentComments(
+ var commentsTask = remoteDeploymentManager!.PostDeploymentComments(
compileJob,
activeCompileJob?.RevisionInformation,
repositorySettings,
@@ -398,7 +399,7 @@ namespace Tgstation.Server.Host.Components.Deployment
{
currentChatCallback?.Invoke(
FormatExceptionForUsers(ex),
- currentDreamMakerOutput);
+ currentDreamMakerOutput!);
throw;
}
@@ -420,13 +421,13 @@ namespace Tgstation.Server.Host.Components.Deployment
var previousCompileJobs = await databaseContext
.CompileJobs
.AsQueryable()
- .Where(x => x.Job.Instance.Id == metadata.Id)
+ .Where(x => x.Job.Instance!.Id == metadata.Id)
.OrderByDescending(x => x.Job.StoppedAt)
.Take(10)
.Select(x => new
{
- x.Job.StoppedAt,
- x.Job.StartedAt,
+ StoppedAt = x.Job.StoppedAt!.Value,
+ StartedAt = x.Job.StartedAt!.Value,
})
.ToListAsync(cancellationToken);
@@ -435,7 +436,7 @@ namespace Tgstation.Server.Host.Components.Deployment
{
var totalSpan = TimeSpan.Zero;
foreach (var previousCompileJob in previousCompileJobs)
- totalSpan += previousCompileJob.StoppedAt.Value - previousCompileJob.StartedAt.Value;
+ totalSpan += previousCompileJob.StoppedAt - previousCompileJob.StartedAt;
averageSpan = totalSpan / previousCompileJobs.Count;
}
@@ -445,6 +446,7 @@ namespace Tgstation.Server.Host.Components.Deployment
///
/// Run the compile implementation.
///
+ /// The currently running .
/// The .
/// The .
/// The .
@@ -456,6 +458,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// The for the operation.
/// A resulting in the completed .
async ValueTask Compile(
+ Models.Job job,
Models.RevisionInformation revisionInformation,
Api.Models.Internal.DreamMakerSettings dreamMakerSettings,
DreamDaemonLaunchParameters launchParameters,
@@ -483,22 +486,20 @@ namespace Tgstation.Server.Host.Components.Deployment
repository.RemoteRepositoryName,
localCommitExistsOnRemote);
- var job = new Models.CompileJob
+ var compileJob = new Models.CompileJob(job, revisionInformation, engineLock.Version.ToString())
{
DirectoryName = Guid.NewGuid(),
DmeName = dreamMakerSettings.ProjectName,
- RevisionInformation = revisionInformation,
- EngineVersion = engineLock.Version.ToString(),
RepositoryOrigin = repository.Origin.ToString(),
};
progressReporter.StageName = "Creating remote deployment notification";
await remoteDeploymentManager.StartDeployment(
repository,
- job,
+ compileJob,
cancellationToken);
- logger.LogTrace("Deployment will timeout at {timeoutTime}", DateTimeOffset.UtcNow + dreamMakerSettings.Timeout.Value);
+ logger.LogTrace("Deployment will timeout at {timeoutTime}", DateTimeOffset.UtcNow + dreamMakerSettings.Timeout!.Value);
using var timeoutTokenSource = new CancellationTokenSource(dreamMakerSettings.Timeout.Value);
var timeoutToken = timeoutTokenSource.Token;
using (timeoutToken.Register(() => logger.LogWarning("Deployment timed out!")))
@@ -508,7 +509,7 @@ namespace Tgstation.Server.Host.Components.Deployment
{
await RunCompileJob(
progressReporter,
- job,
+ compileJob,
dreamMakerSettings,
launchParameters,
engineLock,
@@ -522,7 +523,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
}
- return job;
+ return compileJob;
}
catch (OperationCanceledException)
{
@@ -560,7 +561,7 @@ namespace Tgstation.Server.Host.Components.Deployment
IRemoteDeploymentManager remoteDeploymentManager,
CancellationToken cancellationToken)
{
- var outputDirectory = job.DirectoryName.ToString();
+ var outputDirectory = job.DirectoryName!.Value.ToString();
logger.LogTrace("Compile output GUID: {dirGuid}", outputDirectory);
try
@@ -644,13 +645,13 @@ namespace Tgstation.Server.Host.Components.Deployment
progressReporter.StageName = "Validating DMAPI";
await VerifyApi(
- launchParameters.StartupTimeout.Value,
- dreamMakerSettings.ApiValidationSecurityLevel.Value,
+ launchParameters.StartupTimeout!.Value,
+ dreamMakerSettings.ApiValidationSecurityLevel!.Value,
job,
engineLock,
- dreamMakerSettings.ApiValidationPort.Value,
- dreamMakerSettings.RequireDMApiValidation.Value,
- launchParameters.LogOutput.Value,
+ dreamMakerSettings.ApiValidationPort!.Value,
+ dreamMakerSettings.RequireDMApiValidation!.Value,
+ launchParameters.LogOutput!.Value,
cancellationToken);
}
catch (JobException)
@@ -796,7 +797,7 @@ namespace Tgstation.Server.Host.Components.Deployment
ApiValidationStatus validationStatus;
await using (var provider = new TemporaryDmbProvider(
- ioManager.ResolvePath(job.DirectoryName.ToString()),
+ ioManager.ResolvePath(job.DirectoryName!.Value.ToString()),
job,
engineLock.Version))
await using (var controller = await sessionControllerFactory.LaunchNew(provider, engineLock, launchParameters, true, cancellationToken))
@@ -856,7 +857,7 @@ namespace Tgstation.Server.Host.Components.Deployment
await using var dm = processExecutor.LaunchProcess(
engineLock.CompilerExePath,
ioManager.ResolvePath(
- job.DirectoryName.ToString()),
+ job.DirectoryName!.Value.ToString()),
arguments,
readStandardHandles: true,
noShellExecute: true);
@@ -886,14 +887,15 @@ namespace Tgstation.Server.Host.Components.Deployment
async ValueTask ModifyDme(Models.CompileJob job, CancellationToken cancellationToken)
{
var dmeFileName = String.Join('.', job.DmeName, DmeExtension);
- var dmePath = ioManager.ConcatPath(job.DirectoryName.ToString(), dmeFileName);
+ var stringDirectoryName = job.DirectoryName!.Value.ToString();
+ var dmePath = ioManager.ConcatPath(stringDirectoryName, dmeFileName);
var dmeReadTask = ioManager.ReadAllBytes(dmePath, cancellationToken);
var dmeModificationsTask = configuration.CopyDMFilesTo(
dmeFileName,
ioManager.ResolvePath(
ioManager.ConcatPath(
- job.DirectoryName.ToString(),
+ stringDirectoryName,
ioManager.GetDirectoryName(dmeFileName))),
cancellationToken);
@@ -952,7 +954,7 @@ namespace Tgstation.Server.Host.Components.Deployment
async ValueTask CleanDir()
{
logger.LogTrace("Cleaning compile directory...");
- var jobPath = job.DirectoryName.ToString();
+ var jobPath = job.DirectoryName!.Value.ToString();
try
{
// DCT: None available
@@ -965,13 +967,16 @@ namespace Tgstation.Server.Host.Components.Deployment
}
}
- // DCT: None available
+ var dirCleanTask = CleanDir();
+
+ var failRemoteDeployTask = remoteDeploymentManager.FailDeployment(
+ job,
+ FormatExceptionForUsers(exception),
+ CancellationToken.None); // DCT: None available
+
return ValueTaskExtensions.WhenAll(
- CleanDir(),
- remoteDeploymentManager.FailDeployment(
- job,
- FormatExceptionForUsers(exception),
- CancellationToken.None));
+ dirCleanTask,
+ failRemoteDeployTask);
}
}
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/HardLinkDmbProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/HardLinkDmbProvider.cs
index 568786e340..98a8361e34 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/HardLinkDmbProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/HardLinkDmbProvider.cs
@@ -153,7 +153,7 @@ namespace Tgstation.Server.Host.Components.Deployment
if (taskThrottle.HasValue && taskThrottle < 1)
throw new ArgumentOutOfRangeException(nameof(taskThrottle), taskThrottle, "taskThrottle must be at least 1!");
- var src = IOManager.ResolvePath(CompileJob.DirectoryName.ToString());
+ var src = IOManager.ResolvePath(CompileJob.DirectoryName!.Value.ToString());
var dest = IOManager.ResolvePath(mirrorGuid.ToString());
using var semaphore = taskThrottle.HasValue ? new SemaphoreSlim(taskThrottle.Value) : null;
@@ -178,10 +178,10 @@ namespace Tgstation.Server.Host.Components.Deployment
/// The for the operation.
/// A of s representing the running operations. The first returned is always the necessary call to .
/// I genuinely don't know how this will work with symlinked files. Waiting for the issue report I guess.
- IEnumerable MirrorDirectoryImpl(string src, string dest, SemaphoreSlim semaphore, CancellationToken cancellationToken)
+ IEnumerable MirrorDirectoryImpl(string src, string dest, SemaphoreSlim? semaphore, CancellationToken cancellationToken)
{
var dir = new DirectoryInfo(src);
- Task subdirCreationTask = null;
+ Task? subdirCreationTask = null;
var dreamDaemonWillAcceptOutOfDirectorySymlinks = CompileJob.MinimumSecurityLevel == DreamDaemonSecurity.Trusted;
foreach (var subDirectory in dir.EnumerateDirectories())
{
@@ -191,7 +191,8 @@ namespace Tgstation.Server.Host.Components.Deployment
if (subDirectory.Attributes.HasFlag(FileAttributes.ReparsePoint))
if (dreamDaemonWillAcceptOutOfDirectorySymlinks)
{
- var target = subDirectory.ResolveLinkTarget(false);
+ var target = subDirectory.ResolveLinkTarget(false)
+ ?? throw new InvalidOperationException($"\"{subDirectory.FullName}\" was incorrectly identified as a symlinked directory!");
logger.LogDebug("Recreating directory {name} as symlink to {target}", subDirectory.Name, target);
if (subdirCreationTask == null)
{
@@ -250,7 +251,9 @@ namespace Tgstation.Server.Host.Components.Deployment
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
// AHHHHHHHHHHHHH
- var target = fileInfo.ResolveLinkTarget(!dreamDaemonWillAcceptOutOfDirectorySymlinks);
+ var target = fileInfo.ResolveLinkTarget(!dreamDaemonWillAcceptOutOfDirectorySymlinks)
+ ?? throw new InvalidOperationException($"\"{fileInfo.FullName}\" was incorrectly identified as a symlinked file!");
+
if (dreamDaemonWillAcceptOutOfDirectorySymlinks)
{
logger.LogDebug("Recreating symlinked file {name} as symlink to {target}", fileInfo.Name, target.FullName);
diff --git a/src/Tgstation.Server.Host/Components/Deployment/ICompileJobSink.cs b/src/Tgstation.Server.Host/Components/Deployment/ICompileJobSink.cs
index 49d4eae165..cf18fc0412 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/ICompileJobSink.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/ICompileJobSink.cs
@@ -15,9 +15,9 @@ namespace Tgstation.Server.Host.Components.Deployment
/// Load a new into the .
///
/// The to load.
- /// An to be called when the becomes active or is discarded with or respectively.
+ /// An optional to be called when the becomes active or is discarded with or respectively.
/// The for the operation.
/// A representing the running operation.
- ValueTask LoadCompileJob(CompileJob job, Action activationAction, CancellationToken cancellationToken);
+ ValueTask LoadCompileJob(CompileJob job, Action? activationAction, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs
index 6ff04b0a00..08d1a782b5 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/IDmbFactory.cs
@@ -23,7 +23,7 @@ namespace Tgstation.Server.Host.Components.Deployment
bool DmbAvailable { get; }
///
- /// Gets the next .
+ /// Gets the next . is a precondition.
///
/// The amount of locks to give the resulting . It's must be called this many times to properly clean the job.
/// A new .
@@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// The to make the for.
/// The for the operation.
/// A resulting in a new representing the on success, on failure.
- ValueTask FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken);
+ ValueTask FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken);
///
/// Deletes all compile jobs that are inactive in the Game folder.
diff --git a/src/Tgstation.Server.Host/Components/Deployment/ILatestCompileJobProvider.cs b/src/Tgstation.Server.Host/Components/Deployment/ILatestCompileJobProvider.cs
index 38a90c8dc6..021f62602d 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/ILatestCompileJobProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/ILatestCompileJobProvider.cs
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Deployment
///
/// Gets the latest .
///
- /// The latest .
- CompileJob LatestCompileJob();
+ /// The latest or if none are available.
+ CompileJob? LatestCompileJob();
}
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs
index db48c659ba..0f8c86732f 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs
@@ -52,18 +52,23 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
///
public async ValueTask PostDeploymentComments(
CompileJob compileJob,
- RevisionInformation previousRevisionInformation,
+ RevisionInformation? previousRevisionInformation,
RepositorySettings repositorySettings,
- string repoOwner,
- string repoName,
+ string? repoOwner,
+ string? repoName,
CancellationToken cancellationToken)
{
- if (repositorySettings?.AccessToken == null)
+ ArgumentNullException.ThrowIfNull(compileJob);
+ ArgumentNullException.ThrowIfNull(repositorySettings);
+ ArgumentNullException.ThrowIfNull(repoOwner);
+ ArgumentNullException.ThrowIfNull(repoName);
+
+ if (repositorySettings.AccessToken == null)
return;
var deployedRevisionInformation = compileJob.RevisionInformation;
if ((previousRevisionInformation != null && previousRevisionInformation.CommitSha == deployedRevisionInformation.CommitSha)
- || !repositorySettings.PostTestMergeComment.Value)
+ || !repositorySettings.PostTestMergeComment!.Value)
return;
previousRevisionInformation ??= new RevisionInformation();
@@ -94,7 +99,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
.Any(y => y.TestMerge.Number == x.Number))
.ToList();
- if (!addedTestMerges.Any() && !removedTestMerges.Any() && !updatedTestMerges.Any())
+ if (addedTestMerges.Count == 0 && removedTestMerges.Count == 0 && updatedTestMerges.Count == 0)
return;
Logger.LogTrace(
@@ -105,48 +110,54 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
var tasks = new List(addedTestMerges.Count + updatedTestMerges.Count + removedTestMerges.Count);
foreach (var addedTestMerge in addedTestMerges)
- tasks.Add(
- CommentOnTestMergeSource(
+ {
+ var addCommentTask = CommentOnTestMergeSource(
+ repositorySettings,
+ repoOwner,
+ repoName,
+ FormatTestMerge(
repositorySettings,
+ compileJob,
+ addedTestMerge,
repoOwner,
repoName,
- FormatTestMerge(
- repositorySettings,
- compileJob,
- addedTestMerge,
- repoOwner,
- repoName,
- false),
- addedTestMerge.Number,
- cancellationToken));
+ false),
+ addedTestMerge.Number,
+ cancellationToken);
+ tasks.Add(addCommentTask);
+ }
foreach (var removedTestMerge in removedTestMerges)
- tasks.Add(
- CommentOnTestMergeSource(
- repositorySettings,
- repoOwner,
- repoName,
- "#### Test Merge Removed",
- removedTestMerge.Number,
- cancellationToken));
+ {
+ var removeCommentTask = CommentOnTestMergeSource(
+ repositorySettings,
+ repoOwner,
+ repoName,
+ "#### Test Merge Removed",
+ removedTestMerge.Number,
+ cancellationToken);
+ tasks.Add(removeCommentTask);
+ }
foreach (var updatedTestMerge in updatedTestMerges)
- tasks.Add(
- CommentOnTestMergeSource(
+ {
+ var updateCommentTask = CommentOnTestMergeSource(
+ repositorySettings,
+ repoOwner,
+ repoName,
+ FormatTestMerge(
repositorySettings,
+ compileJob,
+ updatedTestMerge,
repoOwner,
repoName,
- FormatTestMerge(
- repositorySettings,
- compileJob,
- updatedTestMerge,
- repoOwner,
- repoName,
- true),
- updatedTestMerge.Number,
- cancellationToken));
+ true),
+ updatedTestMerge.Number,
+ cancellationToken);
+ tasks.Add(updateCommentTask);
+ }
- if (tasks.Any())
+ if (tasks.Count > 0)
await ValueTaskExtensions.WhenAll(tasks);
}
@@ -155,7 +166,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
{
ArgumentNullException.ThrowIfNull(compileJob);
- if (activationCallbacks.TryGetValue(compileJob.Id.Value, out var activationCallback))
+ if (activationCallbacks.TryGetValue(compileJob.Require(x => x.Id), out var activationCallback))
activationCallback(true);
return ApplyDeploymentImpl(compileJob, cancellationToken);
@@ -169,7 +180,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
{
ArgumentNullException.ThrowIfNull(compileJob);
- if (activationCallbacks.TryRemove(compileJob.Id.Value, out var activationCallback))
+ if (activationCallbacks.TryRemove(compileJob.Require(x => x.Id), out var activationCallback))
activationCallback(false);
return MarkInactiveImpl(compileJob, cancellationToken);
@@ -183,12 +194,13 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
CancellationToken cancellationToken);
///
- public ValueTask StageDeployment(CompileJob compileJob, Action activationCallback, CancellationToken cancellationToken)
+ public ValueTask StageDeployment(CompileJob compileJob, Action? activationCallback, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(compileJob);
- if (activationCallback != null && !activationCallbacks.TryAdd(compileJob.Id.Value, activationCallback))
- Logger.LogError("activationCallbacks conflicted on CompileJob #{id}!", compileJob.Id.Value);
+ var compileJobId = compileJob.Require(x => x.Id);
+ if (activationCallback != null && !activationCallbacks.TryAdd(compileJobId, activationCallback))
+ Logger.LogError("activationCallbacks conflicted on CompileJob #{id}!", compileJobId);
return StageDeploymentImpl(compileJob, cancellationToken);
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
index 751bb33646..4e37e7d008 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
@@ -65,7 +65,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
Logger.LogTrace("Starting deployment...");
- RepositorySettings repositorySettings = null;
+ RepositorySettings? repositorySettings = null;
await databaseContextFactory.UseContext(
async databaseContext =>
repositorySettings = await databaseContext
@@ -74,12 +74,12 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
.Where(x => x.InstanceId == Metadata.Id)
.FirstAsync(cancellationToken));
- var instanceAuthenticated = repositorySettings.AccessToken != null;
- IAuthenticatedGitHubService authenticatedGitHubService;
+ var instanceAuthenticated = repositorySettings!.AccessToken != null;
+ IAuthenticatedGitHubService? authenticatedGitHubService;
IGitHubService gitHubService;
if (instanceAuthenticated)
{
- authenticatedGitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken);
+ authenticatedGitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken!);
gitHubService = authenticatedGitHubService;
}
else
@@ -88,12 +88,14 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
gitHubService = gitHubServiceFactory.CreateService();
}
+ var repoOwner = remoteInformation.RemoteRepositoryOwner!;
+ var repoName = remoteInformation.RemoteRepositoryName!;
var repositoryIdTask = gitHubService.GetRepositoryId(
- remoteInformation.RemoteRepositoryOwner,
- remoteInformation.RemoteRepositoryName,
+ repoOwner,
+ repoName,
cancellationToken);
- if (!repositorySettings.CreateGitHubDeployments.Value)
+ if (!repositorySettings.CreateGitHubDeployments!.Value)
Logger.LogTrace("Not creating deployment");
else if (!instanceAuthenticated)
Logger.LogWarning("Can't create GitHub deployment as no access token is set for repository!");
@@ -102,7 +104,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
Logger.LogTrace("Creating deployment...");
try
{
- compileJob.GitHubDeploymentId = await authenticatedGitHubService.CreateDeployment(
+ compileJob.GitHubDeploymentId = await authenticatedGitHubService!.CreateDeployment(
new NewDeployment(compileJob.RevisionInformation.CommitSha)
{
AutoMerge = false,
@@ -111,8 +113,8 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
ProductionEnvironment = true,
RequiredContexts = new Collection(),
},
- remoteInformation.RemoteRepositoryOwner,
- remoteInformation.RemoteRepositoryName,
+ repoOwner,
+ repoName,
cancellationToken);
Logger.LogDebug("Created deployment ID {deploymentId}", compileJob.GitHubDeploymentId);
@@ -123,8 +125,8 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
Description = "The project is being deployed",
AutoInactive = false,
},
- remoteInformation.RemoteRepositoryOwner,
- remoteInformation.RemoteRepositoryName,
+ repoOwner,
+ repoName,
compileJob.GitHubDeploymentId.Value,
cancellationToken);
@@ -166,7 +168,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
ArgumentNullException.ThrowIfNull(repositorySettings);
ArgumentNullException.ThrowIfNull(revisionInformation);
- if (revisionInformation.ActiveTestMerges?.Any() != true)
+ if ((revisionInformation.ActiveTestMerges?.Count > 0) != true)
{
Logger.LogTrace("No test merges to remove.");
return Array.Empty();
@@ -178,7 +180,11 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
var tasks = revisionInformation
.ActiveTestMerges
- .Select(x => gitHubService.GetPullRequest(repository.RemoteRepositoryOwner, repository.RemoteRepositoryName, x.TestMerge.Number, cancellationToken));
+ .Select(x => gitHubService.GetPullRequest(
+ repository.RemoteRepositoryOwner!,
+ repository.RemoteRepositoryName!,
+ x.TestMerge.Number,
+ cancellationToken));
try
{
await Task.WhenAll(tasks);
@@ -190,7 +196,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
var newList = revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList();
- PullRequest lastMerged = null;
+ PullRequest? lastMerged = null;
async ValueTask CheckRemovePR(Task task)
{
var pr = await task;
@@ -249,7 +255,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
int testMergeNumber,
CancellationToken cancellationToken)
{
- var gitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken);
+ var gitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken!);
try
{
@@ -272,12 +278,12 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
CultureInfo.InvariantCulture,
"#### Test Merge {4}{0}{0}Details
{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}{8}{0} ",
Environment.NewLine,
- repositorySettings.ShowTestMergeCommitters.Value
+ repositorySettings.ShowTestMergeCommitters!.Value
? String.Format(
CultureInfo.InvariantCulture,
"{0}{0}##### Merged By{0}{1}",
Environment.NewLine,
- testMerge.MergedBy.Name)
+ testMerge.MergedBy!.Name)
: String.Empty,
testMerge.TargetCommitSha,
testMerge.Comment != null
@@ -292,7 +298,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
compileJob.RevisionInformation.OriginCommitSha,
compileJob.RevisionInformation.CommitSha,
compileJob.GitHubDeploymentId.HasValue
- ? $"{Environment.NewLine}[GitHub Deployments](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A+{Metadata.Name.Replace(" ", "+", StringComparison.Ordinal)})"
+ ? $"{Environment.NewLine}[GitHub Deployments](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A+{Metadata.Name!.Replace(" ", "+", StringComparison.Ordinal)})"
: String.Empty);
///
@@ -319,7 +325,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
Logger.LogTrace("Updating deployment {gitHubDeploymentId} to {deploymentState}...", compileJob.GitHubDeploymentId.Value, deploymentState);
- string gitHubAccessToken = null;
+ string? gitHubAccessToken = null;
await databaseContextFactory.UseContext(
async databaseContext =>
gitHubAccessToken = await databaseContext
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs
index 2686764cca..9d0adeaf37 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs
@@ -46,7 +46,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
ArgumentNullException.ThrowIfNull(repositorySettings);
ArgumentNullException.ThrowIfNull(revisionInformation);
- if (revisionInformation.ActiveTestMerges?.Any() != true)
+ if ((revisionInformation.ActiveTestMerges?.Count > 0) != true)
{
Logger.LogTrace("No test merges to remove.");
return Array.Empty();
@@ -75,7 +75,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
var newList = revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList();
- MergeRequest lastMerged = null;
+ MergeRequest? lastMerged = null;
async ValueTask CheckRemoveMR(Task task)
{
var mergeRequest = await task;
@@ -162,12 +162,12 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
CultureInfo.InvariantCulture,
"#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Merge Request: {2}{0}Server: {7}{3}",
Environment.NewLine,
- repositorySettings.ShowTestMergeCommitters.Value
+ repositorySettings.ShowTestMergeCommitters!.Value
? String.Format(
CultureInfo.InvariantCulture,
"{0}{0}##### Merged By{0}{1}",
Environment.NewLine,
- testMerge.MergedBy.Name)
+ testMerge.MergedBy!.Name)
: String.Empty,
testMerge.TargetCommitSha,
testMerge.Comment != null
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs
index 42b154ac7e..d8017a6686 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs
@@ -34,7 +34,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
/// A representing the running operation.
ValueTask StageDeployment(
CompileJob compileJob,
- Action activationCallback,
+ Action? activationCallback,
CancellationToken cancellationToken);
///
@@ -66,18 +66,18 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
/// Post deployment comments to the test merge ticket.
///
/// The deployed .
- /// The of the previous deployment.
+ /// The optional of the previous deployment.
/// The .
- /// The GitHub repostiory owner.
- /// The GitHub repostiory name.
+ /// The remote repostiory owner.
+ /// The remote repostiory name.
/// The for the operation.
/// A representing the running operation.
ValueTask PostDeploymentComments(
CompileJob compileJob,
- RevisionInformation previousRevisionInformation,
+ RevisionInformation? previousRevisionInformation,
RepositorySettings repositorySettings,
- string repoOwner,
- string repoName,
+ string? repoOwner,
+ string? repoName,
CancellationToken cancellationToken);
///
diff --git a/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs
index ba730a7c7f..fce9b2c5bc 100644
--- a/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/ByondInstallation.cs
@@ -92,8 +92,8 @@ namespace Tgstation.Server.Host.Components.Engine
InstallationTask = installationTask ?? throw new ArgumentNullException(nameof(installationTask));
ArgumentNullException.ThrowIfNull(version);
- if (version.Engine.Value != EngineType.Byond)
- throw new ArgumentException($"Invalid EngineType: {version.Engine.Value}", nameof(version));
+ if (version.Engine != EngineType.Byond)
+ throw new ArgumentException($"Invalid EngineType: {version.Engine}", nameof(version));
Version = version ?? throw new ArgumentNullException(nameof(version));
ServerExePath = dreamDaemonPath ?? throw new ArgumentNullException(nameof(dreamDaemonPath));
@@ -108,7 +108,7 @@ namespace Tgstation.Server.Host.Components.Engine
IDmbProvider dmbProvider,
IReadOnlyDictionary parameters,
DreamDaemonLaunchParameters launchParameters,
- string logFilePath)
+ string? logFilePath)
{
ArgumentNullException.ThrowIfNull(dmbProvider);
ArgumentNullException.ThrowIfNull(parameters);
@@ -120,19 +120,19 @@ namespace Tgstation.Server.Host.Components.Engine
CultureInfo.InvariantCulture,
"{0} -port {1} -ports 1-65535 {2}-close -verbose -{3} -{4}{5}{6}{7} -params \"{8}\"",
dmbProvider.DmbName,
- launchParameters.Port.Value,
- launchParameters.AllowWebClient.Value
+ launchParameters.Port!.Value,
+ launchParameters.AllowWebClient!.Value
? "-webclient "
: String.Empty,
- SecurityWord(launchParameters.SecurityLevel.Value),
- VisibilityWord(launchParameters.Visibility.Value),
+ SecurityWord(launchParameters.SecurityLevel!.Value),
+ VisibilityWord(launchParameters.Visibility!.Value),
logFilePath != null
? $" -logself -log {logFilePath}"
: String.Empty, // DD doesn't output anything if -logself is set???
- launchParameters.StartProfiler.Value
+ launchParameters.StartProfiler!.Value
? " -profile"
: String.Empty,
- supportsMapThreads && launchParameters.MapThreads.Value != 0
+ supportsMapThreads && launchParameters.MapThreads!.Value != 0
? $" -map-threads {launchParameters.MapThreads.Value}"
: String.Empty,
parametersString);
diff --git a/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs b/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs
index f02239969f..02691acfc0 100644
--- a/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/ByondInstallerBase.cs
@@ -98,7 +98,7 @@ namespace Tgstation.Server.Host.Components.Engine
IOManager.ConcatPath(
binPathForVersion,
GetDreamDaemonName(
- version.Version,
+ version.Version!,
out var supportsCli))),
IOManager.ResolvePath(
IOManager.ConcatPath(
@@ -194,11 +194,11 @@ namespace Tgstation.Server.Host.Components.Engine
}
///
- public override async ValueTask DownloadVersion(EngineVersion version, JobProgressReporter progressReporter, CancellationToken cancellationToken)
+ public override async ValueTask DownloadVersion(EngineVersion version, JobProgressReporter? progressReporter, CancellationToken cancellationToken)
{
CheckVersionValidity(version);
- var url = await GetDownloadZipUrl(version, cancellationToken);
+ var url = GetDownloadZipUrl(version);
Logger.LogTrace("Downloading {engineType} version {version} from {url}...", TargetEngineType, version, url);
await using var download = fileDownloader.DownloadFile(url, null);
@@ -231,13 +231,12 @@ namespace Tgstation.Server.Host.Components.Engine
/// Create a pointing to the location of the download for a given .
///
/// The to create a for.
- /// The for the operation.
- /// A resulting in a new pointing to the version download location.
- ValueTask GetDownloadZipUrl(EngineVersion version, CancellationToken cancellationToken)
+ /// A pointing to the version download location.
+ Uri GetDownloadZipUrl(EngineVersion version)
{
CheckVersionValidity(version);
- var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsUrlTemplate, version.Version.Major, version.Version.Minor);
- return ValueTask.FromResult(new Uri(url));
+ var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsUrlTemplate, version.Version!.Major, version.Version.Minor);
+ return new Uri(url);
}
}
}
diff --git a/src/Tgstation.Server.Host/Components/Engine/DelegatingEngineInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/DelegatingEngineInstaller.cs
index 75393af5b7..1157dff565 100644
--- a/src/Tgstation.Server.Host/Components/Engine/DelegatingEngineInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/DelegatingEngineInstaller.cs
@@ -1,5 +1,5 @@
using System;
-using System.Collections.Generic;
+using System.Collections.Frozen;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -15,15 +15,15 @@ namespace Tgstation.Server.Host.Components.Engine
sealed class DelegatingEngineInstaller : IEngineInstaller
{
///
- /// The mapping s to their appropriate .
+ /// The mapping s to their appropriate .
///
- readonly IReadOnlyDictionary delegatedInstallers;
+ readonly FrozenDictionary delegatedInstallers;
///
/// Initializes a new instance of the class.
///
/// The value of .
- public DelegatingEngineInstaller(IReadOnlyDictionary delegatedInstallers)
+ public DelegatingEngineInstaller(FrozenDictionary delegatedInstallers)
{
this.delegatedInstallers = delegatedInstallers ?? throw new ArgumentNullException(nameof(delegatedInstallers));
}
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components.Engine
=> DelegateCall(version, installer => installer.CreateInstallation(version, path, installationTask));
///
- public ValueTask DownloadVersion(EngineVersion version, JobProgressReporter jobProgressReporter, CancellationToken cancellationToken)
+ public ValueTask DownloadVersion(EngineVersion version, JobProgressReporter? jobProgressReporter, CancellationToken cancellationToken)
=> DelegateCall(version, installer => installer.DownloadVersion(version, jobProgressReporter, cancellationToken));
///
@@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Components.Engine
TReturn DelegateCall(EngineVersion version, Func call)
{
ArgumentNullException.ThrowIfNull(version);
- return call(delegatedInstallers[version.Engine.Value]);
+ return call(delegatedInstallers[version.Engine!.Value]);
}
}
}
diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs b/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs
index 70614f13e1..5d7a1d7fef 100644
--- a/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/EngineExecutableLock.cs
@@ -1,15 +1,19 @@
using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Deployment;
+using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Engine
{
///
- sealed class EngineExecutableLock : ReferenceCounter, IEngineExecutableLock
+ class EngineExecutableLock : ReferenceCounter, IEngineExecutableLock
{
///
public EngineVersion Version => Instance.Version;
@@ -40,7 +44,7 @@ namespace Tgstation.Server.Host.Components.Engine
IDmbProvider dmbProvider,
IReadOnlyDictionary parameters,
DreamDaemonLaunchParameters launchParameters,
- string logFilePath)
+ string? logFilePath)
=> Instance.FormatServerArguments(
dmbProvider,
parameters,
@@ -49,5 +53,14 @@ namespace Tgstation.Server.Host.Components.Engine
///
public string FormatCompilerArguments(string dmePath) => Instance.FormatCompilerArguments(dmePath);
+
+ ///
+ public ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken)
+ => Instance.StopServerProcess(
+ logger,
+ process,
+ accessIdentifier,
+ port,
+ cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs b/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs
index fb607d3601..5edf44609f 100644
--- a/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/EngineInstallationBase.cs
@@ -1,12 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using System.Web;
+using Microsoft.Extensions.Logging;
+
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Deployment;
+using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Components.Engine
{
@@ -56,6 +60,19 @@ namespace Tgstation.Server.Host.Components.Engine
public abstract string FormatCompilerArguments(string dmePath);
///
- public abstract string FormatServerArguments(IDmbProvider dmbProvider, IReadOnlyDictionary parameters, DreamDaemonLaunchParameters launchParameters, string logFilePath);
+ public abstract string FormatServerArguments(
+ IDmbProvider dmbProvider,
+ IReadOnlyDictionary parameters,
+ DreamDaemonLaunchParameters launchParameters,
+ string? logFilePath);
+
+ ///
+ public virtual async ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ logger.LogTrace("Terminating engine server process...");
+ process.Terminate();
+ await process.Lifetime;
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineInstallerBase.cs b/src/Tgstation.Server.Host/Components/Engine/EngineInstallerBase.cs
index e281c3f48f..7c25c0a13c 100644
--- a/src/Tgstation.Server.Host/Components/Engine/EngineInstallerBase.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/EngineInstallerBase.cs
@@ -52,7 +52,7 @@ namespace Tgstation.Server.Host.Components.Engine
public abstract ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken);
///
- public abstract ValueTask DownloadVersion(EngineVersion version, JobProgressReporter jobProgressReporter, CancellationToken cancellationToken);
+ public abstract ValueTask DownloadVersion(EngineVersion version, JobProgressReporter? jobProgressReporter, CancellationToken cancellationToken);
///
public abstract ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken);
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Components.Engine
protected void CheckVersionValidity(EngineVersion version)
{
ArgumentNullException.ThrowIfNull(version);
- if (version.Engine.Value != TargetEngineType)
+ if (version.Engine!.Value != TargetEngineType)
throw new InvalidOperationException($"Non-{TargetEngineType} engine specified: {version.Engine.Value}");
}
}
diff --git a/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs b/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs
index 1205120411..529eec0fb8 100644
--- a/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/EngineManager.cs
@@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Components.Engine
const string ActiveVersionFileName = "ActiveVersion.txt";
///
- public EngineVersion ActiveVersion { get; private set; }
+ public EngineVersion? ActiveVersion { get; private set; }
///
public IReadOnlyList InstalledVersions
@@ -118,9 +118,9 @@ namespace Tgstation.Server.Host.Components.Engine
///
public async ValueTask ChangeVersion(
- JobProgressReporter progressReporter,
+ JobProgressReporter? progressReporter,
EngineVersion version,
- Stream customVersionStream,
+ Stream? customVersionStream,
bool allowInstallation,
CancellationToken cancellationToken)
{
@@ -143,7 +143,7 @@ namespace Tgstation.Server.Host.Components.Engine
await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(stringVersion), cancellationToken);
await eventConsumer.HandleEvent(
EventType.EngineActiveVersionChange,
- new List
+ new List
{
ActiveVersion?.ToString(),
stringVersion,
@@ -160,7 +160,7 @@ namespace Tgstation.Server.Host.Components.Engine
}
///
- public async ValueTask UseExecutables(EngineVersion requiredVersion, string trustDmbFullPath, CancellationToken cancellationToken)
+ public async ValueTask UseExecutables(EngineVersion? requiredVersion, string? trustDmbFullPath, CancellationToken cancellationToken)
{
logger.LogTrace(
"Acquiring lock on BYOND version {version}...",
@@ -196,19 +196,21 @@ namespace Tgstation.Server.Host.Components.Engine
logger.LogTrace("DeleteVersion {version}", version);
- if (version.Equals(ActiveVersion))
+ var activeVersion = ActiveVersion;
+ if (activeVersion != null && version.Equals(activeVersion))
throw new JobException(ErrorCode.EngineCannotDeleteActiveVersion);
ReferenceCountingContainer container;
logger.LogTrace("Waiting to acquire installedVersions lock...");
lock (installedVersions)
{
- if (!installedVersions.TryGetValue(version, out container))
+ if (!installedVersions.TryGetValue(version, out var containerNullable))
{
logger.LogTrace("Version {version} already deleted.", version);
return;
}
+ container = containerNullable;
logger.LogTrace("Installation container acquired for deletion");
}
@@ -236,7 +238,8 @@ namespace Tgstation.Server.Host.Components.Engine
using (await SemaphoreSlimContext.Lock(changeDeleteSemaphore, cancellationToken))
{
// check again because it could have become the active version.
- if (version.Equals(ActiveVersion))
+ activeVersion = ActiveVersion;
+ if (activeVersion != null && version.Equals(activeVersion))
throw new JobException(ErrorCode.EngineCannotDeleteActiveVersion);
bool proceed;
@@ -294,7 +297,7 @@ namespace Tgstation.Server.Host.Components.Engine
///
public async Task StartAsync(CancellationToken cancellationToken)
{
- async ValueTask GetActiveVersion()
+ async ValueTask GetActiveVersion()
{
var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken);
return !activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken);
@@ -319,12 +322,15 @@ namespace Tgstation.Server.Host.Components.Engine
var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken);
var text = Encoding.UTF8.GetString(bytes);
- if (!EngineVersion.TryParse(text, out var version))
+ EngineVersion version;
+ if (!EngineVersion.TryParse(text, out var versionNullable))
{
logger.LogWarning("Cleaning path with unparsable version file: {versionPath}", ioManager.ResolvePath(path));
await ioManager.DeleteDirectory(path, cancellationToken); // cleanup
return;
}
+ else
+ version = versionNullable!;
try
{
@@ -360,11 +366,11 @@ namespace Tgstation.Server.Host.Components.Engine
{
var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
- EngineVersion activeVersion;
+ EngineVersion? activeVersion;
bool hasRequestedActiveVersion;
lock (installedVersions)
hasRequestedActiveVersion = EngineVersion.TryParse(activeVersionString, out activeVersion)
- && installedVersions.ContainsKey(activeVersion);
+ && installedVersions.ContainsKey(activeVersion!);
if (hasRequestedActiveVersion)
ActiveVersion = activeVersion; // not setting TCS because there's no need during init
@@ -384,15 +390,15 @@ namespace Tgstation.Server.Host.Components.Engine
///
/// The optional for the operation.
/// The to install.
- /// Custom zip file to use. Will cause a number to be added.
+ /// Optional custom zip file to use. Will cause a number to be added.
/// If this BYOND version is required as part of a locking operation.
/// If an installation should be performed if the is not installed. If and an installation is required an will be thrown.
/// The for the operation.
/// A resulting in the .
async ValueTask AssertAndLockVersion(
- JobProgressReporter progressReporter,
+ JobProgressReporter? progressReporter,
EngineVersion version,
- Stream customVersionStream,
+ Stream? customVersionStream,
bool neededForLock,
bool allowInstallation,
CancellationToken cancellationToken)
@@ -413,7 +419,8 @@ namespace Tgstation.Server.Host.Components.Engine
while (installedVersions.ContainsKey(version));
}
- installedOrInstalling = installedVersions.TryGetValue(version, out var installationContainer);
+ installedOrInstalling = installedVersions.TryGetValue(version, out var installationContainerNullable);
+ ReferenceCountingContainer installationContainer;
if (!installedOrInstalling)
{
if (!allowInstallation)
@@ -424,6 +431,8 @@ namespace Tgstation.Server.Host.Components.Engine
ioManager.ResolvePath(version.ToString()),
ourTcs.Task);
}
+ else
+ installationContainer = installationContainerNullable!;
installation = installationContainer.Instance;
installLock = installationContainer.AddReference();
@@ -497,7 +506,7 @@ namespace Tgstation.Server.Host.Components.Engine
/// Custom zip file to use. Will cause a number to be added.
/// The for the operation.
/// A representing the running operation.
- async ValueTask InstallVersionFiles(JobProgressReporter progressReporter, EngineVersion version, Stream customVersionStream, CancellationToken cancellationToken)
+ async ValueTask InstallVersionFiles(JobProgressReporter? progressReporter, EngineVersion version, Stream? customVersionStream, CancellationToken cancellationToken)
{
var installFullPath = ioManager.ResolvePath(version.ToString());
async ValueTask DirectoryCleanup()
@@ -517,7 +526,7 @@ namespace Tgstation.Server.Host.Components.Engine
engineInstallationData = await engineInstaller.DownloadVersion(version, progressReporter, cancellationToken);
- progressReporter.ReportProgress(null);
+ progressReporter?.ReportProgress(null);
}
else
#pragma warning disable CA2000 // Dispose objects before losing scope, false positive
diff --git a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs
index f9aecca33b..ff2e4155f3 100644
--- a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallation.cs
@@ -1,9 +1,13 @@
using System.Collections.Generic;
+using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Deployment;
+using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Components.Engine
{
@@ -51,7 +55,7 @@ namespace Tgstation.Server.Host.Components.Engine
/// Return the command line arguments for launching with given .
///
/// The .
- /// The map of parameter s as a . Should NOT include the of .
+ /// The map of parameter s as a . MUST include . Should NOT include the of .
/// The .
/// The full path to the log file, if any.
/// The formatted arguments .
@@ -59,7 +63,7 @@ namespace Tgstation.Server.Host.Components.Engine
IDmbProvider dmbProvider,
IReadOnlyDictionary parameters,
DreamDaemonLaunchParameters launchParameters,
- string logFilePath);
+ string? logFilePath);
///
/// Return the command line arguments for compiling a given if compilation is necessary.
@@ -67,5 +71,16 @@ namespace Tgstation.Server.Host.Components.Engine
/// The full path to the .dme to compile.
/// The formatted arguments .
string FormatCompilerArguments(string dmePath);
+
+ ///
+ /// Kills a given engine server .
+ ///
+ /// The to write to.
+ /// The to be terminated.
+ /// The of the session.
+ /// The port the server is running on.
+ /// The for the operation.
+ /// A representing the running operation.
+ ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Engine/IEngineInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/IEngineInstaller.cs
index 2360202584..7169ffe4b5 100644
--- a/src/Tgstation.Server.Host/Components/Engine/IEngineInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/IEngineInstaller.cs
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Components.Engine
/// The optional for the operation.
/// The for the operation.
/// A resulting in the for the download.
- ValueTask DownloadVersion(EngineVersion version, JobProgressReporter jobProgressReporter, CancellationToken cancellationToken);
+ ValueTask DownloadVersion(EngineVersion version, JobProgressReporter? jobProgressReporter, CancellationToken cancellationToken);
///
/// Does actions necessary to get an extracted installation working.
diff --git a/src/Tgstation.Server.Host/Components/Engine/IEngineManager.cs b/src/Tgstation.Server.Host/Components/Engine/IEngineManager.cs
index bc272b1038..18af1469e6 100644
--- a/src/Tgstation.Server.Host/Components/Engine/IEngineManager.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/IEngineManager.cs
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components.Engine
///
/// The currently active .
///
- EngineVersion ActiveVersion { get; }
+ EngineVersion? ActiveVersion { get; }
///
/// The installed s.
@@ -35,9 +35,9 @@ namespace Tgstation.Server.Host.Components.Engine
/// The for the operation.
/// A representing the running operation.
ValueTask ChangeVersion(
- JobProgressReporter progressReporter,
+ JobProgressReporter? progressReporter,
EngineVersion version,
- Stream customVersionStream,
+ Stream? customVersionStream,
bool allowInstallation,
CancellationToken cancellationToken);
@@ -58,8 +58,8 @@ namespace Tgstation.Server.Host.Components.Engine
/// The for the operation.
/// A resulting in the requested .
ValueTask UseExecutables(
- EngineVersion requiredVersion,
- string trustDmbFullPath,
+ EngineVersion? requiredVersion,
+ string? trustDmbFullPath,
CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs
index 254861e69a..ab9eb923d8 100644
--- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstallation.cs
@@ -1,11 +1,23 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Text;
+using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
+using Tgstation.Server.Common.Http;
using Tgstation.Server.Host.Components.Deployment;
+using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.IO;
+using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Engine
{
@@ -40,28 +52,44 @@ namespace Tgstation.Server.Host.Components.Engine
///
readonly IIOManager ioManager;
+ ///
+ /// The for the .
+ ///
+ readonly IAsyncDelayer asyncDelayer;
+
+ ///
+ /// The for the .
+ ///
+ readonly IAbstractHttpClientFactory httpClientFactory;
+
///
/// Initializes a new instance of the class.
///
/// The value of .
+ /// The value of .
+ /// The value of .
/// The value of .
/// The value of .
/// The value of .
/// The value of .
public OpenDreamInstallation(
IIOManager ioManager,
+ IAsyncDelayer asyncDelayer,
+ IAbstractHttpClientFactory httpClientFactory,
string serverExePath,
string compilerExePath,
Task installationTask,
EngineVersion version)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
+ this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
+ this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
ServerExePath = serverExePath ?? throw new ArgumentNullException(nameof(serverExePath));
CompilerExePath = compilerExePath ?? throw new ArgumentNullException(nameof(compilerExePath));
InstallationTask = installationTask ?? throw new ArgumentNullException(nameof(installationTask));
Version = version ?? throw new ArgumentNullException(nameof(version));
- if (version.Engine.Value != EngineType.OpenDream)
+ if (version.Engine!.Value != EngineType.OpenDream)
throw new ArgumentException($"Invalid EngineType: {version.Engine.Value}", nameof(version));
}
@@ -70,21 +98,86 @@ namespace Tgstation.Server.Host.Components.Engine
IDmbProvider dmbProvider,
IReadOnlyDictionary parameters,
DreamDaemonLaunchParameters launchParameters,
- string logFilePath)
+ string? logFilePath)
{
ArgumentNullException.ThrowIfNull(dmbProvider);
ArgumentNullException.ThrowIfNull(parameters);
ArgumentNullException.ThrowIfNull(launchParameters);
+ if (!parameters.TryGetValue(DMApiConstants.ParamAccessIdentifier, out var accessIdentifier))
+ throw new ArgumentException($"parameters must have \"{DMApiConstants.ParamAccessIdentifier}\" set!", nameof(parameters));
+
var parametersString = EncodeParameters(parameters, launchParameters);
- var loggingEnabled = logFilePath != null;
- var arguments = $"--cvar {(loggingEnabled ? $"log.path=\"{ioManager.GetDirectoryName(logFilePath)}\" --cvar log.format=\"{ioManager.GetFileName(logFilePath)}\"" : "log.enabled=false")} --cvar log.runtimelog=false --cvar net.port={launchParameters.Port.Value} --cvar opendream.topic_port=0 --cvar opendream.world_params=\"{parametersString}\" --cvar opendream.json_path=\"./{dmbProvider.DmbName}\"";
+ var arguments = $"--cvar {(logFilePath != null ? $"log.path=\"{ioManager.GetDirectoryName(logFilePath)}\" --cvar log.format=\"{ioManager.GetFileName(logFilePath)}\"" : "log.enabled=false")} --cvar watchdog.token={accessIdentifier} --cvar log.runtimelog=false --cvar net.port={launchParameters.Port!.Value} --cvar opendream.topic_port=0 --cvar opendream.world_params=\"{parametersString}\" --cvar opendream.json_path=\"./{dmbProvider.DmbName}\"";
return arguments;
}
///
public override string FormatCompilerArguments(string dmePath)
=> $"--suppress-unimplemented --notices-enabled \"{dmePath ?? throw new ArgumentNullException(nameof(dmePath))}\"";
+
+ ///
+ public override async ValueTask StopServerProcess(
+ ILogger logger,
+ IProcess process,
+ string accessIdentifier,
+ ushort port,
+ CancellationToken cancellationToken)
+ {
+ const int MaximumTerminationSeconds = 5;
+
+ logger.LogTrace("Attempting Robust.Server graceful exit (Timeout: {seconds}s)...", MaximumTerminationSeconds);
+ var timeout = asyncDelayer.Delay(TimeSpan.FromSeconds(MaximumTerminationSeconds), cancellationToken);
+ var lifetime = process.Lifetime;
+
+ var stopwatch = Stopwatch.StartNew();
+ try
+ {
+ using var httpClient = httpClientFactory.CreateClient();
+ using var request = new HttpRequestMessage();
+ request.Headers.Add("WatchdogToken", accessIdentifier);
+ request.RequestUri = new Uri($"http://localhost:{port}/shutdown");
+ request.Content = new StringContent(
+ "{\"Reason\":\"TGS session termination\"}",
+ Encoding.UTF8,
+ new MediaTypeHeaderValue(MediaTypeNames.Application.Json));
+ request.Method = HttpMethod.Post;
+
+ var responseTask = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+ try
+ {
+ await Task.WhenAny(timeout, lifetime, responseTask);
+ if (responseTask.IsCompleted)
+ {
+ using var response = await responseTask;
+ if (response.IsSuccessStatusCode)
+ {
+ logger.LogDebug("Robust.Server responded to the shutdown command successfully ({requestMs}ms). Waiting for exit...", stopwatch.ElapsedMilliseconds);
+ await Task.WhenAny(timeout, lifetime);
+ }
+ }
+
+ if (!lifetime.IsCompleted)
+ logger.LogWarning("Robust.Server graceful exit timed out!");
+ }
+ catch (Exception ex) when (ex is not OperationCanceledException)
+ {
+ logger.LogDebug(ex, "Unable to send graceful exit request to Robust.Server watchdog API!");
+ }
+
+ if (lifetime.IsCompleted)
+ {
+ logger.LogTrace("Robust.Server exited without termination");
+ return;
+ }
+ }
+ finally
+ {
+ logger.LogTrace("Robust.Server graceful shutdown attempt took {totalMs}ms", stopwatch.ElapsedMilliseconds);
+ }
+
+ await base.StopServerProcess(logger, process, accessIdentifier, port, cancellationToken);
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs
index b8ffc98334..a2b74e1445 100644
--- a/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/OpenDreamInstaller.cs
@@ -8,12 +8,14 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Common.Extensions;
+using Tgstation.Server.Common.Http;
using Tgstation.Server.Host.Common;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Engine
{
@@ -50,6 +52,16 @@ namespace Tgstation.Server.Host.Components.Engine
///
protected IProcessExecutor ProcessExecutor { get; }
+ ///
+ /// The for the .
+ ///
+ protected GeneralConfiguration GeneralConfiguration { get; }
+
+ ///
+ /// The for the .
+ ///
+ protected SessionConfiguration SessionConfiguration { get; }
+
///
/// The for the .
///
@@ -61,9 +73,14 @@ namespace Tgstation.Server.Host.Components.Engine
readonly IRepositoryManager repositoryManager;
///
- /// The for the .
+ /// The for the .
///
- protected GeneralConfiguration GeneralConfiguration { get; }
+ readonly IAsyncDelayer asyncDelayer;
+
+ ///
+ /// The for the .
+ ///
+ readonly IAbstractHttpClientFactory httpClientFactory;
///
/// Initializes a new instance of the class.
@@ -73,20 +90,29 @@ namespace Tgstation.Server.Host.Components.Engine
/// The value of .
/// The value of .
/// The value of .
+ /// The value of .
+ /// The value of .
/// The containing value of .
+ /// The containing value of .
public OpenDreamInstaller(
IIOManager ioManager,
ILogger logger,
IPlatformIdentifier platformIdentifier,
IProcessExecutor processExecutor,
IRepositoryManager repositoryManager,
- IOptions generalConfigurationOptions)
+ IAsyncDelayer asyncDelayer,
+ IAbstractHttpClientFactory httpClientFactory,
+ IOptions generalConfigurationOptions,
+ IOptions sessionConfigurationOptions)
: base(ioManager, logger)
{
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
ProcessExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
+ this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
+ this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
GeneralConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
+ SessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
}
///
@@ -99,6 +125,8 @@ namespace Tgstation.Server.Host.Components.Engine
GetExecutablePaths(path, out var serverExePath, out var compilerExePath);
return new OpenDreamInstallation(
IOManager,
+ asyncDelayer,
+ httpClientFactory,
serverExePath,
compilerExePath,
installationTask,
@@ -106,7 +134,7 @@ namespace Tgstation.Server.Host.Components.Engine
}
///
- public override async ValueTask DownloadVersion(EngineVersion version, JobProgressReporter jobProgressReporter, CancellationToken cancellationToken)
+ public override async ValueTask DownloadVersion(EngineVersion version, JobProgressReporter? jobProgressReporter, CancellationToken cancellationToken)
{
CheckVersionValidity(version);
@@ -131,7 +159,7 @@ namespace Tgstation.Server.Host.Components.Engine
Logger.LogTrace("OD repo seems to already exist, attempting load and fetch...");
repo = await repositoryManager.LoadRepository(cancellationToken);
- await repo.FetchOrigin(
+ await repo!.FetchOrigin(
progressSection1,
null,
null,
@@ -142,7 +170,7 @@ namespace Tgstation.Server.Host.Components.Engine
var progressSection2 = jobProgressReporter?.CreateSection("Checking out OpenDream version", 0.5f);
var committish = version.SourceSHA
- ?? $"{GeneralConfiguration.OpenDreamGitTagPrefix}{version.Version.Semver()}";
+ ?? $"{GeneralConfiguration.OpenDreamGitTagPrefix}{version.Version!.Semver()}";
await repo.CheckoutObject(
committish,
@@ -232,11 +260,14 @@ namespace Tgstation.Server.Host.Components.Engine
null,
!GeneralConfiguration.OpenDreamSuppressInstallOutput,
!GeneralConfiguration.OpenDreamSuppressInstallOutput);
+
+ if (SessionConfiguration.LowPriorityDeploymentProcesses)
+ buildProcess.AdjustPriority(false);
+
using (cancellationToken.Register(() => buildProcess.Terminate()))
buildExitCode = await buildProcess.Lifetime;
- string output;
-
+ string? output;
if (!GeneralConfiguration.OpenDreamSuppressInstallOutput)
{
var buildOutputTask = buildProcess.GetCombinedOutput(cancellationToken);
diff --git a/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs
index f3c9c561b1..037cacd437 100644
--- a/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/PosixByondInstaller.cs
@@ -93,7 +93,7 @@ namespace Tgstation.Server.Host.Components.Engine
var basePath = IOManager.ConcatPath(path, ByondBinPath);
var ddTask = WriteAndMakeExecutable(
- IOManager.ConcatPath(basePath, GetDreamDaemonName(version.Version, out _)),
+ IOManager.ConcatPath(basePath, GetDreamDaemonName(version.Version!, out _)),
dreamDaemonScript);
var dmTask = WriteAndMakeExecutable(
diff --git a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs
index 0f40d3f538..87424718d5 100644
--- a/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/WindowsByondInstaller.cs
@@ -71,6 +71,11 @@ namespace Tgstation.Server.Host.Components.Engine
///
readonly GeneralConfiguration generalConfiguration;
+ ///
+ /// The for the .
+ ///
+ readonly SessionConfiguration sessionConfiguration;
+
///
/// The for the .
///
@@ -86,6 +91,7 @@ namespace Tgstation.Server.Host.Components.Engine
///
/// The value of .
/// The containing the value of .
+ /// The containing the value of .
/// The for the .
/// The for the .
/// The for the .
@@ -94,11 +100,13 @@ namespace Tgstation.Server.Host.Components.Engine
IIOManager ioManager,
IFileDownloader fileDownloader,
IOptions generalConfigurationOptions,
+ IOptions sessionConfigurationOptions,
ILogger logger)
: base(ioManager, logger, fileDownloader)
{
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
+ sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
var documentsDirectory = Environment.GetFolderPath(
Environment.SpecialFolder.MyDocuments,
@@ -120,14 +128,19 @@ namespace Tgstation.Server.Host.Components.Engine
CheckVersionValidity(version);
ArgumentNullException.ThrowIfNull(path);
+ var noPromptTrustedTask = SetNoPromptTrusted(path, cancellationToken);
+ var installDirectXTask = InstallDirectX(path, cancellationToken);
var tasks = new List(3)
{
- SetNoPromptTrusted(path, cancellationToken),
- InstallDirectX(path, cancellationToken),
+ noPromptTrustedTask,
+ installDirectXTask,
};
if (!generalConfiguration.SkipAddingByondFirewallException)
- tasks.Add(AddDreamDaemonToFirewall(version, path, cancellationToken));
+ {
+ var firewallTask = AddDreamDaemonToFirewall(version, path, cancellationToken);
+ tasks.Add(firewallTask);
+ }
return ValueTaskExtensions.WhenAll(tasks);
}
@@ -230,7 +243,7 @@ namespace Tgstation.Server.Host.Components.Engine
/// A representing the running operation.
async ValueTask AddDreamDaemonToFirewall(EngineVersion version, string path, CancellationToken cancellationToken)
{
- var dreamDaemonName = GetDreamDaemonName(version.Version, out var usesDDExe);
+ var dreamDaemonName = GetDreamDaemonName(version.Version!, out var usesDDExe);
var dreamDaemonPath = IOManager.ResolvePath(
IOManager.ConcatPath(
@@ -251,6 +264,7 @@ namespace Tgstation.Server.Host.Components.Engine
Logger,
ruleName,
dreamDaemonPath,
+ sessionConfiguration.LowPriorityDeploymentProcesses,
cancellationToken);
}
catch (Exception ex)
diff --git a/src/Tgstation.Server.Host/Components/Engine/WindowsOpenDreamInstaller.cs b/src/Tgstation.Server.Host/Components/Engine/WindowsOpenDreamInstaller.cs
index c97ca10418..25968446fe 100644
--- a/src/Tgstation.Server.Host/Components/Engine/WindowsOpenDreamInstaller.cs
+++ b/src/Tgstation.Server.Host/Components/Engine/WindowsOpenDreamInstaller.cs
@@ -7,11 +7,13 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Common.Extensions;
+using Tgstation.Server.Common.Http;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Engine
{
@@ -33,7 +35,10 @@ namespace Tgstation.Server.Host.Components.Engine
/// The for the .
/// The for the .
/// The for the .
+ /// The for the .
+ /// The for the .
/// The of for the .
+ /// The of for the .
/// The value of .
public WindowsOpenDreamInstaller(
IIOManager ioManager,
@@ -41,7 +46,10 @@ namespace Tgstation.Server.Host.Components.Engine
IPlatformIdentifier platformIdentifier,
IProcessExecutor processExecutor,
IRepositoryManager repositoryManager,
+ IAsyncDelayer asyncDelayer,
+ IAbstractHttpClientFactory httpClientFactory,
IOptions generalConfigurationOptions,
+ IOptions sessionConfigurationOptions,
IFilesystemLinkFactory linkFactory)
: base(
ioManager,
@@ -49,7 +57,10 @@ namespace Tgstation.Server.Host.Components.Engine
platformIdentifier,
processExecutor,
repositoryManager,
- generalConfigurationOptions)
+ asyncDelayer,
+ httpClientFactory,
+ generalConfigurationOptions,
+ sessionConfigurationOptions)
{
this.linkFactory = linkFactory ?? throw new ArgumentNullException(nameof(linkFactory));
}
@@ -112,6 +123,7 @@ namespace Tgstation.Server.Host.Components.Engine
Logger,
ruleName,
serverExePath,
+ SessionConfiguration.LowPriorityDeploymentProcesses,
cancellationToken);
}
catch (Exception ex)
diff --git a/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs
index 81a0d34585..559e584a27 100644
--- a/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs
+++ b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs
@@ -19,7 +19,7 @@ namespace Tgstation.Server.Host.Components.Events
///
/// The for the .
///
- IWatchdog watchdog;
+ IWatchdog? watchdog;
///
/// Initializes a new instance of the class.
@@ -31,7 +31,7 @@ namespace Tgstation.Server.Host.Components.Events
}
///
- public async ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
+ public async ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(parameters);
@@ -50,10 +50,9 @@ namespace Tgstation.Server.Host.Components.Events
public void SetWatchdog(IWatchdog watchdog)
{
ArgumentNullException.ThrowIfNull(watchdog);
- if (this.watchdog != null)
+ var oldWatchdog = Interlocked.CompareExchange(ref this.watchdog, watchdog, null);
+ if (oldWatchdog != null)
throw new InvalidOperationException("watchdog already set!");
-
- this.watchdog = watchdog;
}
}
}
diff --git a/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs
index 7fddbb8950..4d268011f1 100644
--- a/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs
+++ b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs
@@ -17,6 +17,6 @@ namespace Tgstation.Server.Host.Components.Events
/// If this event is part of the deployment pipeline.
/// The for the operation.
/// A representing the running operation.
- ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken);
+ ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Events/NoopEventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/NoopEventConsumer.cs
index a8b07e4d7e..dde777572a 100644
--- a/src/Tgstation.Server.Host/Components/Events/NoopEventConsumer.cs
+++ b/src/Tgstation.Server.Host/Components/Events/NoopEventConsumer.cs
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Events
sealed class NoopEventConsumer : IEventConsumer
{
///
- public ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
+ public ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
=> ValueTask.CompletedTask;
}
}
diff --git a/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs b/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs
index 7e6b0b8dc5..ab1db54e91 100644
--- a/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs
+++ b/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs
@@ -10,6 +10,6 @@
///
/// The to get the for.
/// The if it is online, otherwise.
- IInstanceCore GetInstance(Models.Instance instance);
+ IInstanceCore? GetInstance(Models.Instance instance);
}
}
diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs
index 3174686efe..516b2a8f30 100644
--- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs
+++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs
@@ -19,6 +19,6 @@ namespace Tgstation.Server.Host.Components
///
/// The of the desired .
/// The associated with the given if it is online, otherwise.
- IInstanceReference GetInstanceReference(Api.Models.Instance metadata);
+ IInstanceReference? GetInstanceReference(Api.Models.Instance metadata);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index 7176e8f050..a8b9a40721 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -93,12 +93,12 @@ namespace Tgstation.Server.Host.Components
///
/// The auto update .
///
- Task timerTask;
+ Task? timerTask;
///
/// for .
///
- CancellationTokenSource timerCts;
+ CancellationTokenSource? timerCts;
///
/// Initializes a new instance of the class.
@@ -183,7 +183,7 @@ namespace Tgstation.Server.Host.Components
using (LogContext.PushProperty(SerilogContextHelper.InstanceIdContextProperty, metadata.Id))
{
await Task.WhenAll(
- SetAutoUpdateInterval(metadata.AutoUpdateInterval.Value).AsTask(),
+ SetAutoUpdateInterval(metadata.Require(x => x.AutoUpdateInterval)).AsTask(),
Configuration.StartAsync(cancellationToken),
EngineManager.StartAsync(cancellationToken),
Chat.StartAsync(cancellationToken),
@@ -221,7 +221,7 @@ namespace Tgstation.Server.Host.Components
if (timerTask != null)
{
logger.LogTrace("Cancelling auto-update task");
- timerCts.Cancel();
+ timerCts!.Cancel();
timerCts.Dispose();
toWait = timerTask;
timerTask = null;
@@ -253,7 +253,7 @@ namespace Tgstation.Server.Host.Components
}
///
- public CompileJob LatestCompileJob() => dmbFactory.LatestCompileJob();
+ public CompileJob? LatestCompileJob() => dmbFactory.LatestCompileJob();
///
/// The for updating the repository.
@@ -266,7 +266,7 @@ namespace Tgstation.Server.Host.Components
/// A representing the running operation.
#pragma warning disable CA1502 // Cyclomatic complexity
ValueTask RepositoryAutoUpdateJob(
- IInstanceCore core,
+ IInstanceCore? core,
IDatabaseContextFactory databaseContextFactory,
Job job,
JobProgressReporter progressReporter,
@@ -315,18 +315,18 @@ namespace Tgstation.Server.Host.Components
cancellationToken);
var hasDbChanges = false;
- RevisionInformation currentRevInfo = null;
- Models.Instance attachedInstance = null;
- async ValueTask UpdateRevInfo(string currentHead, bool onOrigin, IEnumerable updatedTestMerges)
+ RevisionInformation? currentRevInfo = null;
+ Models.Instance? attachedInstance = null;
+ async ValueTask UpdateRevInfo(string currentHead, bool onOrigin, IEnumerable? updatedTestMerges)
{
if (currentRevInfo == null)
{
logger.LogTrace("Loading revision info for commit {sha}...", startSha[..7]);
currentRevInfo = await databaseContext
- .RevisionInformations
+ .RevisionInformations
.AsQueryable()
- .Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id)
- .Include(x => x.ActiveTestMerges)
+ .Where(x => x.CommitSha == startSha && x.InstanceId == metadata.Id)
+ .Include(x => x.ActiveTestMerges!)
.ThenInclude(x => x.TestMerge)
.FirstOrDefaultAsync(cancellationToken);
}
@@ -364,12 +364,9 @@ namespace Tgstation.Server.Host.Components
if (!onOrigin)
{
- var testMerges = updatedTestMerges ?? oldRevInfo.ActiveTestMerges.Select(x => x.TestMerge);
+ var testMerges = updatedTestMerges ?? oldRevInfo!.ActiveTestMerges!.Select(x => x.TestMerge);
var revInfoTestMerges = testMerges.Select(
- testMerge => new RevInfoTestMerge
- {
- TestMerge = testMerge,
- })
+ testMerge => new RevInfoTestMerge(testMerge, currentRevInfo))
.ToList();
currentRevInfo.ActiveTestMerges = revInfoTestMerges;
@@ -382,21 +379,21 @@ namespace Tgstation.Server.Host.Components
// build current commit data if it's missing
await UpdateRevInfo(repo.Head, false, null);
- var preserveTestMerges = repositorySettings.AutoUpdatesKeepTestMerges.Value;
+ var preserveTestMerges = repositorySettings.AutoUpdatesKeepTestMerges!.Value;
var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager(
metadata,
- repo.RemoteGitProvider.Value);
+ repo.RemoteGitProvider!.Value);
var updatedTestMerges = await remoteDeploymentManager.RemoveMergedTestMerges(
repo,
repositorySettings,
- currentRevInfo,
+ currentRevInfo!,
cancellationToken);
var result = await repo.MergeOrigin(
NextProgressReporter("Merge Origin"),
- repositorySettings.CommitterName,
- repositorySettings.CommitterEmail,
+ repositorySettings.CommitterName!,
+ repositorySettings.CommitterEmail!,
true,
cancellationToken);
@@ -435,7 +432,7 @@ namespace Tgstation.Server.Host.Components
NextProgressReporter(StageName),
repositorySettings.AccessUser,
repositorySettings.AccessToken,
- repositorySettings.UpdateSubmodules.Value,
+ repositorySettings.UpdateSubmodules!.Value,
true,
cancellationToken);
@@ -443,7 +440,7 @@ namespace Tgstation.Server.Host.Components
currentRevInfo = await databaseContext.RevisionInformations
.AsQueryable()
- .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id)
+ .Where(x => x.CommitSha == currentHead && x.InstanceId == metadata.Id)
.FirstOrDefaultAsync(cancellationToken);
if (currentHead != startSha && currentRevInfo == default)
@@ -453,19 +450,19 @@ namespace Tgstation.Server.Host.Components
}
// synch if necessary
- if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head && (shouldSyncTracked || repositorySettings.PushTestMergeCommits.Value))
+ if (repositorySettings.AutoUpdatesSynchronize!.Value && startSha != repo.Head && (shouldSyncTracked || repositorySettings.PushTestMergeCommits!.Value))
{
- var pushedOrigin = await repo.Sychronize(
+ var pushedOrigin = await repo.Synchronize(
NextProgressReporter("Synchronize"),
repositorySettings.AccessUser,
repositorySettings.AccessToken,
- repositorySettings.CommitterName,
- repositorySettings.CommitterEmail,
+ repositorySettings.CommitterName!,
+ repositorySettings.CommitterEmail!,
shouldSyncTracked,
true,
cancellationToken);
var currentHead = repo.Head;
- if (currentHead != currentRevInfo.CommitSha)
+ if (currentHead != currentRevInfo!.CommitSha)
await UpdateRevInfo(currentHead, pushedOrigin, null);
}
@@ -517,6 +514,9 @@ namespace Tgstation.Server.Host.Components
Job compileProcessJob;
using (var repo = await RepositoryManager.LoadRepository(cancellationToken))
{
+ if (repo == null)
+ throw new JobException(Api.Models.ErrorCode.RepoMissing);
+
var deploySha = repo.Head;
if (deploySha == null)
{
diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs
index a2e9dc6ff5..2b24bcb6cb 100644
--- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs
@@ -150,7 +150,7 @@ namespace Tgstation.Server.Host.Components
///
/// The instance's .
/// The for the instance's "Game" directory.
- static IIOManager CreateGameIOManager(IIOManager instanceIOManager) => new ResolvingIOManager(instanceIOManager, "Game");
+ static ResolvingIOManager CreateGameIOManager(IIOManager instanceIOManager) => new(instanceIOManager, "Game");
#pragma warning disable CA1502 // TODO: Decomplexify
///
@@ -324,13 +324,13 @@ namespace Tgstation.Server.Host.Components
configuration, // watchdog doesn't need itself as an event consumer
remoteDeploymentManagerFactory,
metadata,
- metadata.DreamDaemonSettings);
+ metadata.DreamDaemonSettings!);
try
{
eventConsumer.SetWatchdog(watchdog);
commandFactory.SetWatchdog(watchdog);
- Instance instance = null;
+ Instance? instance = null;
var dreamMaker = new DreamMaker(
engineManager,
gameIoManager,
@@ -404,6 +404,6 @@ namespace Tgstation.Server.Host.Components
///
/// The .
/// The for the .
- IIOManager CreateInstanceIOManager(Models.Instance metadata) => new ResolvingIOManager(ioManager, metadata.Path);
+ ResolvingIOManager CreateInstanceIOManager(Models.Instance metadata) => new(ioManager, metadata.Path!);
}
}
diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs
index 0959e9ca01..613b9d7800 100644
--- a/src/Tgstation.Server.Host/Components/InstanceManager.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs
@@ -21,6 +21,7 @@ using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Swarm;
using Tgstation.Server.Host.System;
@@ -115,7 +116,7 @@ namespace Tgstation.Server.Host.Components
readonly Dictionary bridgeHandlers;
///
- /// used to guard calls to and .
+ /// used to guard calls to and .
///
readonly SemaphoreSlim instanceStateChangeSemaphore;
@@ -147,12 +148,12 @@ namespace Tgstation.Server.Host.Components
///
/// The original of .
///
- readonly string originalConsoleTitle;
+ readonly string? originalConsoleTitle;
///
/// The returned by .
///
- Task startupTask;
+ Task? startupTask;
///
/// If the has been 'd.
@@ -241,13 +242,13 @@ namespace Tgstation.Server.Host.Components
}
///
- public IInstanceReference GetInstanceReference(Api.Models.Instance metadata)
+ public IInstanceReference? GetInstanceReference(Api.Models.Instance metadata)
{
ArgumentNullException.ThrowIfNull(metadata);
lock (instances)
{
- if (!instances.TryGetValue(metadata.Id.Value, out var instance))
+ if (!instances.TryGetValue(metadata.Require(x => x.Id), out var instance))
return null;
return instance.AddReference();
@@ -263,7 +264,7 @@ namespace Tgstation.Server.Host.Components
using var instanceReferenceCheck = GetInstanceReference(instance);
if (instanceReferenceCheck != null)
throw new InvalidOperationException("Cannot move an online instance!");
- var newPath = instance.Path;
+ var newPath = instance.Path!;
try
{
await ioManager.MoveDirectory(oldPath, newPath, cancellationToken);
@@ -326,22 +327,23 @@ namespace Tgstation.Server.Host.Components
}
///
- public async ValueTask OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
+ public async ValueTask OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(metadata);
using (await SemaphoreSlimContext.Lock(instanceStateChangeSemaphore, cancellationToken))
{
- ReferenceCountingContainer container;
+ ReferenceCountingContainer? container;
+ var instanceId = metadata.Require(x => x.Id);
lock (instances)
{
- if (!instances.TryGetValue(metadata.Id.Value, out container))
+ if (!instances.TryGetValue(instanceId, out container))
{
logger.LogDebug("Not offlining removed instance {instanceId}", metadata.Id);
return;
}
- instances.Remove(metadata.Id.Value);
+ instances.Remove(instanceId);
}
logger.LogInformation("Offlining instance ID {instanceId}", metadata.Id);
@@ -351,27 +353,29 @@ namespace Tgstation.Server.Host.Components
await container.OnZeroReferences.WaitAsync(cancellationToken);
// we are the one responsible for cancelling his jobs
- var tasks = new List>();
+ ValueTask groupedTask = default;
await databaseContextFactory.UseContext(
async db =>
{
var jobs = await db
.Jobs
.AsQueryable()
- .Where(x => x.Instance.Id == metadata.Id && !x.StoppedAt.HasValue)
- .Select(x => new Models.Job(x.Id.Value))
+ .Where(x => x.Instance!.Id == metadata.Id && !x.StoppedAt.HasValue)
+ .Select(x => new Job(x.Id!.Value))
.ToListAsync(cancellationToken);
- foreach (var job in jobs)
- tasks.Add(jobService.CancelJob(job, user, true, cancellationToken));
+
+ groupedTask = ValueTaskExtensions.WhenAll(
+ jobs.Select(job => jobService.CancelJob(job, user, true, cancellationToken)),
+ jobs.Count);
});
- await ValueTaskExtensions.WhenAll(tasks);
+ await groupedTask;
}
catch
{
// not too late to change your mind
lock (instances)
- instances.Add(metadata.Id.Value, container);
+ instances.Add(instanceId, container);
throw;
}
@@ -393,9 +397,10 @@ namespace Tgstation.Server.Host.Components
{
ArgumentNullException.ThrowIfNull(metadata);
+ var instanceId = metadata.Require(x => x.Id);
using var lockContext = await SemaphoreSlimContext.Lock(instanceStateChangeSemaphore, cancellationToken);
lock (instances)
- if (instances.ContainsKey(metadata.Id.Value))
+ if (instances.ContainsKey(instanceId))
{
logger.LogDebug("Aborting instance creation due to it seemingly already being online");
return;
@@ -411,7 +416,7 @@ namespace Tgstation.Server.Host.Components
{
lock (instances)
instances.Add(
- metadata.Id.Value,
+ instanceId,
new ReferenceCountingContainer(instance));
}
catch (Exception ex)
@@ -453,6 +458,12 @@ namespace Tgstation.Server.Host.Components
using (cancellationToken.Register(shutdownCancellationTokenSource.Cancel))
try
{
+ if (startupTask == null)
+ {
+ logger.LogWarning("InstanceManager was never started!");
+ return;
+ }
+
logger.LogDebug("Stopping instance manager...");
if (!startupTask.IsCompleted)
@@ -485,7 +496,7 @@ namespace Tgstation.Server.Host.Components
finally
{
if (originalConsoleTitle != null)
- console.Title = originalConsoleTitle;
+ console.SetTitle(originalConsoleTitle);
}
}
catch (Exception ex)
@@ -495,18 +506,25 @@ namespace Tgstation.Server.Host.Components
}
///
- public async ValueTask ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
+ public async ValueTask ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(parameters);
- IBridgeHandler bridgeHandler = null;
+ var accessIdentifier = parameters.AccessIdentifier;
+ if (accessIdentifier == null)
+ {
+ logger.LogWarning("Received invalid bridge request with null access identifier!");
+ return null;
+ }
+
+ IBridgeHandler? bridgeHandler = null;
for (var i = 0; bridgeHandler == null && i < 30; ++i)
{
// There's a miniscule time period where we could potentially receive a bridge request and not have the registration ready when we launch DD
// This is a stopgap
Task delayTask = Task.CompletedTask;
lock (bridgeHandlers)
- if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
+ if (!bridgeHandlers.TryGetValue(accessIdentifier, out bridgeHandler))
delayTask = asyncDelayer.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
await delayTask;
@@ -514,9 +532,9 @@ namespace Tgstation.Server.Host.Components
if (bridgeHandler == null)
lock (bridgeHandlers)
- if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
+ if (!bridgeHandlers.TryGetValue(accessIdentifier, out bridgeHandler))
{
- logger.LogWarning("Received invalid bridge request with access identifier: {accessIdentifier}", parameters.AccessIdentifier);
+ logger.LogWarning("Received invalid bridge request with access identifier: {accessIdentifier}", accessIdentifier);
return null;
}
@@ -528,7 +546,8 @@ namespace Tgstation.Server.Host.Components
{
ArgumentNullException.ThrowIfNull(bridgeHandler);
- var accessIdentifier = bridgeHandler.DMApiParameters.AccessIdentifier;
+ var accessIdentifier = bridgeHandler.DMApiParameters.AccessIdentifier
+ ?? throw new InvalidOperationException("Attempted bridge registration with null AccessIdentifier!");
lock (bridgeHandlers)
{
bridgeHandlers.Add(accessIdentifier, bridgeHandler);
@@ -546,11 +565,11 @@ namespace Tgstation.Server.Host.Components
}
///
- public IInstanceCore GetInstance(Models.Instance metadata)
+ public IInstanceCore? GetInstance(Models.Instance metadata)
{
lock (instances)
{
- instances.TryGetValue(metadata.Id.Value, out var container);
+ instances.TryGetValue(metadata.Require(x => x.Id), out var container);
return container?.Instance;
}
}
@@ -565,7 +584,7 @@ namespace Tgstation.Server.Host.Components
try
{
logger.LogInformation("{versionString}", assemblyInformationProvider.VersionString);
- console.Title = assemblyInformationProvider.VersionString;
+ console.SetTitle(assemblyInformationProvider.VersionString);
CheckSystemCompatibility();
@@ -574,13 +593,13 @@ namespace Tgstation.Server.Host.Components
await InitializeSwarm(cancellationToken);
- List dbInstances = null;
+ List? dbInstances = null;
async ValueTask EnumerateInstances(IDatabaseContext databaseContext)
=> dbInstances = await databaseContext
.Instances
.AsQueryable()
- .Where(x => x.Online.Value && x.SwarmIdentifer == swarmConfiguration.Identifier)
+ .Where(x => x.Online!.Value && x.SwarmIdentifer == swarmConfiguration.Identifier)
.Include(x => x.RepositorySettings)
.Include(x => x.ChatSettings)
.ThenInclude(x => x.Channels)
@@ -594,7 +613,7 @@ namespace Tgstation.Server.Host.Components
await Task.WhenAll(instanceEnumeration.AsTask(), factoryStartup, jobManagerStartup);
- var instanceOnliningTasks = dbInstances.Select(
+ var instanceOnliningTasks = dbInstances!.Select(
async metadata =>
{
try
@@ -609,10 +628,11 @@ namespace Tgstation.Server.Host.Components
await Task.WhenAll(instanceOnliningTasks);
- jobService.Activate(this);
-
logger.LogInformation("Server ready!");
readyTcs.SetResult();
+
+ // this needs to happen after the HTTP API opens with readyTcs otherwise it can race and cause failed bridge requests with 503's
+ jobService.Activate(this);
}
catch (OperationCanceledException ex)
{
diff --git a/src/Tgstation.Server.Host/Components/InstanceWrapper.cs b/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
index 5c525d9869..ba2c84d688 100644
--- a/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceWrapper.cs
@@ -58,6 +58,6 @@ namespace Tgstation.Server.Host.Components
public ValueTask SetAutoUpdateInterval(uint newInterval) => Instance.SetAutoUpdateInterval(newInterval);
///
- public CompileJob LatestCompileJob() => Instance.LatestCompileJob();
+ public CompileJob? LatestCompileJob() => Instance.LatestCompileJob();
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs
index c1c9213063..1240bc81cb 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeParameters.cs
@@ -24,12 +24,12 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
///
/// The DMAPI for requests.
///
- public Version Version { get; set; }
+ public Version? Version { get; set; }
///
/// The DMAPI s for requests.
///
- public ICollection CustomCommands { get; set; }
+ public ICollection? CustomCommands { get; set; }
///
/// The minimum required level for requests.
@@ -39,16 +39,25 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
///
/// The for requests.
///
- public ChatMessage ChatMessage { get; set; }
+ public ChatMessage? ChatMessage { get; set; }
///
/// The for requests.
///
- public ChunkData Chunk { get; set; }
+ public ChunkData? Chunk { get; set; }
///
/// The port that should be used to send world topics, if not the default.
///
public ushort? TopicPort { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The access identifier for the .
+ public BridgeParameters(string accessIdentifier)
+ : base(accessIdentifier)
+ {
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeRegistration.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeRegistration.cs
index ade7d5adfe..1ba9673ee6 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeRegistration.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeRegistration.cs
@@ -1,38 +1,19 @@
using System;
+using Tgstation.Server.Host.Utils;
+
namespace Tgstation.Server.Host.Components.Interop.Bridge
{
///
- sealed class BridgeRegistration : IBridgeRegistration
+ sealed class BridgeRegistration : DisposeInvoker, IBridgeRegistration
{
- ///
- /// for accessing .
- ///
- readonly object lockObject;
-
- ///
- /// to run when d.
- ///
- Action onDispose;
-
///
/// Initializes a new instance of the class.
///
- /// The value of .
- public BridgeRegistration(Action onDispose)
+ /// The action for the .
+ public BridgeRegistration(Action disposeAction)
+ : base(disposeAction)
{
- this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
- lockObject = new object();
- }
-
- ///
- public void Dispose()
- {
- lock (lockObject)
- {
- onDispose?.Invoke();
- onDispose = null;
- }
}
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeResponse.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeResponse.cs
index f7f358d612..620241fafb 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeResponse.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/BridgeResponse.cs
@@ -15,11 +15,11 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
///
/// The for requests.
///
- public RuntimeInformation RuntimeInformation { get; set; }
+ public RuntimeInformation? RuntimeInformation { get; set; }
///
/// The s missing from a chunked request.
///
- public IReadOnlyCollection MissingChunks { get; set; }
+ public IReadOnlyCollection? MissingChunks { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeDispatcher.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeDispatcher.cs
index 82de2d6631..8035583442 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeDispatcher.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/IBridgeDispatcher.cs
@@ -14,6 +14,6 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
/// The to handle.
/// The for the operation.
/// A resulting in the for the request or if the request could not be dispatched.
- ValueTask ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken);
+ ValueTask ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs
index b7d05b67d7..a011dad75a 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs
@@ -91,11 +91,11 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha,
};
- TestMerges = (IReadOnlyCollection)dmbProvider
+ TestMerges = (IReadOnlyCollection?)dmbProvider
.CompileJob
.RevisionInformation
- .ActiveTestMerges?
- .Select(x => x.TestMerge)
+ .ActiveTestMerges
+ ?.Select(x => x.TestMerge)
.Select(x => new TestMergeInformation(x, Revision))
.ToList()
?? Array.Empty();
diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/TestMergeInformation.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/TestMergeInformation.cs
index 1270af68cf..4537c56bd9 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Bridge/TestMergeInformation.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/TestMergeInformation.cs
@@ -18,10 +18,10 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge
///
/// Backing field for needed to continue to support DMAPI 5.
///
- public string PullRequestRevision { get; set; }
+ public string? PullRequestRevision { get; set; }
///
- public override string TargetCommitSha
+ public override string? TargetCommitSha
{
get => PullRequestRevision;
set => PullRequestRevision = value;
diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatEmbed.cs b/src/Tgstation.Server.Host/Components/Interop/ChatEmbed.cs
index da3518ec2a..41563191f3 100644
--- a/src/Tgstation.Server.Host/Components/Interop/ChatEmbed.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/ChatEmbed.cs
@@ -10,63 +10,63 @@ namespace Tgstation.Server.Host.Components.Interop
///
/// The title of the embed.
///
- public string Title { get; set; }
+ public string? Title { get; set; }
///
/// The description of the embed.
///
- public string Description { get; set; }
+ public string? Description { get; set; }
///
/// The URL of the embed.
///
#pragma warning disable CA1056 // Uri properties should not be strings
- public string Url { get; set; }
+ public string? Url { get; set; }
#pragma warning restore CA1056 // Uri properties should not be strings
///
/// The ISO 8601 timestamp of the embed.
///
- public string Timestamp { get; set; }
+ public string? Timestamp { get; set; }
///
/// The colour of the embed in the format hex "#AARRGGBB".
///
- public string Colour { get; set; }
+ public string? Colour { get; set; }
///
/// The .
///
- public ChatEmbedFooter Footer { get; set; }
+ public ChatEmbedFooter? Footer { get; set; }
///
/// The for an image.
///
- public ChatEmbedMedia Image { get; set; }
+ public ChatEmbedMedia? Image { get; set; }
///
/// The for a thumbnail.
///
- public ChatEmbedMedia Thumbnail { get; set; }
+ public ChatEmbedMedia? Thumbnail { get; set; }
///
/// The for a video.
///
- public ChatEmbedMedia Video { get; set; }
+ public ChatEmbedMedia? Video { get; set; }
///
/// The .
///
- public ChatEmbedProvider Provider { get; set; }
+ public ChatEmbedProvider? Provider { get; set; }
///
/// The .
///
- public ChatEmbedAuthor Author { get; set; }
+ public ChatEmbedAuthor? Author { get; set; }
///
/// The s.
///
- public ICollection Fields { get; set; }
+ public ICollection? Fields { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedAuthor.cs b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedAuthor.cs
index b79d38be5b..d136b895b0 100644
--- a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedAuthor.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedAuthor.cs
@@ -9,12 +9,12 @@
/// Gets the icon URL of the author.
///
#pragma warning disable CA1056 // Uri properties should not be strings
- public string IconUrl { get; set; }
+ public string? IconUrl { get; set; }
///
/// Gets the proxied icon URL of the thumbnail.
///
- public string ProxyIconUrl { get; set; }
+ public string? ProxyIconUrl { get; set; }
#pragma warning restore CA1056 // Uri properties should not be strings
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedField.cs b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedField.cs
index 9d3ca0b864..70b3c37c9f 100644
--- a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedField.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedField.cs
@@ -8,12 +8,12 @@
///
/// Gets the name of the field.
///
- public string Name { get; set; }
+ public string? Name { get; set; }
///
/// Gets the value of the field.
///
- public string Value { get; set; }
+ public string? Value { get; set; }
///
/// Gets a value indicating whether the field should display inline.
diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedFooter.cs b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedFooter.cs
index 0df7a2c5d9..57d45ec7eb 100644
--- a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedFooter.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedFooter.cs
@@ -8,18 +8,18 @@
///
/// Gets the text of the footer.
///
- public string Text { get; set; }
+ public string? Text { get; set; }
///
/// Gets the URL of the footer icon. Only supports http(s) and attachments.
///
#pragma warning disable CA1056 // Uri properties should not be strings
- public string IconUrl { get; set; }
+ public string? IconUrl { get; set; }
///
/// Gets the proxied icon URL.
///
- public string ProxyIconUrl { get; set; }
+ public string? ProxyIconUrl { get; set; }
#pragma warning restore CA1056 // Uri properties should not be strings
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedMedia.cs b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedMedia.cs
index 1cad4c4349..c031a01e92 100644
--- a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedMedia.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedMedia.cs
@@ -9,12 +9,12 @@
/// Gets the source URL of the media. Only supports http(s) and attachments.
///
#pragma warning disable CA1056 // Uri properties should not be strings
- public string Url { get; set; }
+ public string? Url { get; set; }
///
/// Gets the proxied URL of the media.
///
- public string ProxyUrl { get; set; }
+ public string? ProxyUrl { get; set; }
#pragma warning restore CA1056 // Uri properties should not be strings
///
diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedProvider.cs b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedProvider.cs
index 386a269a7d..bc4dba30d7 100644
--- a/src/Tgstation.Server.Host/Components/Interop/ChatEmbedProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/ChatEmbedProvider.cs
@@ -8,13 +8,13 @@
///
/// Gets the name of the provider.
///
- public string Name { get; set; }
+ public string? Name { get; set; }
///
/// Gets the URL of the provider.
///
#pragma warning disable CA1056 // Uri properties should not be strings
- public string Url { get; set; }
+ public string? Url { get; set; }
#pragma warning restore CA1056 // Uri properties should not be strings
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/ChatMessage.cs b/src/Tgstation.Server.Host/Components/Interop/ChatMessage.cs
index 3d8900e427..c84e44e6cc 100644
--- a/src/Tgstation.Server.Host/Components/Interop/ChatMessage.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/ChatMessage.cs
@@ -10,6 +10,6 @@ namespace Tgstation.Server.Host.Components.Interop
///
/// The of s to sent the to. Must be safe to parse as s.
///
- public ICollection ChannelIds { get; set; }
+ public ICollection? ChannelIds { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/ChunkData.cs b/src/Tgstation.Server.Host/Components/Interop/ChunkData.cs
index ee6122d764..409179b660 100644
--- a/src/Tgstation.Server.Host/Components/Interop/ChunkData.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/ChunkData.cs
@@ -14,6 +14,6 @@
///
/// The partial JSON payload of the chunk.
///
- public string Payload { get; set; }
+ public string? Payload { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Chunker.cs b/src/Tgstation.Server.Host/Components/Interop/Chunker.cs
index 85f6de0d6c..e13cfd00ba 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Chunker.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Chunker.cs
@@ -14,6 +14,11 @@ namespace Tgstation.Server.Host.Components.Interop
///
abstract class Chunker
{
+ ///
+ /// The for the .
+ ///
+ protected ILogger Logger { get; }
+
///
/// Gets a payload ID for use in a new .
///
@@ -39,11 +44,6 @@ namespace Tgstation.Server.Host.Components.Interop
///
uint highestSeenPayloadId;
- ///
- /// The for the .
- ///
- protected ILogger Logger { get; }
-
///
/// Initializes a new instance of the class.
///
@@ -64,11 +64,12 @@ namespace Tgstation.Server.Host.Components.Interop
/// The .
/// The for the operation.
/// A resulting in the for the chunked request.
- protected async ValueTask ProcessChunk(
- Func> completionCallback,
- Func chunkErrorCallback,
- ChunkData chunk,
+ protected async ValueTask ProcessChunk(
+ Func> completionCallback,
+ Func chunkErrorCallback,
+ ChunkData? chunk,
CancellationToken cancellationToken)
+ where TCommunication : class
where TResponse : IMissingPayloadsCommunication, new()
{
if (chunk == null)
@@ -80,6 +81,9 @@ namespace Tgstation.Server.Host.Components.Interop
if (!chunk.SequenceId.HasValue)
return chunkErrorCallback("Missing chunk sequenceId!");
+ if (chunk.Payload == null)
+ return chunkErrorCallback("Missing chunk payload!");
+
ChunkSetInfo requestInfo;
string[] payloads;
lock (chunkSets)
@@ -102,13 +106,13 @@ namespace Tgstation.Server.Host.Components.Interop
if (chunk.TotalChunks != requestInfo.TotalChunks)
{
- chunkSets.Remove(requestInfo.PayloadId.Value);
+ chunkSets.Remove(requestInfo.PayloadId!.Value);
return chunkErrorCallback("Received differing total chunks for same payloadId! Invalidating payloadId!");
}
if (payloads[chunk.SequenceId.Value] != null && payloads[chunk.SequenceId.Value] != chunk.Payload)
{
- chunkSets.Remove(requestInfo.PayloadId.Value);
+ chunkSets.Remove(requestInfo.PayloadId!.Value);
return chunkErrorCallback("Received differing payload for same sequenceId! Invalidating payloadId!");
}
@@ -125,10 +129,10 @@ namespace Tgstation.Server.Host.Components.Interop
};
Logger.LogTrace("Received all chunks for P{payloadId}, processing request...", requestInfo.PayloadId);
- chunkSets.Remove(requestInfo.PayloadId.Value);
+ chunkSets.Remove(requestInfo.PayloadId!.Value);
}
- TCommunication completedCommunication;
+ TCommunication? completedCommunication;
var fullCommunicationJson = String.Concat(payloads);
try
{
@@ -137,9 +141,12 @@ namespace Tgstation.Server.Host.Components.Interop
catch (Exception ex)
{
Logger.LogDebug(ex, "Bad chunked communication for payload {payloadId}!", requestInfo.PayloadId);
- return chunkErrorCallback("Chunked request completed with bad JSON!");
+ completedCommunication = null;
}
+ if (completedCommunication == null)
+ return chunkErrorCallback("Chunked request completed with bad JSON!");
+
return await completionCallback(completedCommunication, cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs b/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs
index 2a59892180..7386e85669 100644
--- a/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs
@@ -40,12 +40,12 @@ namespace Tgstation.Server.Host.Components.Interop
public const uint MaximumBridgeRequestLength = 8198;
///
- /// The maximum length in bytes of a payload.
+ /// The maximum length in bytes of a payload.
///
public const uint MaximumTopicRequestLength = 65528;
///
- /// The maximum length in bytes of a response.
+ /// The maximum length in bytes of a response.
///
public const uint MaximumTopicResponseLength = 65529;
diff --git a/src/Tgstation.Server.Host/Components/Interop/DMApiParameters.cs b/src/Tgstation.Server.Host/Components/Interop/DMApiParameters.cs
index a6faad3292..64b774aeb5 100644
--- a/src/Tgstation.Server.Host/Components/Interop/DMApiParameters.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/DMApiParameters.cs
@@ -1,4 +1,5 @@
-using System.ComponentModel.DataAnnotations;
+using System;
+using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Host.Components.Interop
{
@@ -12,5 +13,23 @@ namespace Tgstation.Server.Host.Components.Interop
///
[Required]
public string AccessIdentifier { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ public DMApiParameters(string accessIdentifier)
+ {
+ AccessIdentifier = accessIdentifier ?? throw new ArgumentNullException(nameof(accessIdentifier));
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// For use by EFCore only.
+ protected DMApiParameters()
+ {
+ AccessIdentifier = null!;
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/DMApiResponse.cs b/src/Tgstation.Server.Host/Components/Interop/DMApiResponse.cs
index bb1adf4925..e6c7502063 100644
--- a/src/Tgstation.Server.Host/Components/Interop/DMApiResponse.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/DMApiResponse.cs
@@ -8,6 +8,6 @@
///
/// Any errors in the client's parameters.
///
- public string ErrorMessage { get; set; }
+ public string? ErrorMessage { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/IMissingPayloadsCommunication.cs b/src/Tgstation.Server.Host/Components/Interop/IMissingPayloadsCommunication.cs
index 392cd7eecb..7adf7d9cce 100644
--- a/src/Tgstation.Server.Host/Components/Interop/IMissingPayloadsCommunication.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/IMissingPayloadsCommunication.cs
@@ -10,6 +10,6 @@ namespace Tgstation.Server.Host.Components.Interop
///
/// The s missing from a chunked request.
///
- IReadOnlyCollection MissingChunks { get; set; }
+ IReadOnlyCollection? MissingChunks { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/MessageContent.cs b/src/Tgstation.Server.Host/Components/Interop/MessageContent.cs
index 6d484a411b..6b04fb10ae 100644
--- a/src/Tgstation.Server.Host/Components/Interop/MessageContent.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/MessageContent.cs
@@ -8,11 +8,11 @@
///
/// The message .
///
- public string Text { get; set; }
+ public string? Text { get; set; }
///
/// The .
///
- public ChatEmbed Embed { get; set; }
+ public ChatEmbed? Embed { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/ChunkedTopicParameters.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/ChunkedTopicParameters.cs
index 79353a66bb..0940281ece 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/ChunkedTopicParameters.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/ChunkedTopicParameters.cs
@@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
sealed class ChunkedTopicParameters : TopicParameters, IMissingPayloadsCommunication, IChunkPayloadId
{
///
- public IReadOnlyCollection MissingChunks { get; set; }
+ public IReadOnlyCollection? MissingChunks { get; set; }
///
public uint? PayloadId { get; set; }
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs
index 54a8d496a5..75eb3bc9f4 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs
@@ -20,14 +20,14 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
///
/// The set of parameters.
///
- public IReadOnlyCollection Parameters { get; }
+ public IReadOnlyCollection Parameters { get; }
///
/// Initializes a new instance of the class.
///
/// The value of .
/// The that forms the value of .
- public EventNotification(EventType eventType, IEnumerable parameters = null)
+ public EventNotification(EventType eventType, IEnumerable parameters)
{
Type = eventType;
Parameters = parameters?.ToList() ?? throw new ArgumentNullException(nameof(parameters));
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
index 65c060b412..bdc8405b02 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicParameters.cs
@@ -20,12 +20,12 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
///
/// The for requests.
///
- public ChatCommand ChatCommand { get; }
+ public ChatCommand? ChatCommand { get; }
///
/// The for requests.
///
- public EventNotification EventNotification { get; }
+ public EventNotification? EventNotification { get; }
///
/// The new port for or requests.
@@ -40,27 +40,27 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
///
/// The new for requests.
///
- public string NewInstanceName { get; }
+ public string? NewInstanceName { get; }
///
/// The message to broadcast for requests.
///
- public string BroadcastMessage { get; }
+ public string? BroadcastMessage { get; }
///
/// The for requests.
///
- public ChatUpdate ChatUpdate { get; }
+ public ChatUpdate? ChatUpdate { get; }
///
/// The new server after a reattach.
///
- public Version NewServerVersion { get; }
+ public Version? NewServerVersion { get; }
///
/// The for a partial request.
///
- public ChunkData Chunk { get; }
+ public ChunkData? Chunk { get; }
///
/// Whether or not the constitute a priority request.
@@ -188,6 +188,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
///
/// The value of .
protected TopicParameters(TopicCommandType commandType)
+ : base(String.Empty) // access identifier gets set before send
{
CommandType = commandType;
}
diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs
index f108aba543..12db6f3ef1 100644
--- a/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs
+++ b/src/Tgstation.Server.Host/Components/Interop/Topic/TopicResponse.cs
@@ -12,29 +12,29 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
///
/// The text to reply with as the result of a request, if any. Deprecated circa Interop 5.4.0.
///
- public string CommandResponseMessage { get; set; }
+ public string? CommandResponseMessage { get; set; }
///
/// The response from a . Added in Interop 5.4.0.
///
- public ChatMessage CommandResponse { get; set; }
+ public ChatMessage? CommandResponse { get; set; }
///
/// The s to send as the result of a request, if any.
///
- public ICollection ChatResponses { get; set; }
+ public ICollection? ChatResponses { get; set; }
///
/// The DMAPI s for requests.
///
- public ICollection CustomCommands { get; set; }
+ public ICollection? CustomCommands { get; set; }
///
/// The for a partial response.
///
- public ChunkData Chunk { get; set; }
+ public ChunkData? Chunk { get; set; }
///
- public IReadOnlyCollection MissingChunks { get; set; }
+ public IReadOnlyCollection? MissingChunks { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs
index ead4007720..05d9408a67 100644
--- a/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs
@@ -21,10 +21,10 @@ namespace Tgstation.Server.Host.Components.Repository
public RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.Unknown;
///
- public string RemoteRepositoryOwner => null;
+ public string? RemoteRepositoryOwner => null;
///
- public string RemoteRepositoryName => null;
+ public string? RemoteRepositoryName => null;
///
public ValueTask GetTestMerge(
diff --git a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
index 6e5d5c4ec0..7a21ebde2b 100644
--- a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs
@@ -25,12 +25,6 @@ namespace Tgstation.Server.Host.Components.Repository
///
public override RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.GitHub;
- ///
- public override string RemoteRepositoryOwner { get; }
-
- ///
- public override string RemoteRepositoryName { get; }
-
///
/// The for the .
///
@@ -46,13 +40,6 @@ namespace Tgstation.Server.Host.Components.Repository
: base(logger, remoteUrl)
{
this.gitHubServiceFactory = gitHubServiceFactory ?? throw new ArgumentNullException(nameof(gitHubServiceFactory));
-
- ArgumentNullException.ThrowIfNull(remoteUrl);
-
- RemoteRepositoryOwner = remoteUrl.Segments[1].TrimEnd('/');
- RemoteRepositoryName = remoteUrl.Segments[2].TrimEnd('/');
- if (RemoteRepositoryName.EndsWith(".git", StringComparison.OrdinalIgnoreCase))
- RemoteRepositoryName = RemoteRepositoryName[0..^4];
}
///
@@ -65,9 +52,9 @@ namespace Tgstation.Server.Host.Components.Repository
? gitHubServiceFactory.CreateService(repositorySettings.AccessToken)
: gitHubServiceFactory.CreateService();
- PullRequest pr = null;
- ApiException exception = null;
- string errorMessage = null;
+ PullRequest? pr = null;
+ ApiException? exception = null;
+ string? errorMessage = null;
try
{
pr = await gitHubService.GetPullRequest(RemoteRepositoryOwner, RemoteRepositoryName, parameters.Number, cancellationToken);
diff --git a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs
index def1adbaaf..9e468b06e6 100644
--- a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs
@@ -30,12 +30,6 @@ namespace Tgstation.Server.Host.Components.Repository
///
public override RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.GitLab;
- ///
- public override string RemoteRepositoryOwner { get; }
-
- ///
- public override string RemoteRepositoryName { get; }
-
///
/// Initializes a new instance of the class.
///
@@ -44,10 +38,6 @@ namespace Tgstation.Server.Host.Components.Repository
public GitLabRemoteFeatures(ILogger logger, Uri remoteUrl)
: base(logger, remoteUrl)
{
- RemoteRepositoryOwner = remoteUrl.Segments[1].TrimEnd('/');
- RemoteRepositoryName = remoteUrl.Segments[2].TrimEnd('/');
- if (RemoteRepositoryName.EndsWith(".git", StringComparison.OrdinalIgnoreCase))
- RemoteRepositoryName = RemoteRepositoryName[0..^4];
}
///
diff --git a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs
index 1b1c5337af..d42e928792 100644
--- a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs
@@ -25,10 +25,10 @@ namespace Tgstation.Server.Host.Components.Repository
public abstract RemoteGitProvider? RemoteGitProvider { get; }
///
- public abstract string RemoteRepositoryOwner { get; }
+ public string RemoteRepositoryOwner { get; }
///
- public abstract string RemoteRepositoryName { get; }
+ public string RemoteRepositoryName { get; }
///
/// The for the .
@@ -50,6 +50,11 @@ namespace Tgstation.Server.Host.Components.Repository
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
ArgumentNullException.ThrowIfNull(remoteUrl);
+ RemoteRepositoryOwner = remoteUrl.Segments[1].TrimEnd('/');
+ RemoteRepositoryName = remoteUrl.Segments[2].TrimEnd('/');
+ if (RemoteRepositoryName.EndsWith(".git", StringComparison.OrdinalIgnoreCase))
+ RemoteRepositoryName = RemoteRepositoryName[0..^4];
+
cachedLookups = new Dictionary();
}
@@ -62,18 +67,18 @@ namespace Tgstation.Server.Host.Components.Repository
ArgumentNullException.ThrowIfNull(parameters);
ArgumentNullException.ThrowIfNull(repositorySettings);
- Models.TestMerge result;
+ Models.TestMerge? result;
lock (cachedLookups)
if (cachedLookups.TryGetValue(parameters, out result))
- Logger.LogTrace("Using cache for test merge #{0}", parameters.Number);
+ Logger.LogTrace("Using cache for test merge #{testMergeNumber}", parameters.Number);
if (result == null)
{
- Logger.LogTrace("Retrieving metadata for test merge #{0}...", parameters.Number);
+ Logger.LogTrace("Retrieving metadata for test merge #{testMergeNumber}...", parameters.Number);
result = await GetTestMergeImpl(parameters, repositorySettings, cancellationToken);
lock (cachedLookups)
if (!cachedLookups.TryAdd(parameters, result))
- Logger.LogError("Race condition on adding test merge #{0}!", parameters.Number);
+ Logger.LogError("Race condition on adding test merge #{testMergeNumber}!", parameters.Number);
}
return result;
diff --git a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs
index ab5dcb45ec..70694d0b94 100644
--- a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs
@@ -78,7 +78,7 @@ namespace Tgstation.Server.Host.Components.Repository
case "GIT.GITLAB.COM":
return RemoteGitProvider.GitLab;
default:
- logger.LogTrace("Unknown git remote: {0}", origin);
+ logger.LogDebug("Unknown git remote: {origin}", origin);
return RemoteGitProvider.Unknown;
}
}
diff --git a/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs b/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs
index cef08354c6..1324c35320 100644
--- a/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// The optional username to use in the .
/// The optional password to use in the .
/// A new .
- CredentialsHandler GenerateCredentialsHandler(string username, string password);
+ CredentialsHandler GenerateCredentialsHandler(string? username, string? password);
///
/// Rethrow the authentication failure message as a if it is one.
diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs
index 0482eaaa90..65bcd1d61e 100644
--- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs
@@ -44,18 +44,18 @@ namespace Tgstation.Server.Host.Components.Repository
/// Checks out a given .
///
/// The sha or reference to checkout.
- /// The username used for fetching from submodule repositories.
- /// The password used for fetching from submodule repositories.
+ /// The optional username used for fetching from submodule repositories.
+ /// The optional password used for fetching from submodule repositories.
/// If a submodule update should be attempted after the merge.
/// The optional to report progress of the operation.
/// The for the operation.
/// A representing the running operation.
ValueTask CheckoutObject(
string committish,
- string username,
- string password,
+ string? username,
+ string? password,
bool updateSubmodules,
- JobProgressReporter progressReporter,
+ JobProgressReporter? progressReporter,
CancellationToken cancellationToken);
///
@@ -64,8 +64,8 @@ namespace Tgstation.Server.Host.Components.Repository
/// The of the pull request.
/// The name of the merge committer.
/// The e-mail of the merge committer.
- /// The username used to fetch from the origin and submodule repositories.
- /// The password used to fetch from the origin and submodule repositories.
+ /// The optional username used to fetch from the origin and submodule repositories.
+ /// The optional password used to fetch from the origin and submodule repositories.
/// If a submodule update should be attempted after the merge.
/// The to report progress of the operation.
/// The for the operation.
@@ -74,8 +74,8 @@ namespace Tgstation.Server.Host.Components.Repository
TestMergeParameters testMergeParameters,
string committerName,
string committerEmail,
- string username,
- string password,
+ string? username,
+ string? password,
bool updateSubmodules,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
@@ -84,15 +84,15 @@ namespace Tgstation.Server.Host.Components.Repository
/// Fetch commits from the origin repository.
///
/// The optional to report progress of the operation.
- /// The username to fetch from the origin repository.
- /// The password to fetch from the origin repository.
+ /// The optional username to fetch from the origin repository.
+ /// The optional password to fetch from the origin repository.
/// If any events created should be marked as part of the deployment pipeline.
/// The for the operation.
/// A representing the running operation.
ValueTask FetchOrigin(
- JobProgressReporter progressReporter,
- string username,
- string password,
+ JobProgressReporter? progressReporter,
+ string? username,
+ string? password,
bool deploymentPipeline,
CancellationToken cancellationToken);
@@ -100,16 +100,16 @@ namespace Tgstation.Server.Host.Components.Repository
/// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository.
///
/// The to report progress of the operation.
- /// The username used for fetching from submodule repositories.
- /// The password used for fetching from submodule repositories.
+ /// The optional username used for fetching from submodule repositories.
+ /// The optional password used for fetching from submodule repositories.
/// If a submodule update should be attempted after the merge.
/// If any events created should be marked as part of the deployment pipeline.
/// The for the operation.
/// A resulting in the SHA of the new HEAD.
ValueTask ResetToOrigin(
JobProgressReporter progressReporter,
- string username,
- string password,
+ string? username,
+ string? password,
bool updateSubmodules,
bool deploymentPipeline,
CancellationToken cancellationToken);
@@ -151,10 +151,10 @@ namespace Tgstation.Server.Host.Components.Repository
/// If any events created should be marked as part of the deployment pipeline.
/// The for the operation.
/// A resulting in if commits were pushed to the tracked origin reference, otherwise.
- ValueTask Sychronize(
+ ValueTask Synchronize(
JobProgressReporter progressReporter,
- string username,
- string password,
+ string? username,
+ string? password,
string committerName,
string committerEmail,
bool synchronizeTrackedBranch,
diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs
index d602730e15..596301611a 100644
--- a/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryManager.cs
@@ -26,25 +26,25 @@ namespace Tgstation.Server.Host.Components.Repository
///
/// The for the operation.
/// A resulting in the loaded if it exists, otherwise.
- ValueTask LoadRepository(CancellationToken cancellationToken);
+ ValueTask LoadRepository(CancellationToken cancellationToken);
///
/// Clone the repository at .
///
/// The of the remote repository to clone.
- /// The branch to clone.
- /// The username to clone from .
- /// The password to clone from .
+ /// The optional branch to clone.
+ /// The optional username to clone from .
+ /// The optional password to clone from .
/// The optional for progress of the clone.
/// If submodules should be recusively cloned and initialized.
/// The for the operation.
/// A resulting i the newly cloned , if one already exists.
- ValueTask CloneRepository(
+ ValueTask CloneRepository(
Uri url,
- string initialBranch,
- string username,
- string password,
- JobProgressReporter progressReporter,
+ string? initialBranch,
+ string? username,
+ string? password,
+ JobProgressReporter? progressReporter,
bool recurseSubmodules,
CancellationToken cancellationToken);
diff --git a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs
index 83011a61b1..418758ebe7 100644
--- a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs
@@ -80,7 +80,7 @@ namespace Tgstation.Server.Host.Components.Repository
TaskScheduler.Current);
///
- public CredentialsHandler GenerateCredentialsHandler(string username, string password) => (a, b, supportedCredentialTypes) =>
+ public CredentialsHandler GenerateCredentialsHandler(string? username, string? password) => (a, b, supportedCredentialTypes) =>
{
var hasCreds = username != null;
var supportsUserPass = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.UsernamePassword);
diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs
index 125ceded30..5518c1a44d 100644
--- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs
@@ -17,12 +17,13 @@ using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Components.Repository
{
///
#pragma warning disable CA1506 // TODO: Decomplexify
- sealed class Repository : IRepository
+ sealed class Repository : DisposeInvoker, IRepository
{
///
/// The default username for committers.
@@ -53,10 +54,10 @@ namespace Tgstation.Server.Host.Components.Repository
public RemoteGitProvider? RemoteGitProvider => gitRemoteFeatures.RemoteGitProvider;
///
- public string RemoteRepositoryOwner => gitRemoteFeatures.RemoteRepositoryOwner;
+ public string? RemoteRepositoryOwner => gitRemoteFeatures.RemoteRepositoryOwner;
///
- public string RemoteRepositoryName => gitRemoteFeatures.RemoteRepositoryName;
+ public string? RemoteRepositoryName => gitRemoteFeatures.RemoteRepositoryName;
///
public bool Tracking => Reference != null && libGitRepo.Head.IsTracking;
@@ -115,16 +116,6 @@ namespace Tgstation.Server.Host.Components.Repository
///
readonly GeneralConfiguration generalConfiguration;
- ///
- /// to be taken when is called.
- ///
- readonly Action onDispose;
-
- ///
- /// If the was disposed.
- ///
- bool disposed;
-
///
/// Initializes a new instance of the class.
///
@@ -137,7 +128,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// The to provide the value of .
/// The value of .
/// The value of .
- /// The value if .
+ /// The action for the .
public Repository(
LibGit2Sharp.IRepository libGitRepo,
ILibGit2Commands commands,
@@ -148,7 +139,8 @@ namespace Tgstation.Server.Host.Components.Repository
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
ILogger logger,
GeneralConfiguration generalConfiguration,
- Action onDispose)
+ Action disposeAction)
+ : base(disposeAction)
{
this.libGitRepo = libGitRepo ?? throw new ArgumentNullException(nameof(libGitRepo));
this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
@@ -160,35 +152,18 @@ namespace Tgstation.Server.Host.Components.Repository
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration));
- this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
gitRemoteFeatures = gitRemoteFeaturesFactory.CreateGitRemoteFeatures(this);
}
- ///
- public void Dispose()
- {
- lock (onDispose)
- {
- if (disposed)
- return;
-
- disposed = true;
- }
-
- logger.LogTrace("Disposing...");
- libGitRepo.Dispose();
- onDispose();
- }
-
///
#pragma warning disable CA1506 // TODO: Decomplexify
public async ValueTask AddTestMerge(
TestMergeParameters testMergeParameters,
string committerName,
string committerEmail,
- string username,
- string password,
+ string? username,
+ string? password,
bool updateSubmodules,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
@@ -227,12 +202,12 @@ namespace Tgstation.Server.Host.Components.Repository
var originalCommit = libGitRepo.Head;
- MergeResult result = null;
+ MergeResult? result = null;
var progressFactor = 1.0 / (updateSubmodules ? 3 : 2);
var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.UtcNow);
- List conflictedPaths = null;
+ List? conflictedPaths = null;
await Task.Factory.StartNew(
() =>
{
@@ -316,17 +291,17 @@ namespace Tgstation.Server.Host.Components.Repository
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current);
- if (result.Status == MergeStatus.Conflicts)
+ if (result!.Status == MergeStatus.Conflicts)
{
var arguments = new List
{
originalCommit.Tip.Sha,
- testMergeParameters.TargetCommitSha,
+ testMergeParameters.TargetCommitSha!,
originalCommit.FriendlyName ?? UnknownReference,
testMergeBranchName,
};
- arguments.AddRange(conflictedPaths);
+ arguments.AddRange(conflictedPaths!);
await eventConsumer.HandleEvent(
EventType.RepoMergeConflict,
@@ -365,10 +340,10 @@ namespace Tgstation.Server.Host.Components.Repository
await eventConsumer.HandleEvent(
EventType.RepoAddTestMerge,
- new List
+ new List
{
testMergeParameters.Number.ToString(CultureInfo.InvariantCulture),
- testMergeParameters.TargetCommitSha,
+ testMergeParameters.TargetCommitSha!,
testMergeParameters.Comment,
},
false,
@@ -384,10 +359,10 @@ namespace Tgstation.Server.Host.Components.Repository
///
public async ValueTask CheckoutObject(
string committish,
- string username,
- string password,
+ string? username,
+ string? password,
bool updateSubmodules,
- JobProgressReporter progressReporter,
+ JobProgressReporter? progressReporter,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(committish);
@@ -418,9 +393,9 @@ namespace Tgstation.Server.Host.Components.Repository
///
public async ValueTask FetchOrigin(
- JobProgressReporter progressReporter,
- string username,
- string password,
+ JobProgressReporter? progressReporter,
+ string? username,
+ string? password,
bool deploymentPipeline,
CancellationToken cancellationToken)
{
@@ -468,8 +443,8 @@ namespace Tgstation.Server.Host.Components.Repository
///
public async ValueTask ResetToOrigin(
JobProgressReporter progressReporter,
- string username,
- string password,
+ string? username,
+ string? password,
bool updateSubmodules,
bool deploymentPipeline,
CancellationToken cancellationToken)
@@ -566,8 +541,8 @@ namespace Tgstation.Server.Host.Components.Repository
{
ArgumentNullException.ThrowIfNull(progressReporter);
- MergeResult result = null;
- Branch trackedBranch = null;
+ MergeResult? result = null;
+ Branch? trackedBranch = null;
var oldHead = libGitRepo.Head;
var oldTip = oldHead.Tip;
@@ -616,14 +591,14 @@ namespace Tgstation.Server.Host.Components.Repository
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current);
- if (result.Status == MergeStatus.Conflicts)
+ if (result!.Status == MergeStatus.Conflicts)
{
await eventConsumer.HandleEvent(
EventType.RepoMergeConflict,
new List
{
oldTip.Sha,
- trackedBranch.Tip.Sha,
+ trackedBranch!.Tip.Sha,
oldHead.FriendlyName ?? UnknownReference,
trackedBranch.FriendlyName,
},
@@ -636,10 +611,10 @@ namespace Tgstation.Server.Host.Components.Repository
}
///
- public async ValueTask Sychronize(
+ public async ValueTask Synchronize(
JobProgressReporter progressReporter,
- string username,
- string password,
+ string? username,
+ string? password,
string committerName,
string committerEmail,
bool synchronizeTrackedBranch,
@@ -867,13 +842,21 @@ namespace Tgstation.Server.Host.Components.Repository
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current);
+ ///
+ protected override void DisposeImpl()
+ {
+ logger.LogTrace("Disposing...");
+ libGitRepo.Dispose();
+ base.DisposeImpl();
+ }
+
///
/// Runs a blocking force checkout to .
///
/// The committish to checkout.
/// The optional for the operation.
/// The for the operation.
- void RawCheckout(string committish, JobProgressReporter progressReporter, CancellationToken cancellationToken)
+ void RawCheckout(string committish, JobProgressReporter? progressReporter, CancellationToken cancellationToken)
{
logger.LogTrace("Checkout: {committish}", committish);
@@ -1008,15 +991,15 @@ namespace Tgstation.Server.Host.Components.Repository
/// Recusively update all s in the .
///
/// Optional of the operation.
- /// The username for the .
- /// The password for the .
+ /// The optional username for the .
+ /// The optional password for the .
/// If any events created should be marked as part of the deployment pipeline.
/// The for the operation.
/// A representing the running operation.
async ValueTask UpdateSubmodules(
- JobProgressReporter progressReporter,
- string username,
- string password,
+ JobProgressReporter? progressReporter,
+ string? username,
+ string? password,
bool deploymentPipeline,
CancellationToken cancellationToken)
{
diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
index 8ce9b60318..727d171e4e 100644
--- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
@@ -118,12 +118,12 @@ namespace Tgstation.Server.Host.Components.Repository
}
///
- public async ValueTask CloneRepository(
+ public async ValueTask CloneRepository(
Uri url,
- string initialBranch,
- string username,
- string password,
- JobProgressReporter progressReporter,
+ string? initialBranch,
+ string? username,
+ string? password,
+ JobProgressReporter? progressReporter,
bool recurseSubmodules,
CancellationToken cancellationToken)
{
@@ -208,7 +208,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
///
- public async ValueTask LoadRepository(CancellationToken cancellationToken)
+ public async ValueTask LoadRepository(CancellationToken cancellationToken)
{
logger.LogTrace("Begin LoadRepository...");
lock (semaphore)
diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs
index 418dc9462d..a7aca78c4b 100644
--- a/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs
@@ -85,16 +85,18 @@ namespace Tgstation.Server.Host.Components.Repository
IDatabaseContext databaseContext,
ILogger logger,
Models.Instance instance,
- string lastOriginCommitSha,
- Action revInfoSink,
+ string? lastOriginCommitSha,
+ Action? revInfoSink,
CancellationToken cancellationToken)
{
var repoSha = repository.Head;
IQueryable ApplyQuery(IQueryable query) => query
- .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
+ .Where(x => x.CommitSha == repoSha && x.InstanceId == instance.Id)
.Include(x => x.CompileJobs)
- .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy);
+ .Include(x => x.ActiveTestMerges!)
+ .ThenInclude(x => x.TestMerge)
+ .ThenInclude(x => x.MergedBy);
var revisionInfo = await ApplyQuery(databaseContext.RevisionInformations).FirstOrDefaultAsync(cancellationToken);
@@ -103,7 +105,7 @@ namespace Tgstation.Server.Host.Components.Repository
revisionInfo = databaseContext
.RevisionInformations
.Local
- .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
+ .Where(x => x.CommitSha == repoSha && x.InstanceId == instance.Id)
.FirstOrDefault();
var needsDbUpdate = revisionInfo == default;
@@ -123,7 +125,7 @@ namespace Tgstation.Server.Host.Components.Repository
databaseContext.RevisionInformations.Add(revisionInfo);
}
- revisionInfo.OriginCommitSha ??= lastOriginCommitSha;
+ revisionInfo!.OriginCommitSha ??= lastOriginCommitSha;
if (revisionInfo.OriginCommitSha == null)
{
revisionInfo.OriginCommitSha = repoSha;
@@ -145,13 +147,15 @@ namespace Tgstation.Server.Host.Components.Repository
/// A representing the running operation.
#pragma warning disable CA1502, CA1506 // TODO: Decomplexify
public async ValueTask RepositoryUpdateJob(
- IInstanceCore instance,
+ IInstanceCore? instance,
IDatabaseContextFactory databaseContextFactory,
Job job,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
#pragma warning restore CA1502, CA1506
{
+ ArgumentNullException.ThrowIfNull(instance);
+
_ = job; // shuts up an IDE warning
var repoManager = instance.RepositoryManager;
@@ -160,23 +164,23 @@ namespace Tgstation.Server.Host.Components.Repository
var startReference = repo.Reference;
var startSha = repo.Head;
- string postUpdateSha = null;
+ string? postUpdateSha = null;
var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
if (newTestMerges && repo.RemoteGitProvider == RemoteGitProvider.Unknown)
throw new JobException(ErrorCode.RepoUnsupportedTestMergeRemote);
- var committerName = currentModel.ShowTestMergeCommitters.Value
+ var committerName = (currentModel.ShowTestMergeCommitters!.Value
? initiatingUser.Name
- : currentModel.CommitterName;
+ : currentModel.CommitterName)!;
var hardResettingToOriginReference = model.UpdateFromOrigin == true && model.Reference != null;
var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1));
var progressFactor = 1.0 / numSteps;
- JobProgressReporter NextProgressReporter(string stage)
+ JobProgressReporter NextProgressReporter(string? stage)
{
return progressReporter.CreateSection(stage, progressFactor);
}
@@ -184,19 +188,19 @@ namespace Tgstation.Server.Host.Components.Repository
progressReporter.ReportProgress(0);
// get a base line for where we are
- Models.RevisionInformation lastRevisionInfo = null;
+ Models.RevisionInformation? lastRevisionInfo = null;
var attachedInstance = new Models.Instance
{
Id = instanceId,
};
- ValueTask CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory
+ ValueTask CallLoadRevInfo(Models.TestMerge? testMergeToAdd = null, string? lastOriginCommitSha = null) => databaseContextFactory
.UseContext(
async databaseContext =>
{
databaseContext.Instances.Attach(attachedInstance);
- var previousRevInfo = lastRevisionInfo;
+ var previousRevInfo = lastRevisionInfo!;
var needsUpdate = await LoadRevisionInformation(
repo,
databaseContext,
@@ -223,13 +227,11 @@ namespace Tgstation.Server.Host.Components.Repository
testMergeToAdd.MergedBy = mergedBy;
testMergeToAdd.MergedAt = DateTimeOffset.UtcNow;
- foreach (var activeTestMerge in previousRevInfo.ActiveTestMerges)
- lastRevisionInfo.ActiveTestMerges.Add(activeTestMerge);
+ var activeTestMerges = lastRevisionInfo!.ActiveTestMerges!;
+ foreach (var activeTestMerge in previousRevInfo.ActiveTestMerges!)
+ activeTestMerges.Add(activeTestMerge);
- lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
- {
- TestMerge = testMergeToAdd,
- });
+ activeTestMerges.Add(new RevInfoTestMerge(testMergeToAdd, lastRevisionInfo));
lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
needsUpdate = true;
@@ -242,7 +244,7 @@ namespace Tgstation.Server.Host.Components.Repository
await CallLoadRevInfo();
// apply new rev info, tracking applied test merges
- ValueTask UpdateRevInfo(Models.TestMerge testMergeToAdd = null) => CallLoadRevInfo(testMergeToAdd, lastRevisionInfo.OriginCommitSha);
+ ValueTask UpdateRevInfo(Models.TestMerge? testMergeToAdd = null) => CallLoadRevInfo(testMergeToAdd, lastRevisionInfo!.OriginCommitSha);
try
{
@@ -263,21 +265,21 @@ namespace Tgstation.Server.Host.Components.Repository
var fastForward = await repo.MergeOrigin(
NextProgressReporter("Merge Origin"),
committerName,
- currentModel.CommitterEmail,
+ currentModel.CommitterEmail!,
false,
cancellationToken);
if (!fastForward.HasValue)
throw new JobException(ErrorCode.RepoMergeConflict);
- lastRevisionInfo.OriginCommitSha = await repo.GetOriginSha(cancellationToken);
+ lastRevisionInfo!.OriginCommitSha = await repo.GetOriginSha(cancellationToken);
await UpdateRevInfo();
if (fastForward.Value)
{
- await repo.Sychronize(
+ await repo.Synchronize(
NextProgressReporter("Sychronize"),
currentModel.AccessUser,
currentModel.AccessToken,
- currentModel.CommitterName,
- currentModel.CommitterEmail,
+ currentModel.CommitterName!,
+ currentModel.CommitterEmail!,
true,
false,
cancellationToken);
@@ -288,7 +290,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
}
- var updateSubmodules = currentModel.UpdateSubmodules.Value;
+ var updateSubmodules = currentModel.UpdateSubmodules!.Value;
// checkout/hard reset
if (modelHasShaOrReference)
@@ -302,7 +304,7 @@ namespace Tgstation.Server.Host.Components.Repository
if (validCheckoutSha || validCheckoutReference)
{
- var committish = model.CheckoutSha ?? model.Reference;
+ var committish = model.CheckoutSha ?? model.Reference!;
var isSha = await repo.IsSha(committish, cancellationToken);
if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
@@ -331,12 +333,12 @@ namespace Tgstation.Server.Host.Components.Repository
updateSubmodules,
false,
cancellationToken);
- await repo.Sychronize(
+ await repo.Synchronize(
NextProgressReporter("Synchronize"),
currentModel.AccessUser,
currentModel.AccessToken,
- currentModel.CommitterName,
- currentModel.CommitterEmail,
+ currentModel.CommitterName!,
+ currentModel.CommitterEmail!,
true,
false,
cancellationToken);
@@ -344,7 +346,7 @@ namespace Tgstation.Server.Host.Components.Repository
// repo head is on origin so force this
// will update the db if necessary
- lastRevisionInfo.OriginCommitSha = repo.Head;
+ lastRevisionInfo!.OriginCommitSha = repo.Head;
}
}
@@ -355,19 +357,20 @@ namespace Tgstation.Server.Host.Components.Repository
throw new JobException(ErrorCode.RepoTestMergeInvalidRemote);
// bit of sanitization
- foreach (var newTestMergeWithoutTargetCommitSha in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.TargetCommitSha)))
+ var newTestMergeModels = model.NewTestMerges!;
+ foreach (var newTestMergeWithoutTargetCommitSha in newTestMergeModels.Where(x => String.IsNullOrWhiteSpace(x.TargetCommitSha)))
newTestMergeWithoutTargetCommitSha.TargetCommitSha = null;
var repoOwner = repo.RemoteRepositoryOwner;
var repoName = repo.RemoteRepositoryName;
// optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
- Models.RevisionInformation revInfoWereLookingFor = null;
+ Models.RevisionInformation? revInfoWereLookingFor = null;
bool needToApplyRemainingPrs = true;
- if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
+ if (lastRevisionInfo!.OriginCommitSha == lastRevisionInfo.CommitSha)
{
bool cantSearch = false;
- foreach (var newTestMerge in model.NewTestMerges)
+ foreach (var newTestMerge in newTestMergeModels)
{
if (newTestMerge.TargetCommitSha != null)
#pragma warning disable CA1308 // Normalize strings to uppercase
@@ -391,56 +394,58 @@ namespace Tgstation.Server.Host.Components.Repository
if (!cantSearch)
{
- List dbPull = null;
+ List? dbPull = null;
await databaseContextFactory.UseContext(
async databaseContext =>
dbPull = await databaseContext.RevisionInformations
.AsQueryable()
- .Where(x => x.Instance.Id == instanceId
- && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
- && x.ActiveTestMerges.Count <= model.NewTestMerges.Count
- && x.ActiveTestMerges.Count > 0)
- .Include(x => x.ActiveTestMerges)
- .ThenInclude(x => x.TestMerge)
+ .Where(x => x.InstanceId == instanceId
+ && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
+ && x.ActiveTestMerges!.Count <= newTestMergeModels.Count
+ && x.ActiveTestMerges!.Count > 0)
+ .Include(x => x.ActiveTestMerges!)
+ .ThenInclude(x => x.TestMerge)
.ToListAsync(cancellationToken));
// split here cause this bit has to be done locally
- revInfoWereLookingFor = dbPull
- .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
- && x.ActiveTestMerges.Select(y => y.TestMerge)
- .All(y => model.NewTestMerges.Any(z =>
- y.Number == z.Number
- && y.TargetCommitSha.StartsWith(z.TargetCommitSha, StringComparison.Ordinal)
- && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
+ revInfoWereLookingFor = dbPull!
+ .Where(x => x.ActiveTestMerges!.Count == newTestMergeModels.Count
+ && x.ActiveTestMerges
+ .Select(y => y.TestMerge)
+ .All(y => newTestMergeModels
+ .Any(z =>
+ y.Number == z.Number
+ && y.TargetCommitSha!.StartsWith(z.TargetCommitSha!, StringComparison.Ordinal)
+ && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
.FirstOrDefault();
- if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1)
+ if (revInfoWereLookingFor == default && newTestMergeModels.Count > 1)
{
// okay try to add at least SOME prs we've seen before
- var listedNewTestMerges = model.NewTestMerges.ToList();
+ var listedNewTestMerges = newTestMergeModels.ToList();
var appliedTestMergeIds = new List();
- Models.RevisionInformation lastGoodRevInfo = null;
+ Models.RevisionInformation? lastGoodRevInfo = null;
do
{
foreach (var newTestMergeParameters in listedNewTestMerges)
{
- revInfoWereLookingFor = dbPull
+ revInfoWereLookingFor = dbPull!
.Where(testRevInfo =>
{
if (testRevInfo.PrimaryTestMerge == null)
return false;
- var testMergeMatch = model.NewTestMerges.Any(testTestMerge =>
+ var testMergeMatch = newTestMergeModels.Any(testTestMerge =>
{
var numberMatch = testRevInfo.PrimaryTestMerge.Number == testTestMerge.Number;
if (!numberMatch)
return false;
- var shaMatch = testRevInfo.PrimaryTestMerge.TargetCommitSha.StartsWith(
- testTestMerge.TargetCommitSha,
+ var shaMatch = testRevInfo.PrimaryTestMerge.TargetCommitSha!.StartsWith(
+ testTestMerge.TargetCommitSha!,
StringComparison.Ordinal);
if (!shaMatch)
return false;
@@ -453,7 +458,7 @@ namespace Tgstation.Server.Host.Components.Repository
return false;
var previousTestMergesMatch = testRevInfo
- .ActiveTestMerges
+ .ActiveTestMerges!
.Select(previousRevInfoTestMerge => previousRevInfoTestMerge.TestMerge)
.All(previousTestMerge => appliedTestMergeIds.Contains(previousTestMerge.Id));
@@ -464,7 +469,7 @@ namespace Tgstation.Server.Host.Components.Repository
if (revInfoWereLookingFor != null)
{
lastGoodRevInfo = revInfoWereLookingFor;
- appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id);
+ appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge!.Id);
listedNewTestMerges.Remove(newTestMergeParameters);
break;
}
@@ -485,16 +490,17 @@ namespace Tgstation.Server.Host.Components.Repository
if (revInfoWereLookingFor != null)
{
// goteem
- logger.LogDebug("Reusing existing SHA {sha}...", revInfoWereLookingFor.CommitSha);
- await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter($"Reset to {revInfoWereLookingFor.CommitSha[..7]}"), cancellationToken);
+ var commitSha = revInfoWereLookingFor.CommitSha!;
+ logger.LogDebug("Reusing existing SHA {sha}...", commitSha);
+ await repo.ResetToSha(commitSha, NextProgressReporter($"Reset to {commitSha[..7]}"), cancellationToken);
lastRevisionInfo = revInfoWereLookingFor;
}
if (needToApplyRemainingPrs)
{
- foreach (var newTestMerge in model.NewTestMerges)
+ foreach (var newTestMerge in newTestMergeModels)
{
- if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == newTestMerge.Number))
+ if (lastRevisionInfo.ActiveTestMerges!.Any(x => x.TestMerge.Number == newTestMerge.Number))
throw new JobException(ErrorCode.RepoDuplicateTestMerge);
var fullTestMergeTask = repo.GetTestMerge(newTestMerge, currentModel, cancellationToken);
@@ -502,7 +508,7 @@ namespace Tgstation.Server.Host.Components.Repository
var mergeResult = await repo.AddTestMerge(
newTestMerge,
committerName,
- currentModel.CommitterEmail,
+ currentModel.CommitterEmail!,
currentModel.AccessUser,
currentModel.AccessToken,
updateSubmodules,
@@ -513,7 +519,7 @@ namespace Tgstation.Server.Host.Components.Repository
throw new JobException(
ErrorCode.RepoTestMergeConflict,
new JobException(
- $"Test Merge #{newTestMerge.Number} at {newTestMerge.TargetCommitSha[..7]} conflicted! Conflicting files:{Environment.NewLine}{String.Join(Environment.NewLine, mergeResult.ConflictingFiles.Select(file => $"\t- /{file}"))}"));
+ $"Test Merge #{newTestMerge.Number} at {newTestMerge.TargetCommitSha![..7]} conflicted! Conflicting files:{Environment.NewLine}{String.Join(Environment.NewLine, mergeResult.ConflictingFiles!.Select(file => $"\t- /{file}"))}"));
Models.TestMerge fullTestMerge;
try
@@ -544,14 +550,14 @@ namespace Tgstation.Server.Host.Components.Repository
}
var currentHead = repo.Head;
- if (currentModel.PushTestMergeCommits.Value && (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead)))
+ if (currentModel.PushTestMergeCommits!.Value && (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead)))
{
- await repo.Sychronize(
+ await repo.Synchronize(
NextProgressReporter("Synchronize"),
currentModel.AccessUser,
currentModel.AccessToken,
- currentModel.CommitterName,
- currentModel.CommitterEmail,
+ currentModel.CommitterName!,
+ currentModel.CommitterEmail!,
false,
false,
cancellationToken);
diff --git a/src/Tgstation.Server.Host/Components/Repository/TestMergeResult.cs b/src/Tgstation.Server.Host/Components/Repository/TestMergeResult.cs
index 387a6baad1..2505a491d8 100644
--- a/src/Tgstation.Server.Host/Components/Repository/TestMergeResult.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/TestMergeResult.cs
@@ -17,6 +17,6 @@ namespace Tgstation.Server.Host.Components.Repository
///
/// List of conflicting file paths relative to the repository root. Only present if is .
///
- public IReadOnlyList ConflictingFiles { get; init; }
+ public IReadOnlyList? ConflictingFiles { get; init; }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Session/CombinedTopicResponse.cs b/src/Tgstation.Server.Host/Components/Session/CombinedTopicResponse.cs
index 417934f04a..ad22b6204b 100644
--- a/src/Tgstation.Server.Host/Components/Session/CombinedTopicResponse.cs
+++ b/src/Tgstation.Server.Host/Components/Session/CombinedTopicResponse.cs
@@ -5,26 +5,26 @@ using Tgstation.Server.Host.Components.Interop.Topic;
namespace Tgstation.Server.Host.Components.Session
{
///
- /// Combines a with a .
+ /// Combines a with a .
///
sealed class CombinedTopicResponse
{
///
- /// The raw .
+ /// The raw .
///
- public global::Byond.TopicSender.TopicResponse ByondTopicResponse { get; }
+ public Byond.TopicSender.TopicResponse ByondTopicResponse { get; }
///
/// The interop , if any.
///
- public TopicResponse InteropResponse { get; }
+ public TopicResponse? InteropResponse { get; }
///
/// Initializes a new instance of the class.
///
/// The value of .
/// The optional value of .
- public CombinedTopicResponse(global::Byond.TopicSender.TopicResponse byondTopicResponse, TopicResponse interopResponse)
+ public CombinedTopicResponse(Byond.TopicSender.TopicResponse byondTopicResponse, TopicResponse? interopResponse)
{
ByondTopicResponse = byondTopicResponse ?? throw new ArgumentNullException(nameof(byondTopicResponse));
InteropResponse = interopResponse;
diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs
index 2769aafa60..ccdaa448c4 100644
--- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs
+++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs
@@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Components.Session
///
/// The DMAPI .
///
- Version DMApiVersion { get; }
+ Version? DMApiVersion { get; }
///
/// Gets the associated with the .
@@ -96,7 +96,7 @@ namespace Tgstation.Server.Host.Components.Session
/// The to send.
/// The for the operation.
/// A resulting in the of /world/Topic().
- ValueTask SendCommand(TopicParameters parameters, CancellationToken cancellationToken);
+ ValueTask SendCommand(TopicParameters parameters, CancellationToken cancellationToken);
///
/// Attempts to change the current to .
diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs
index 26cc5e4a63..7058d3f4e4 100644
--- a/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Session/ISessionControllerFactory.cs
@@ -16,14 +16,14 @@ namespace Tgstation.Server.Host.Components.Session
/// Create a from a freshly launch DreamDaemon instance.
///
/// The to use.
- /// The current if any.
+ /// The current . if any.
/// The to use. will be updated with the minumum required security level for the launch.
/// If the should only validate the DMAPI then exit.
/// The for the operation.
/// A resulting in a new .
ValueTask LaunchNew(
IDmbProvider dmbProvider,
- IEngineExecutableLock currentByondLock,
+ IEngineExecutableLock? currentByondLock,
DreamDaemonLaunchParameters launchParameters,
bool apiValidate,
CancellationToken cancellationToken);
@@ -34,7 +34,7 @@ namespace Tgstation.Server.Host.Components.Session
/// The to use.
/// The for the operation.
/// A resulting in a new on success or on failure to reattach.
- ValueTask Reattach(
+ ValueTask Reattach(
ReattachInformation reattachInformation,
CancellationToken cancellationToken);
}
diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionPersistor.cs b/src/Tgstation.Server.Host/Components/Session/ISessionPersistor.cs
index 5b1f1d7200..9d3806c317 100644
--- a/src/Tgstation.Server.Host/Components/Session/ISessionPersistor.cs
+++ b/src/Tgstation.Server.Host/Components/Session/ISessionPersistor.cs
@@ -29,7 +29,7 @@ namespace Tgstation.Server.Host.Components.Session
///
/// The for the operation.
/// A resulting in the stored if any.
- ValueTask Load(CancellationToken cancellationToken);
+ ValueTask Load(CancellationToken cancellationToken);
///
/// Clear any stored .
diff --git a/src/Tgstation.Server.Host/Components/Session/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Session/LaunchResult.cs
index fd5785fd41..eb44b6e55f 100644
--- a/src/Tgstation.Server.Host/Components/Session/LaunchResult.cs
+++ b/src/Tgstation.Server.Host/Components/Session/LaunchResult.cs
@@ -11,12 +11,12 @@ namespace Tgstation.Server.Host.Components.Session
///
/// The time it took for to return or the initial bridge request to process. If the startup timed out.
///
- public TimeSpan? StartupTime { get; set; }
+ public TimeSpan? StartupTime { get; init; }
///
/// The if it exited.
///
- public int? ExitCode { get; set; }
+ public int? ExitCode { get; init; }
///
public override string ToString() => String.Format(CultureInfo.InvariantCulture, "Exit Code: {0}, Time {1}ms", ExitCode, StartupTime?.TotalMilliseconds);
diff --git a/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs b/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs
index 6c429d2d0a..df2ce141d4 100644
--- a/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs
+++ b/src/Tgstation.Server.Host/Components/Session/ReattachInformation.cs
@@ -20,12 +20,12 @@ namespace Tgstation.Server.Host.Components.Session
///
/// The initially used to launch DreamDaemon. Should be a different than . Should not be set if persisting the initial isn't necessary.
///
- public IDmbProvider InitialDmb { get; set; }
+ public IDmbProvider? InitialDmb { get; set; }
///
/// The for the DMAPI.
///
- public RuntimeInformation RuntimeInformation { get; private set; }
+ public RuntimeInformation? RuntimeInformation { get; private set; }
///
/// The which indicates when topic requests should timeout.
@@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.Components.Session
public ReattachInformation(
Models.ReattachInformation copy,
IDmbProvider dmb,
- IDmbProvider initialDmb,
+ IDmbProvider? initialDmb,
TimeSpan topicRequestTimeout)
: base(copy)
{
@@ -72,13 +72,12 @@ namespace Tgstation.Server.Host.Components.Session
RuntimeInformation runtimeInformation,
string accessIdentifier,
ushort port)
+ : base(accessIdentifier)
{
Dmb = dmb ?? throw new ArgumentNullException(nameof(dmb));
ProcessId = process?.Id ?? throw new ArgumentNullException(nameof(process));
RuntimeInformation = runtimeInformation ?? throw new ArgumentNullException(nameof(runtimeInformation));
- AccessIdentifier = accessIdentifier ?? throw new ArgumentNullException(nameof(accessIdentifier));
-
LaunchSecurityLevel = runtimeInformation.SecurityLevel;
LaunchVisibility = runtimeInformation.Visibility;
Port = port;
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
index 2e57410eab..ad9bdf1728 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs
@@ -16,6 +16,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Components.Chat;
+using Tgstation.Server.Host.Components.Chat.Commands;
using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.Components.Engine;
using Tgstation.Server.Host.Components.Interop;
@@ -59,7 +60,7 @@ namespace Tgstation.Server.Host.Components.Session
public RebootState RebootState => ReattachInformation.RebootState;
///
- public Version DMApiVersion { get; private set; }
+ public Version? DMApiVersion { get; private set; }
///
public bool TerminationWasRequested { get; private set; }
@@ -121,7 +122,7 @@ namespace Tgstation.Server.Host.Components.Session
///
/// The for the .
///
- readonly IBridgeRegistration bridgeRegistration;
+ readonly IBridgeRegistration? bridgeRegistration;
///
/// The for the .
@@ -131,7 +132,7 @@ namespace Tgstation.Server.Host.Components.Session
///
/// The for the .
///
- readonly IEngineExecutableLock byondLock;
+ readonly IEngineExecutableLock engineLock;
///
/// The for the .
@@ -196,7 +197,7 @@ namespace Tgstation.Server.Host.Components.Session
///
/// for shutting down the server if it is taking too long after validation.
///
- volatile Task postValidationShutdownTask;
+ volatile Task? postValidationShutdownTask;
///
/// The number of currently active calls to from TgsReboot().
@@ -224,7 +225,7 @@ namespace Tgstation.Server.Host.Components.Session
/// The value of .
/// The owning .
/// The value of .
- /// The value of .
+ /// The value of .
/// The value of .
/// The used to populate .
/// The value of .
@@ -240,7 +241,7 @@ namespace Tgstation.Server.Host.Components.Session
ReattachInformation reattachInformation,
Api.Models.Instance metadata,
IProcess process,
- IEngineExecutableLock byondLock,
+ IEngineExecutableLock engineLock,
Byond.TopicSender.ITopicClient byondTopicSender,
IChatTrackingContext chatTrackingContext,
IBridgeRegistrar bridgeRegistrar,
@@ -257,7 +258,7 @@ namespace Tgstation.Server.Host.Components.Session
ReattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
this.process = process ?? throw new ArgumentNullException(nameof(process));
- this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock));
+ this.engineLock = engineLock ?? throw new ArgumentNullException(nameof(engineLock));
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
this.chatTrackingContext = chatTrackingContext ?? throw new ArgumentNullException(nameof(chatTrackingContext));
ArgumentNullException.ThrowIfNull(bridgeRegistrar);
@@ -338,16 +339,21 @@ namespace Tgstation.Server.Host.Components.Session
Logger.LogTrace("Disposing...");
reattachTopicCts.Cancel();
- var semaphoreLockTask = TopicSendSemaphore.Lock(CancellationToken.None); // DCT: None available
+ var cancellationToken = CancellationToken.None; // DCT: None available
+ var semaphoreLockTask = TopicSendSemaphore.Lock(cancellationToken);
if (!released)
{
- process.Terminate();
- await process.Lifetime;
+ await engineLock.StopServerProcess(
+ Logger,
+ process,
+ ReattachInformation.AccessIdentifier,
+ ReattachInformation.Port,
+ cancellationToken);
}
await process.DisposeAsync();
- byondLock.Dispose();
+ engineLock.Dispose();
bridgeRegistration?.Dispose();
var regularDmbDisposeTask = ReattachInformation.Dmb.DisposeAsync();
var initialDmb = ReattachInformation.InitialDmb;
@@ -367,7 +373,7 @@ namespace Tgstation.Server.Host.Components.Session
}
///
- public async ValueTask ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
+ public async ValueTask ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(parameters);
@@ -393,13 +399,13 @@ namespace Tgstation.Server.Host.Components.Session
ReattachInformation.Dmb.KeepAlive();
ReattachInformation.InitialDmb?.KeepAlive();
- byondLock.DoNotDeleteThisSession();
+ engineLock.DoNotDeleteThisSession();
released = true;
return DisposeAsync();
}
///
- public ValueTask SendCommand(TopicParameters parameters, CancellationToken cancellationToken)
+ public ValueTask SendCommand(TopicParameters parameters, CancellationToken cancellationToken)
=> SendCommand(parameters, false, cancellationToken);
///
@@ -430,10 +436,10 @@ namespace Tgstation.Server.Host.Components.Session
public void AdjustPriority(bool higher) => process.AdjustPriority(higher);
///
- public void Suspend() => process.Suspend();
+ public void SuspendProcess() => process.SuspendProcess();
///
- public void Resume() => process.Resume();
+ public void ResumeProcess() => process.ResumeProcess();
///
public IAsyncDisposable ReplaceDmbProvider(IDmbProvider dmbProvider)
@@ -446,7 +452,10 @@ namespace Tgstation.Server.Host.Components.Session
///
public async ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
{
- ReattachInformation.RuntimeInformation.InstanceName = newInstanceName;
+ var runtimeInformation = ReattachInformation.RuntimeInformation;
+ if (runtimeInformation != null)
+ runtimeInformation.InstanceName = newInstanceName;
+
await SendCommand(
TopicParameters.CreateInstanceRenamedTopicParameters(newInstanceName),
cancellationToken);
@@ -512,7 +521,7 @@ namespace Tgstation.Server.Host.Components.Session
var reattachResponse = await SendCommand(
new TopicParameters(
assemblyInformationProvider.Version,
- ReattachInformation.RuntimeInformation.ServerPort),
+ ReattachInformation.RuntimeInformation!.ServerPort),
true,
reattachTopicCts.Token);
@@ -526,7 +535,7 @@ namespace Tgstation.Server.Host.Components.Session
? LogLevel.Warning
: LogLevel.Debug,
"DMAPI Interop v{interopVersion} isn't returning the TGS custom commands list. Functionality added in v5.2.0.",
- CompileJob.DMApiVersion.Semver());
+ CompileJob.DMApiVersion!.Semver());
}
}
@@ -574,7 +583,7 @@ namespace Tgstation.Server.Host.Components.Session
/// The for the operation.
/// A resulting in the for the request or if the request could not be dispatched.
#pragma warning disable CA1502 // TODO: Decomplexify
- async ValueTask ProcessBridgeCommand(BridgeParameters parameters, CancellationToken cancellationToken)
+ async ValueTask ProcessBridgeCommand(BridgeParameters parameters, CancellationToken cancellationToken)
{
var response = new BridgeResponse();
switch (parameters.CommandType)
@@ -638,7 +647,7 @@ namespace Tgstation.Server.Host.Components.Session
// TODO: When OD figures out how to unite port and topic_port, set an upper version bound on OD for this check
if (DMApiVersion.Major != DMApiConstants.InteropVersion.Major
- || (EngineVersion.Engine.Value == EngineType.OpenDream && DMApiVersion < new Version(5, 7)))
+ || (EngineVersion.Engine == EngineType.OpenDream && DMApiVersion < new Version(5, 7)))
{
apiValidationStatus = ApiValidationStatus.Incompatible;
return BridgeError("Incompatible dmApiVersion!");
@@ -663,10 +672,11 @@ namespace Tgstation.Server.Host.Components.Session
Logger.LogTrace("ApiValidationStatus set to {apiValidationStatus}", apiValidationStatus);
+ // we create new runtime info here because of potential .Dmb changes (i think. i forget...)
response.RuntimeInformation = new RuntimeInformation(
chatTrackingContext,
ReattachInformation.Dmb,
- ReattachInformation.RuntimeInformation.ServerVersion,
+ ReattachInformation.RuntimeInformation!.ServerVersion,
ReattachInformation.RuntimeInformation.InstanceName,
ReattachInformation.RuntimeInformation.SecurityLevel,
ReattachInformation.RuntimeInformation.Visibility,
@@ -677,11 +687,11 @@ namespace Tgstation.Server.Host.Components.Session
{
var newTopicPort = parameters.TopicPort.Value;
Logger.LogInformation("Server is requesting use of port {topicPort} for topic communications", newTopicPort);
- ReattachInformation.Port = newTopicPort;
+ ReattachInformation.TopicPort = newTopicPort;
}
// Load custom commands
- chatTrackingContext.CustomCommands = parameters.CustomCommands;
+ chatTrackingContext.CustomCommands = parameters.CustomCommands ?? Array.Empty();
chatTrackingContext.Active = true;
Interlocked.Exchange(ref startupTcs, new TaskCompletionSource()).SetResult();
break;
@@ -731,7 +741,7 @@ namespace Tgstation.Server.Host.Components.Session
/// The to send.
/// The for the operation.
/// A resulting in the of the topic request.
- async ValueTask SendTopicRequest(TopicParameters parameters, CancellationToken cancellationToken)
+ async ValueTask SendTopicRequest(TopicParameters parameters, CancellationToken cancellationToken)
{
parameters.AccessIdentifier = ReattachInformation.AccessIdentifier;
@@ -757,14 +767,14 @@ namespace Tgstation.Server.Host.Components.Session
var payloadId = NextPayloadId;
// AccessIdentifer is just noise in a chunked request
- parameters.AccessIdentifier = null;
+ parameters.AccessIdentifier = null!;
GenerateQueryString(parameters, out json);
// yes, this straight up ignores unicode, precalculating it is useless when we don't
// even know if the UTF8 bytes of the url encoded chunk will fit the window until we do said encoding
var fullPayloadSize = (uint)json.Length;
- List chunkQueryStrings = null;
+ List? chunkQueryStrings = null;
for (var chunkCount = 2; chunkQueryStrings == null; ++chunkCount)
{
var standardChunkSize = fullPayloadSize / chunkCount;
@@ -810,7 +820,7 @@ namespace Tgstation.Server.Host.Components.Session
Logger.LogTrace("Chunking topic request ({totalChunks} total)...", chunkQueryStrings.Count);
- CombinedTopicResponse combinedResponse = null;
+ CombinedTopicResponse? combinedResponse = null;
bool LogRequestIssue(bool possiblyFromCompletedRequest)
{
if (combinedResponse?.InteropResponse == null || combinedResponse.InteropResponse.ErrorMessage != null)
@@ -834,11 +844,12 @@ namespace Tgstation.Server.Host.Components.Session
return null;
}
- while ((combinedResponse.InteropResponse.MissingChunks?.Count ?? 0) > 0)
+ while ((combinedResponse?.InteropResponse?.MissingChunks?.Count ?? 0) > 0)
{
Logger.LogWarning("DD is still missing some chunks of topic request P{payloadId}! Sending missing chunks...", payloadId);
- var lastIndex = combinedResponse.InteropResponse.MissingChunks.Last();
- foreach (var missingChunkIndex in combinedResponse.InteropResponse.MissingChunks)
+ var missingChunks = combinedResponse!.InteropResponse!.MissingChunks!;
+ var lastIndex = missingChunks.Last();
+ foreach (var missingChunkIndex in missingChunks)
{
var chunkCommandString = chunkQueryStrings[(int)missingChunkIndex];
combinedResponse = await SendRawTopic(chunkCommandString, topicPriority, cancellationToken);
@@ -874,7 +885,7 @@ namespace Tgstation.Server.Host.Components.Session
/// If this is a priority message. If so, the topic will make 5 attempts to send unless BYOND reboots or exits.
/// The for the operation.
/// A resulting in the of the topic request.
- async ValueTask SendRawTopic(string queryString, bool priority, CancellationToken cancellationToken)
+ async ValueTask SendRawTopic(string queryString, bool priority, CancellationToken cancellationToken)
{
if (disposed)
{
@@ -883,8 +894,8 @@ namespace Tgstation.Server.Host.Components.Session
return null;
}
- var targetPort = ReattachInformation.Port;
- Byond.TopicSender.TopicResponse byondResponse;
+ var targetPort = ReattachInformation.TopicPort ?? ReattachInformation.Port;
+ Byond.TopicSender.TopicResponse? byondResponse;
using (await TopicSendSemaphore.Lock(cancellationToken))
byondResponse = await byondTopicSender.SendWithOptionalPriority(
asyncDelayer,
@@ -906,7 +917,7 @@ namespace Tgstation.Server.Host.Components.Session
var topicReturn = byondResponse.StringData;
- TopicResponse interopResponse = null;
+ TopicResponse? interopResponse = null;
if (topicReturn != null)
try
{
@@ -927,7 +938,7 @@ namespace Tgstation.Server.Host.Components.Session
/// If waiting for the should be bypassed.
/// The for the operation.
/// A resulting in the of /world/Topic().
- async ValueTask SendCommand(TopicParameters parameters, bool bypassLaunchResult, CancellationToken cancellationToken)
+ async ValueTask SendCommand(TopicParameters parameters, bool bypassLaunchResult, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(parameters);
@@ -989,7 +1000,7 @@ namespace Tgstation.Server.Host.Components.Session
}
}
- TopicResponse fullResponse = null;
+ TopicResponse? fullResponse = null;
var lifetimeWatchingTask = CancelIfLifetimeElapses();
try
{
@@ -1007,14 +1018,14 @@ namespace Tgstation.Server.Host.Components.Session
{
Logger.LogTrace("Topic response is chunked...");
- ChunkData nextChunk = combinedResponse.InteropResponse.Chunk;
+ ChunkData? nextChunk = combinedResponse.InteropResponse.Chunk;
do
{
var nextRequest = await ProcessChunk(
(completedResponse, _) =>
{
fullResponse = completedResponse;
- return ValueTask.FromResult(null);
+ return ValueTask.FromResult(null);
},
error =>
{
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
index a54f9f8874..70bed0f5ba 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs
@@ -225,7 +225,7 @@ namespace Tgstation.Server.Host.Components.Session
#pragma warning disable CA1506 // TODO: Decomplexify
public async ValueTask LaunchNew(
IDmbProvider dmbProvider,
- IEngineExecutableLock currentByondLock,
+ IEngineExecutableLock? currentByondLock,
DreamDaemonLaunchParameters launchParameters,
bool apiValidate,
CancellationToken cancellationToken)
@@ -268,17 +268,17 @@ namespace Tgstation.Server.Host.Components.Session
dmbProvider.CompileJob.Id);
// mad this isn't abstracted but whatever
- var engineType = dmbProvider.EngineVersion.Engine.Value;
+ var engineType = dmbProvider.EngineVersion.Engine!.Value;
if (engineType == EngineType.Byond)
await CheckPagerIsNotRunning();
await PortBindTest(launchParameters.Port.Value, engineType, cancellationToken);
- string outputFilePath = null;
+ string? outputFilePath = null;
var preserveLogFile = true;
var hasStandardOutput = engineLock.HasStandardOutput;
- if (launchParameters.LogOutput.Value)
+ if (launchParameters.LogOutput!.Value)
{
var now = DateTimeOffset.UtcNow;
var dateDirectory = diagnosticsIOManager.ConcatPath(DreamDaemonLogsPath, now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
@@ -320,8 +320,8 @@ namespace Tgstation.Server.Host.Components.Session
var runtimeInformation = CreateRuntimeInformation(
dmbProvider,
chatTrackingContext,
- launchParameters.SecurityLevel.Value,
- launchParameters.Visibility.Value,
+ launchParameters.SecurityLevel!.Value,
+ launchParameters.Visibility!.Value,
apiValidate);
var reattachInformation = new ReattachInformation(
@@ -333,7 +333,7 @@ namespace Tgstation.Server.Host.Components.Session
var byondTopicSender = topicClientFactory.CreateTopicClient(
TimeSpan.FromMilliseconds(
- launchParameters.TopicRequestTimeout.Value));
+ launchParameters.TopicRequestTimeout!.Value));
var sessionController = new SessionController(
reattachInformation,
@@ -385,7 +385,7 @@ namespace Tgstation.Server.Host.Components.Session
#pragma warning restore CA1506
///
- public async ValueTask Reattach(
+ public async ValueTask Reattach(
ReattachInformation reattachInformation,
CancellationToken cancellationToken)
{
@@ -450,19 +450,21 @@ namespace Tgstation.Server.Host.Components.Session
}
catch
{
- chatTrackingContext.Dispose();
+ chatTrackingContext?.Dispose();
throw;
}
}
catch
{
- await process.DisposeAsync();
+ if (process != null)
+ await process.DisposeAsync();
+
throw;
}
}
catch
{
- engineLock.Dispose();
+ engineLock?.Dispose();
throw;
}
}
@@ -474,7 +476,7 @@ namespace Tgstation.Server.Host.Components.Session
/// The .
/// The .
/// The secure string to use for the session.
- /// The full path to log DreamDaemon output to.
+ /// The optional full path to log DreamDaemon output to.
/// If we are only validating the DMAPI then exiting.
/// The for the operation.
/// A resulting in the DreamDaemon .
@@ -483,7 +485,7 @@ namespace Tgstation.Server.Host.Components.Session
IEngineExecutableLock engineLock,
DreamDaemonLaunchParameters launchParameters,
string accessIdentifier,
- string logFilePath,
+ string? logFilePath,
bool apiValidate,
CancellationToken cancellationToken)
{
@@ -555,19 +557,19 @@ namespace Tgstation.Server.Host.Components.Session
/// If , will be deleted.
/// The for the operation.
/// A representing the running operation.
- async ValueTask LogDDOutput(IProcess process, string outputFilePath, bool cliSupported, bool preserveFile, CancellationToken cancellationToken)
+ async ValueTask LogDDOutput(IProcess process, string? outputFilePath, bool cliSupported, bool preserveFile, CancellationToken cancellationToken)
{
try
{
- string ddOutput = null;
+ string? ddOutput = null;
if (cliSupported)
- ddOutput = await process.GetCombinedOutput(cancellationToken);
+ ddOutput = (await process.GetCombinedOutput(cancellationToken))!;
if (ddOutput == null)
try
{
var dreamDaemonLogBytes = await gameIOManager.ReadAllBytes(
- outputFilePath,
+ outputFilePath!,
cancellationToken);
ddOutput = Encoding.UTF8.GetString(dreamDaemonLogBytes);
@@ -578,7 +580,7 @@ namespace Tgstation.Server.Host.Components.Session
try
{
logger.LogTrace("Deleting temporary log file {path}...", outputFilePath);
- await gameIOManager.DeleteFile(outputFilePath, cancellationToken);
+ await gameIOManager.DeleteFile(outputFilePath!, cancellationToken);
}
catch (Exception ex)
{
@@ -618,7 +620,7 @@ namespace Tgstation.Server.Host.Components.Session
chatTrackingContext,
dmbProvider,
assemblyInformationProvider.Version,
- instance.Name,
+ instance.Name!,
securityLevel,
visibility,
serverPortProvider.HttpApiPort,
diff --git a/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs b/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs
index e932fa6ec7..a916ea792b 100644
--- a/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs
+++ b/src/Tgstation.Server.Host/Components/Session/SessionPersistor.cs
@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.Database;
+using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.System;
using Z.EntityFramework.Plus;
@@ -73,11 +74,10 @@ namespace Tgstation.Server.Host.Components.Session
await ClearImpl(db, false, cancellationToken);
- var dbReattachInfo = new Models.ReattachInformation
+ var dbReattachInfo = new Models.ReattachInformation(reattachInformation.AccessIdentifier)
{
- AccessIdentifier = reattachInformation.AccessIdentifier,
- CompileJobId = reattachInformation.Dmb.CompileJob.Id.Value,
- InitialCompileJobId = reattachInformation.InitialDmb?.CompileJob.Id.Value,
+ CompileJobId = reattachInformation.Dmb.CompileJob.Require(x => x.Id),
+ InitialCompileJobId = reattachInformation.InitialDmb?.CompileJob.Require(x => x.Id),
Port = reattachInformation.Port,
ProcessId = reattachInformation.ProcessId,
RebootState = reattachInformation.RebootState,
@@ -88,7 +88,7 @@ namespace Tgstation.Server.Host.Components.Session
db.ReattachInformations.Add(dbReattachInfo);
await db.Save(cancellationToken);
- reattachInformation.Id = dbReattachInfo.Id.Value;
+ reattachInformation.Id = dbReattachInfo.Id!.Value;
logger.LogDebug("Saved reattach information: {info}", reattachInformation);
});
@@ -101,7 +101,7 @@ namespace Tgstation.Server.Host.Components.Session
logger.LogTrace("Updating reattach information: {info}...", reattachInformation);
- var dbReattachInfo = new Models.ReattachInformation
+ var dbReattachInfo = new Models.ReattachInformation(String.Empty)
{
Id = reattachInformation.Id.Value,
};
@@ -109,8 +109,8 @@ namespace Tgstation.Server.Host.Components.Session
db.ReattachInformations.Attach(dbReattachInfo);
dbReattachInfo.AccessIdentifier = reattachInformation.AccessIdentifier;
- dbReattachInfo.CompileJobId = reattachInformation.Dmb.CompileJob.Id.Value;
- dbReattachInfo.InitialCompileJobId = reattachInformation.InitialDmb?.CompileJob.Id.Value;
+ dbReattachInfo.CompileJobId = reattachInformation.Dmb.CompileJob.Require(x => x.Id);
+ dbReattachInfo.InitialCompileJobId = reattachInformation.InitialDmb?.CompileJob.Require(x => x.Id);
dbReattachInfo.Port = reattachInformation.Port;
dbReattachInfo.ProcessId = reattachInformation.ProcessId;
dbReattachInfo.RebootState = reattachInformation.RebootState;
@@ -123,9 +123,9 @@ namespace Tgstation.Server.Host.Components.Session
});
///
- public async ValueTask Load(CancellationToken cancellationToken)
+ public async ValueTask Load(CancellationToken cancellationToken)
{
- Models.ReattachInformation result = null;
+ Models.ReattachInformation? result = null;
TimeSpan? topicTimeout = null;
async ValueTask KillProcess(Models.ReattachInformation reattachInfo)
@@ -159,19 +159,19 @@ namespace Tgstation.Server.Host.Components.Session
var dbReattachInfos = await db
.ReattachInformations
.AsQueryable()
- .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id)
+ .Where(x => x.CompileJob!.Job.Instance!.Id == metadata.Id)
.Include(x => x.CompileJob)
.Include(x => x.InitialCompileJob)
.ToListAsync(cancellationToken);
result = dbReattachInfos.FirstOrDefault();
- if (result == default)
+ if (result == null)
return;
var timeoutMilliseconds = await db
.Instances
.AsQueryable()
.Where(x => x.Id == metadata.Id)
- .Select(x => x.DreamDaemonSettings.TopicRequestTimeout)
+ .Select(x => x.DreamDaemonSettings!.TopicRequestTimeout)
.FirstOrDefaultAsync(cancellationToken);
if (timeoutMilliseconds == default)
@@ -206,7 +206,7 @@ namespace Tgstation.Server.Host.Components.Session
return null;
}
- var dmb = await dmbFactory.FromCompileJob(result.CompileJob, cancellationToken);
+ var dmb = await dmbFactory.FromCompileJob(result!.CompileJob!, cancellationToken);
if (dmb == null)
{
logger.LogError("Unable to reattach! Could not load .dmb!");
@@ -224,7 +224,7 @@ namespace Tgstation.Server.Host.Components.Session
return null;
}
- IDmbProvider initialDmb = null;
+ IDmbProvider? initialDmb = null;
if (result.InitialCompileJob != null)
{
logger.LogTrace("Loading initial compile job...");
@@ -265,7 +265,7 @@ namespace Tgstation.Server.Host.Components.Session
var baseQuery = databaseContext
.ReattachInformations
.AsQueryable()
- .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id);
+ .Where(x => x.CompileJob!.Job.Instance!.Id == metadata.Id);
if (instant)
await baseQuery
diff --git a/src/Tgstation.Server.Host/Components/Session/TopicClientFactory.cs b/src/Tgstation.Server.Host/Components/Session/TopicClientFactory.cs
index 29768e7604..3ed153a641 100644
--- a/src/Tgstation.Server.Host/Components/Session/TopicClientFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Session/TopicClientFactory.cs
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Components.Session
///
/// The for created s.
///
- readonly ILogger logger;
+ readonly ILogger? logger;
///
/// Initializes a new instance of the class.
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
index 5e871c2551..8261afe918 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs
@@ -78,7 +78,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
eventType => new KeyValuePair>(
eventType,
typeof(EventType)
- .GetField(eventType.ToString())
+ .GetField(eventType.ToString())!
.GetCustomAttributes(false)
.OfType()
.First()
@@ -199,7 +199,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
///
- public async ValueTask CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken)
+ public async ValueTask CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken)
{
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
{
@@ -232,12 +232,19 @@ namespace Tgstation.Server.Host.Components.StaticFiles
static string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath);
- return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false);
+ return new ServerSideModifications(
+ headFileExistsTask.Result
+ ? IncludeLine(CodeModificationsHeadFile)
+ : null,
+ tailFileExistsTask.Result
+ ? IncludeLine(CodeModificationsTailFile)
+ : null,
+ false);
}
}
///
- public async ValueTask> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
+ public async ValueTask?> ListDirectory(string? configurationRelativePath, ISystemIdentity? systemIdentity, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken);
var path = ValidateConfigRelativePath(configurationRelativePath);
@@ -284,12 +291,12 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
///
- public async ValueTask Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
+ public async ValueTask Read(string configurationRelativePath, ISystemIdentity? systemIdentity, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken);
var path = ValidateConfigRelativePath(configurationRelativePath);
- ConfigurationFileResponse result = null;
+ ConfigurationFileResponse? result = null;
void ReadImpl()
{
@@ -319,7 +326,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
},
async cancellationToken =>
{
- FileStream result = null;
+ FileStream? result = null;
void GetFileStream()
{
result = ioManager.GetFileStream(path, false);
@@ -330,7 +337,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
else
await systemIdentity.RunImpersonated(GetFileStream, cancellationToken);
- return result;
+ return result!;
},
path,
false));
@@ -451,12 +458,12 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
///
- public async ValueTask Write(string configurationRelativePath, ISystemIdentity systemIdentity, string previousHash, CancellationToken cancellationToken)
+ public async ValueTask Write(string configurationRelativePath, ISystemIdentity? systemIdentity, string? previousHash, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken);
var path = ValidateConfigRelativePath(configurationRelativePath);
- ConfigurationFileResponse result = null;
+ ConfigurationFileResponse? result = null;
void WriteImpl()
{
@@ -561,7 +568,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
///
- public async ValueTask CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
+ public async ValueTask CreateDirectory(string configurationRelativePath, ISystemIdentity? systemIdentity, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken);
var path = ValidateConfigRelativePath(configurationRelativePath);
@@ -583,7 +590,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
await systemIdentity.RunImpersonated(DoCreate, cancellationToken);
}
- return result.Value;
+ return result!.Value;
}
///
@@ -593,7 +600,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
public Task StopAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken);
///
- public async ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
+ public async ValueTask HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(parameters);
@@ -630,6 +637,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles
' ',
parameters.Select(arg =>
{
+ if (arg == null)
+ return "(NULL)";
+
if (!arg.Contains(' ', StringComparison.Ordinal))
return arg;
@@ -657,7 +667,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
///
- public async ValueTask DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
+ public async ValueTask DeleteDirectory(string configurationRelativePath, ISystemIdentity? systemIdentity, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken);
var path = ValidateConfigRelativePath(configurationRelativePath);
@@ -735,16 +745,16 @@ namespace Tgstation.Server.Host.Components.StaticFiles
///
/// A relative path in the instance's configuration directory.
/// The full on-disk path of .
- string ValidateConfigRelativePath(string configurationRelativePath)
+ string ValidateConfigRelativePath(string? configurationRelativePath)
{
var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath);
if (nullOrEmptyCheck)
configurationRelativePath = DefaultIOManager.CurrentDirectory;
- if (configurationRelativePath[0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar)
+ if (configurationRelativePath![0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar)
configurationRelativePath = DefaultIOManager.CurrentDirectory + configurationRelativePath;
var resolved = ioManager.ResolvePath(configurationRelativePath);
var local = !nullOrEmptyCheck ? ioManager.ResolvePath() : null;
- if (!nullOrEmptyCheck && resolved.Length < local.Length) // .. fuccbois
+ if (!nullOrEmptyCheck && resolved.Length < local!.Length) // .. fuccbois
throw new InvalidOperationException("Attempted to access file outside of configuration manager!");
return resolved;
}
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs
index 59546a044e..d156df54cb 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs
@@ -22,7 +22,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// Path to the destination folder.
/// The for the operation.
/// A resulting in the if any.
- ValueTask CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken);
+ ValueTask CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken);
///
/// Symlinks all directories in the GameData directory to .
@@ -39,7 +39,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// The for the operation. If , the operation will be performed as the user of the .
/// The for the operation.
/// A resulting in an of the s for the items in the directory. and will both be . will be returned if the operation failed due to access contention.
- ValueTask> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
+ ValueTask?> ListDirectory(string? configurationRelativePath, ISystemIdentity? systemIdentity, CancellationToken cancellationToken);
///
/// Reads a given .
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// The for the operation. If , the operation will be performed as the user of the .
/// The for the operation.
/// A resulting in the of the file. will be returned if the operation failed due to access contention.
- ValueTask Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
+ ValueTask Read(string configurationRelativePath, ISystemIdentity? systemIdentity, CancellationToken cancellationToken);
///
/// Create an empty directory at .
@@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// The for the operation. If , the operation will be performed as the user of the .
/// The for the operation. Usage may result in partial writes.
/// A resulting in if the directory already existed, otherwise. will be returned if the operation failed due to access contention.
- ValueTask CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
+ ValueTask CreateDirectory(string configurationRelativePath, ISystemIdentity? systemIdentity, CancellationToken cancellationToken);
///
/// Attempt to delete an empty directory at .
@@ -66,7 +66,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// The for the operation. If , the operation will be performed as the user of the .
/// The for the operation.
/// A resulting in if the directory was empty and deleted, otherwise. will be returned if the operation failed due to access contention.
- ValueTask DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
+ ValueTask DeleteDirectory(string configurationRelativePath, ISystemIdentity? systemIdentity, CancellationToken cancellationToken);
///
/// Writes to a given .
@@ -76,6 +76,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// The hash any existing file must match in order for the write to succeed.
/// The for the operation. Usage may result in partial writes.
/// A resulting in the updated and associated writing . will be returned if the operation failed due to access contention.
- ValueTask Write(string configurationRelativePath, ISystemIdentity systemIdentity, string previousHash, CancellationToken cancellationToken);
+ ValueTask Write(string configurationRelativePath, ISystemIdentity? systemIdentity, string? previousHash, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs b/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs
index 6b7e358f0b..e28920a06e 100644
--- a/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs
+++ b/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs
@@ -13,12 +13,12 @@
///
/// The #include line which should be added to the beginning of the .dme if any.
///
- public string HeadIncludeLine { get; }
+ public string? HeadIncludeLine { get; }
///
/// The #include line which should be added to the end of the .dme if any.
///
- public string TailIncludeLine { get; }
+ public string? TailIncludeLine { get; }
///
/// Initializes a new instance of the class.
@@ -26,7 +26,7 @@
/// The value of .
/// The value of .
/// The value of .
- public ServerSideModifications(string headIncludeLine, string tailIncludeLine, bool totalDmeOverwrite)
+ public ServerSideModifications(string? headIncludeLine, string? tailIncludeLine, bool totalDmeOverwrite)
{
HeadIncludeLine = headIncludeLine;
TailIncludeLine = tailIncludeLine;
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs
index 4495a99fe3..884541e9fb 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/AdvancedWatchdog.cs
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
/// The for .
///
- protected SwappableDmbProvider ActiveSwappable { get; private set; }
+ protected SwappableDmbProvider? ActiveSwappable { get; private set; }
///
/// The for the .
@@ -43,12 +43,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
/// The active for .
///
- SwappableDmbProvider pendingSwappable;
+ SwappableDmbProvider? pendingSwappable;
///
/// The representing the cleanup of an unused .
///
- volatile TaskCompletionSource deploymentCleanupGate;
+ volatile TaskCompletionSource? deploymentCleanupGate;
///
/// Initializes a new instance of the class.
@@ -141,11 +141,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
ValueTask RunPrequel() => BeforeApplyDmb(pendingSwappable.CompileJob, cancellationToken);
var needToSwap = !pendingSwappable.Swapped;
+ var controller = Server!;
if (needToSwap)
{
// IMPORTANT: THE SESSIONCONTROLLER SHOULD STILL BE PROCESSING THE BRIDGE REQUEST SO WE KNOW DD IS SLEEPING
// OTHERWISE, IT COULD RETURN TO /world/Reboot() TOO EARLY AND LOAD THE WRONG .DMB
- if (!Server.ProcessingRebootBridgeRequest)
+ if (!controller.ProcessingRebootBridgeRequest)
{
// integration test logging will catch this
Logger.LogError(
@@ -168,7 +169,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (needToSwap)
await PerformDmbSwap(pendingSwappable, cancellationToken);
- var currentCompileJobId = Server.ReattachInformation.Dmb.CompileJob.Id;
+ var currentCompileJobId = controller.ReattachInformation.Dmb.CompileJob.Id;
await DrainDeploymentCleanupTasks(false);
@@ -176,7 +177,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var localDeploymentCleanupGate = new TaskCompletionSource();
async Task CleanupLingeringDeployment()
{
- var lingeringDeploymentExpirySeconds = ActiveLaunchParameters.StartupTimeout.Value;
+ var lingeringDeploymentExpirySeconds = ActiveLaunchParameters.StartupTimeout!.Value;
Logger.LogDebug(
"Holding old deployment {compileJobId} for up to {expiry} seconds...",
currentCompileJobId,
@@ -209,7 +210,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
lock (deploymentCleanupTasks)
{
- lingeringDeployment = Server.ReplaceDmbProvider(pendingSwappable);
+ lingeringDeployment = controller.ReplaceDmbProvider(pendingSwappable);
deploymentCleanupTasks.Add(
CleanupLingeringDeployment());
}
@@ -217,7 +218,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
ActiveSwappable = pendingSwappable;
pendingSwappable = null;
- await SessionPersistor.Update(Server.ReattachInformation, cancellationToken);
+ await SessionPersistor.Update(controller.ReattachInformation, cancellationToken);
await updateTask;
}
else
@@ -232,7 +233,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
IDmbProvider compileJobProvider = DmbFactory.LockNextDmb(1);
bool canSeamlesslySwap = CanUseSwappableDmbProvider(compileJobProvider);
if (canSeamlesslySwap)
- if (compileJobProvider.CompileJob.EngineVersion != ActiveCompileJob.EngineVersion)
+ if (compileJobProvider.CompileJob.EngineVersion != ActiveCompileJob!.EngineVersion)
{
// have to do a graceful restart
Logger.LogDebug(
@@ -260,11 +261,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
return;
}
- SwappableDmbProvider swappableProvider = null;
+ SwappableDmbProvider? swappableProvider = null;
try
{
swappableProvider = CreateSwappableDmbProvider(compileJobProvider);
- if (ActiveCompileJob.DMApiVersion == null)
+ if (ActiveCompileJob!.DMApiVersion == null)
{
Logger.LogWarning("Active compile job has no DMAPI! Commencing immediate .dmb swap. Note this behavior is known to be buggy in some DM code contexts. See https://github.com/tgstation/tgstation-server/issues/1550");
await PerformDmbSwap(swappableProvider, cancellationToken);
@@ -348,9 +349,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// if swapping is possible, otherwise.
bool CanUseSwappableDmbProvider(IDmbProvider dmbProvider)
{
- if (dmbProvider.EngineVersion.Engine.Value != EngineType.Byond)
+ if (dmbProvider.EngineVersion.Engine != EngineType.Byond)
{
- Logger.LogDebug("Not using SwappableDmbProvider for engine type {engineType}", dmbProvider.EngineVersion.Engine.Value);
+ Logger.LogDebug("Not using SwappableDmbProvider for engine type {engineType}", dmbProvider.EngineVersion.Engine);
return false;
}
@@ -364,7 +365,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// A representing the running operation.
async ValueTask InitialLink(CancellationToken cancellationToken)
{
- await ActiveSwappable.FinishActivationPreparation(cancellationToken);
+ await ActiveSwappable!.FinishActivationPreparation(cancellationToken);
Logger.LogTrace("Linking compile job...");
await ActiveSwappable.MakeActive(cancellationToken);
}
@@ -382,10 +383,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
await newProvider.FinishActivationPreparation(cancellationToken);
var suspended = false;
- var server = Server;
+ var server = Server!;
try
{
- server.Suspend();
+ server.SuspendProcess();
suspended = true;
}
catch (Exception ex)
@@ -402,7 +403,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
// Let this throw hard if it fails
if (suspended)
- server.Resume();
+ server.ResumeProcess();
}
}
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
index 5beaab473c..10b6a7b19c 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
/// The single .
///
- protected ISessionController Server { get; private set; }
+ protected ISessionController? Server { get; private set; }
///
/// If the server is set to gracefully reboot due to a pending dmb or settings change.
@@ -109,16 +109,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
protected override async ValueTask HandleMonitorWakeup(MonitorActivationReason reason, CancellationToken cancellationToken)
{
+ var controller = Server!;
switch (reason)
{
case MonitorActivationReason.ActiveServerCrashed:
- var eventType = Server.TerminationWasRequested
+ var eventType = controller.TerminationWasRequested
? EventType.WorldEndProcess
: EventType.WatchdogCrash;
await HandleEventImpl(eventType, Enumerable.Empty(), false, cancellationToken);
- var exitWord = Server.TerminationWasRequested ? "exited" : "crashed";
- if (Server.RebootState == Session.RebootState.Shutdown)
+ var exitWord = controller.TerminationWasRequested ? "exited" : "crashed";
+ if (controller.RebootState == Session.RebootState.Shutdown)
{
// the time for graceful shutdown is now
Chat.QueueWatchdogMessage(
@@ -136,7 +137,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
exitWord));
return MonitorAction.Restart;
case MonitorActivationReason.ActiveServerRebooted:
- var rebootState = Server.RebootState;
+ var rebootState = controller.RebootState;
if (gracefulRebootRequired && rebootState == Session.RebootState.Normal)
{
Logger.LogError("Watchdog reached normal reboot state with gracefulRebootRequired set!");
@@ -144,7 +145,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
gracefulRebootRequired = false;
- Server.ResetRebootState();
+ controller.ResetRebootState();
var eventTask = HandleEventImpl(EventType.WorldReboot, Enumerable.Empty(), false, cancellationToken);
try
@@ -170,7 +171,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
case MonitorActivationReason.ActiveLaunchParametersUpdated:
- await Server.SetRebootState(Session.RebootState.Restart, cancellationToken);
+ await controller.SetRebootState(Session.RebootState.Restart, cancellationToken);
gracefulRebootRequired = true;
break;
case MonitorActivationReason.NewDmbAvailable:
@@ -202,12 +203,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
///
- protected sealed override ISessionController GetActiveController() => Server;
+ protected sealed override ISessionController? GetActiveController() => Server;
///
protected override async ValueTask InitController(
ValueTask eventTask,
- ReattachInformation reattachInfo,
+ ReattachInformation? reattachInfo,
CancellationToken cancellationToken)
{
// don't need a new dmb if reattaching
@@ -220,15 +221,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
// start the alpha server task, either by launch a new process or attaching to an existing one
// The tasks returned are mainly for writing interop files to the directories among other things and should generally never fail
// The tasks pertaining to server startup times are in the ISessionControllers
- ValueTask serverLaunchTask;
if (!reattachInProgress)
{
- Logger.LogTrace("Initializing controller with CompileJob {compileJobId}...", dmbToUse.CompileJob.Id);
+ Logger.LogTrace("Initializing controller with CompileJob {compileJobId}...", dmbToUse!.CompileJob.Id);
await BeforeApplyDmb(dmbToUse.CompileJob, cancellationToken);
dmbToUse = await PrepServerForLaunch(dmbToUse, cancellationToken);
await eventTask;
- serverLaunchTask = SessionControllerFactory.LaunchNew(
+ Server = await SessionControllerFactory.LaunchNew(
dmbToUse,
null,
ActiveLaunchParameters,
@@ -238,12 +238,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
else
{
await eventTask;
- serverLaunchTask = SessionControllerFactory.Reattach(reattachInfo, cancellationToken);
+ Server = await SessionControllerFactory.Reattach(reattachInfo!, cancellationToken);
}
- // retrieve the session controller
- Server = await serverLaunchTask;
-
// possiblity of null servers due to failed reattaches
if (Server == null)
{
@@ -291,7 +288,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// The for the operation.
/// A representing the running operation.
protected virtual ValueTask SessionStartupPersist(CancellationToken cancellationToken)
- => SessionPersistor.Save(Server.ReattachInformation, cancellationToken);
+ => SessionPersistor.Save(Server!.ReattachInformation, cancellationToken);
///
/// Handler for when the is .
@@ -309,7 +306,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
protected virtual async ValueTask HandleNewDmbAvailable(CancellationToken cancellationToken)
{
gracefulRebootRequired = true;
- if (Server.CompileJob.DMApiVersion == null)
+ if (Server!.CompileJob.DMApiVersion == null)
{
Chat.QueueWatchdogMessage(
"A new deployment has been made but cannot be applied automatically as the currently running server has no DMAPI. Please manually reboot the server to apply the update.");
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs
index 7600dc9a36..96c6c9ccf9 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs
@@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
/// Retrieves the currently running on the server.
///
- Models.CompileJob ActiveCompileJob { get; }
+ Models.CompileJob? ActiveCompileJob { get; }
///
/// The to be applied.
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// The the active server is using.
///
/// This may not be the exact same as but still be associated with the same session.
- DreamDaemonLaunchParameters LastLaunchParameters { get; }
+ DreamDaemonLaunchParameters? LastLaunchParameters { get; }
///
/// The of the active server.
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
index 4fb3e1677f..e922322995 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs
@@ -56,10 +56,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
public DreamDaemonLaunchParameters ActiveLaunchParameters { get; protected set; }
///
- public DreamDaemonLaunchParameters LastLaunchParameters { get; protected set; }
+ public DreamDaemonLaunchParameters? LastLaunchParameters { get; protected set; }
///
- public Models.CompileJob ActiveCompileJob => GetActiveController()?.CompileJob;
+ public Models.CompileJob? ActiveCompileJob => GetActiveController()?.CompileJob;
///
public abstract RebootState? RebootState { get; }
@@ -152,12 +152,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
/// The for the monitor loop.
///
- CancellationTokenSource monitorCts;
+ CancellationTokenSource? monitorCts;
///
/// The running the monitor loop.
///
- Task monitorTask;
+ Task? monitorTask;
///
/// Backing field for .
@@ -393,7 +393,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
job,
async (core, databaseContextFactory, paramJob, progressFunction, ct) =>
{
- if (core.Watchdog != this)
+ if (core?.Watchdog != this)
throw new InvalidOperationException(Instance.DifferentCoreExceptionMessage);
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, ct))
@@ -416,7 +416,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
///
- public async ValueTask HandleRestart(Version updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
+ public async ValueTask HandleRestart(Version? updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
{
if (handlerMayDelayShutdownWithExtremelyLongRunningTasks)
{
@@ -425,7 +425,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (Status != WatchdogStatus.Offline)
{
Logger.LogDebug("Waiting for server to gracefully shut down.");
- await monitorTask.WaitAsync(cancellationToken);
+ await monitorTask!.WaitAsync(cancellationToken);
}
else
Logger.LogTrace("Graceful shutdown requested but server is already offline.");
@@ -488,7 +488,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
}
///
- async ValueTask IEventConsumer.HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
+ async ValueTask IEventConsumer.HandleEvent(EventType eventType, IEnumerable parameters, bool deploymentPipeline, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(parameters);
@@ -514,7 +514,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// to use, if any.
/// The for the operation.
/// A representing the running operation.
- protected abstract ValueTask InitController(ValueTask eventTask, ReattachInformation reattachInfo, CancellationToken cancellationToken);
+ protected abstract ValueTask InitController(ValueTask eventTask, ReattachInformation? reattachInfo, CancellationToken cancellationToken);
///
/// Launches the watchdog.
@@ -529,7 +529,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
bool startMonitor,
bool announce,
bool announceFailure,
- ReattachInformation reattachInfo,
+ ReattachInformation? reattachInfo,
CancellationToken cancellationToken)
{
Logger.LogTrace("Begin LaunchImplNoLock");
@@ -610,7 +610,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (monitorTask == null)
return false;
var wasRunning = !monitorTask.IsCompleted;
- monitorCts.Cancel();
+ monitorCts!.Cancel();
await monitorTask;
Logger.LogTrace("Stopped Monitor");
monitorCts.Dispose();
@@ -638,7 +638,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (!launchResult.StartupTime.HasValue)
throw new JobException(
ErrorCode.WatchdogStartupTimeout,
- new JobException($"{serverName} timed out on startup: {ActiveLaunchParameters.StartupTimeout.Value}s"));
+ new JobException($"{serverName} timed out on startup: {ActiveLaunchParameters.StartupTimeout!.Value}s"));
}
///
@@ -683,8 +683,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
/// Get the active .
///
- /// The active .
- protected abstract ISessionController GetActiveController();
+ /// The active , if any.
+ protected abstract ISessionController? GetActiveController();
///
/// Handles the actions to take when the monitor has to "wake up".
@@ -716,9 +716,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
var eventTask = eventConsumer.HandleEvent(
EventType.DeploymentActivation,
- new List
+ new List
{
- GameIOManager.ResolvePath(newCompileJob.DirectoryName.ToString()),
+ GameIOManager.ResolvePath(newCompileJob.DirectoryName!.Value.ToString()),
},
false,
cancellationToken);
@@ -843,13 +843,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
try
{
MonitorAction nextAction = MonitorAction.Continue;
- Task activeServerLifetime = null,
+ Task? activeServerLifetime = null,
activeServerReboot = null,
activeServerStartup = null,
serverPrimed = null,
activeLaunchParametersChanged = null,
- newDmbAvailable = null;
- ISessionController lastController = null;
+ newDmbAvailable = null,
+ healthCheck = null;
+ ISessionController? lastController = null;
var ranInitialDmbCheck = false;
for (ulong iteration = 1; nextAction != MonitorAction.Exit; ++iteration)
using (LogContext.PushProperty(SerilogContextHelper.WatchdogMonitorIterationContextProperty, iteration))
@@ -865,7 +866,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
void UpdateMonitoredTasks()
{
var sameController = lastController == controller;
- void TryUpdateTask(ref Task oldTask, Func newTaskFactory)
+ void TryUpdateTask(ref Task? oldTask, Func newTaskFactory)
{
if (sameController && oldTask?.IsCompleted == true)
return;
@@ -873,7 +874,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
oldTask = newTaskFactory();
}
- controller.RebootGate = nextMonitorWakeupTcs.Task;
+ controller!.RebootGate = nextMonitorWakeupTcs.Task;
TryUpdateTask(ref activeServerLifetime, () => controller.Lifetime);
TryUpdateTask(ref activeServerReboot, () => controller.OnReboot);
@@ -896,28 +897,36 @@ namespace Tgstation.Server.Host.Components.Watchdog
});
}
- UpdateMonitoredTasks();
+ if (controller != null)
+ {
+ UpdateMonitoredTasks();
- var healthCheckSeconds = ActiveLaunchParameters.HealthCheckSeconds.Value;
- var healthCheck = healthCheckSeconds == 0
- || !controller.DMApiAvailable
- ? Extensions.TaskExtensions.InfiniteTask
- : Task.Delay(
- TimeSpan.FromSeconds(healthCheckSeconds),
- cancellationToken);
+ var healthCheckSeconds = ActiveLaunchParameters.HealthCheckSeconds!.Value;
+ healthCheck = healthCheckSeconds == 0
+ || !controller.DMApiAvailable
+ ? Extensions.TaskExtensions.InfiniteTask
+ : Task.Delay(
+ TimeSpan.FromSeconds(healthCheckSeconds),
+ cancellationToken);
- // cancel waiting if requested
- var toWaitOn = Task.WhenAny(
- activeServerLifetime,
- activeServerReboot,
- activeServerStartup,
- healthCheck,
- newDmbAvailable,
- activeLaunchParametersChanged,
- serverPrimed);
+ // cancel waiting if requested
+ var toWaitOn = Task.WhenAny(
+ activeServerLifetime!,
+ activeServerReboot!,
+ activeServerStartup!,
+ healthCheck,
+ newDmbAvailable!,
+ activeLaunchParametersChanged!,
+ serverPrimed!);
- // wait for something to happen
- await toWaitOn.WaitAsync(cancellationToken);
+ // wait for something to happen
+ await toWaitOn.WaitAsync(cancellationToken);
+ }
+ else
+ {
+ Logger.LogError("Controller was null on monitor wakeup! Attempting restart...");
+ nextAction = MonitorAction.Restart; // excuse me wtf?
+ }
cancellationToken.ThrowIfCancellationRequested();
Logger.LogTrace("Monitor activated");
@@ -926,7 +935,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
using (await SemaphoreSlimContext.Lock(synchronizationSemaphore, cancellationToken))
{
// Set this sooner so chat sends don't hold us up
- if (activeServerLifetime.IsCompleted)
+ if (activeServerLifetime!.IsCompleted)
Status = WatchdogStatus.Restoring;
// multiple things may have happened, handle them one at a time
@@ -934,7 +943,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
MonitorActivationReason activationReason = default; // this will always be assigned before being used
- bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason)
+ bool CheckActivationReason(ref Task? task, MonitorActivationReason testActivationReason)
{
var taskCompleted = task?.IsCompleted == true;
task = null;
@@ -1025,7 +1034,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
Logger.LogTrace("Detaching server...");
var controller = GetActiveController();
- await controller.Release();
+ if (controller != null)
+ await controller.Release();
+ else
+ Logger.LogError("Controller was null on monitor shutdown!");
}
}
@@ -1089,6 +1101,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
Logger.LogTrace("Sending health check to active server...");
var activeServer = GetActiveController();
+ if (activeServer == null)
+ return MonitorAction.Restart; // uhhhh???
+
var response = await activeServer.SendCommand(new TopicParameters(), cancellationToken);
var shouldShutdown = activeServer.RebootState == Session.RebootState.Shutdown;
@@ -1128,7 +1143,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
actionTaken,
StringComparison.Ordinal));
- if (ActiveLaunchParameters.DumpOnHealthCheckRestart.Value)
+ if (ActiveLaunchParameters.DumpOnHealthCheckRestart!.Value)
{
Logger.LogDebug("DumpOnHealthCheckRestart enabled.");
try
@@ -1164,13 +1179,30 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// Handle any in a given topic .
///
/// The .
- void HandleChatResponses(TopicResponse result)
+ void HandleChatResponses(TopicResponse? result)
{
if (result?.ChatResponses != null)
- foreach (var response in result.ChatResponses)
+ {
+ var warnedMissingChannelIds = false;
+ foreach (var response in result.ChatResponses
+ .Where(response =>
+ {
+ if (response.ChannelIds == null)
+ {
+ if (!warnedMissingChannelIds)
+ {
+ Logger.LogWarning("DMAPI response contains null channelIds!");
+ warnedMissingChannelIds = true;
+ }
+
+ return false;
+ }
+
+ return true;
+ }))
Chat.QueueMessage(
response,
- response.ChannelIds
+ response.ChannelIds!
.Select(channelIdString =>
{
if (UInt64.TryParse(channelIdString, out var channelId))
@@ -1181,7 +1213,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
return null;
})
.Where(nullableChannelId => nullableChannelId.HasValue)
- .Select(nullableChannelId => nullableChannelId.Value));
+ .Select(nullableChannelId => nullableChannelId!.Value));
+ }
}
///
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs
index cd8f0b4525..e75ba74511 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs
@@ -81,9 +81,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
///
protected override async ValueTask ApplyInitialDmb(CancellationToken cancellationToken)
{
- if (Server.EngineVersion.Engine.Value != EngineType.Byond)
+ if (Server!.EngineVersion.Engine != EngineType.Byond)
{
- Logger.LogTrace("Not setting InitialDmb for engine type {engineType}", Server.EngineVersion.Engine.Value);
+ Logger.LogTrace("Not setting InitialDmb for engine type {engineType}", Server.EngineVersion.Engine);
return;
}
diff --git a/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs b/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs
index 19dfa9cace..12da65e312 100644
--- a/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/ControlPanelConfiguration.cs
@@ -36,16 +36,16 @@ namespace Tgstation.Server.Host.Configuration
///
/// The channel to retrieve the webpanel from. "local" uses the bundled version.
///
- public string Channel { get; set; }
+ public string? Channel { get; set; }
///
/// The public path to the TGS control panel from a wider network.
///
- public string PublicPath { get; set; }
+ public string? PublicPath { get; set; }
///
/// Origins allowed for CORS requests.
///
- public ICollection AllowedOrigins { get; set; }
+ public ICollection? AllowedOrigins { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs
index d5adce7577..ea699cd4ad 100644
--- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Configuration
///
/// The connection string for the database.
///
- public string ConnectionString { get; set; }
+ public string? ConnectionString { get; set; }
///
/// If the database should be deleted on application startup. Should not be used in production!.
@@ -37,6 +37,6 @@ namespace Tgstation.Server.Host.Configuration
///
/// The form of the of the target server.
///
- public string ServerVersion { get; set; }
+ public string? ServerVersion { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Configuration/ElasticsearchConfiguration.cs b/src/Tgstation.Server.Host/Configuration/ElasticsearchConfiguration.cs
index 8c45f2e9f5..a99f3b03e8 100644
--- a/src/Tgstation.Server.Host/Configuration/ElasticsearchConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/ElasticsearchConfiguration.cs
@@ -1,7 +1,7 @@
using System;
namespace Tgstation.Server.Host.Configuration
- {
+{
///
/// Configuration options pertaining to elasticsearch log storage.
///
@@ -20,16 +20,16 @@ namespace Tgstation.Server.Host.Configuration
///
/// The host of the elasticsearch endpoint.
///
- public Uri Host { get; set; }
+ public Uri? Host { get; set; }
///
/// Username for elasticsearch.
///
- public string Username { get; set; }
+ public string? Username { get; set; }
///
/// Password for elasticsearch.
///
- public string Password { get; set; }
+ public string? Password { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs b/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs
index c317fc1b97..534daf8ebb 100644
--- a/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/FileLoggingConfiguration.cs
@@ -22,7 +22,7 @@ namespace Tgstation.Server.Host.Configuration
///
/// Where log files are stored.
///
- public string Directory { get; set; }
+ public string? Directory { get; set; }
///
/// If file logging is disabled.
diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs
index 8e2fe59579..ae084860df 100644
--- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs
@@ -81,7 +81,7 @@ namespace Tgstation.Server.Host.Configuration
///
/// The the file says it is.
///
- public Version ConfigVersion { get; set; }
+ public Version? ConfigVersion { get; set; }
///
/// The port the TGS API listens on.
@@ -91,7 +91,7 @@ namespace Tgstation.Server.Host.Configuration
///
/// A GitHub personal access token to use for bypassing rate limits on requests. Requires no scopes.
///
- public string GitHubAccessToken { get; set; }
+ public string? GitHubAccessToken { get; set; }
///
/// The .
diff --git a/src/Tgstation.Server.Host/Configuration/InternalConfiguration.cs b/src/Tgstation.Server.Host/Configuration/InternalConfiguration.cs
index 6bf30bafdc..58f2ad4720 100644
--- a/src/Tgstation.Server.Host/Configuration/InternalConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/InternalConfiguration.cs
@@ -13,12 +13,12 @@
///
/// The name of the pipe opened by the host watchdog for sending commands, if any.
///
- public string CommandPipe { get; set; }
+ public string? CommandPipe { get; set; }
///
/// The name of the pipe opened by the host watchdog for receiving commands, if any.
///
- public string ReadyPipe { get; set; }
+ public string? ReadyPipe { get; set; }
///
/// If the server is running under SystemD.
@@ -28,7 +28,7 @@
///
/// The base path for the app settings configuration files.
///
- public string AppSettingsBasePath { get; set; }
+ public string AppSettingsBasePath { get; set; } = "UNINITIALIZED"; // this is set in a hacky way in ServerFactory
///
/// Coerce the to select .
@@ -38,6 +38,6 @@
///
/// Generate default configuration using the given default password.
///
- public string MariaDBDefaultRootPassword { get; set; }
+ public string? MariaDBDefaultRootPassword { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs b/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs
index 1818a15d8b..2d4c781102 100644
--- a/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/OAuthConfiguration.cs
@@ -10,16 +10,16 @@ namespace Tgstation.Server.Host.Configuration
///
/// The client redirect URL. Not used by all providers.
///
- public Uri ServerUrl { get; set; }
+ public Uri? ServerUrl { get; set; }
///
/// The authentication server URL. Not used by all providers.
///
- public Uri RedirectUrl { get; set; }
+ public Uri? RedirectUrl { get; set; }
///
/// User information URL override. Not supported by the provider.
///
- public Uri UserInformationUrlOverride { get; set; }
+ public Uri? UserInformationUrlOverride { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs b/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs
index d2d7668072..a170cd235f 100644
--- a/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs
+++ b/src/Tgstation.Server.Host/Configuration/OAuthConfigurationBase.cs
@@ -10,12 +10,12 @@ namespace Tgstation.Server.Host.Configuration
///
/// The client ID.
///
- public string ClientId { get; set; }
+ public string? ClientId { get; set; }
///
/// The client secret.
///
- public string ClientSecret { get; set; }
+ public string? ClientSecret { get; set; }
///
/// Initializes a new instance of the class.
diff --git a/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs b/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs
index a6073538cd..35585612c0 100644
--- a/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs
@@ -60,12 +60,12 @@ namespace Tgstation.Server.Host.Configuration
///
/// A custom token signing key. Overrides .
///
- public string CustomTokenSigningKeyBase64 { get; set; }
+ public string? CustomTokenSigningKeyBase64 { get; set; }
///
/// OAuth provider settings.
///
- public IDictionary OAuth
+ public IDictionary? OAuth
{
get => oAuth;
set
@@ -73,10 +73,10 @@ namespace Tgstation.Server.Host.Configuration
// Workaround for https://github.com/dotnet/runtime/issues/89547
var publicProperties = typeof(OAuthConfiguration)
.GetProperties()
- .Where(property => property.CanWrite && property.SetMethod.IsPublic)
+ .Where(property => property.CanWrite && property.SetMethod!.IsPublic)
.ToList();
oAuth = value
- .Where(
+ ?.Where(
kvp => !publicProperties.All(
prop => prop.GetValue(kvp.Value) == prop.PropertyType.GetDefaultValue()))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
@@ -86,6 +86,6 @@ namespace Tgstation.Server.Host.Configuration
///
/// Backing field for .
///
- IDictionary oAuth;
+ IDictionary? oAuth;
}
}
diff --git a/src/Tgstation.Server.Host/Configuration/SwarmConfiguration.cs b/src/Tgstation.Server.Host/Configuration/SwarmConfiguration.cs
index 38c709ec39..12c9cf25a0 100644
--- a/src/Tgstation.Server.Host/Configuration/SwarmConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/SwarmConfiguration.cs
@@ -1,6 +1,7 @@
using System;
using Tgstation.Server.Api.Models.Internal;
+
using YamlDotNet.Serialization;
namespace Tgstation.Server.Host.Configuration
@@ -17,7 +18,7 @@ namespace Tgstation.Server.Host.Configuration
///
[YamlMember(SerializeAs = typeof(string))]
- public override Uri Address
+ public override Uri? Address
{
get => base.Address;
set => base.Address = value;
@@ -25,7 +26,7 @@ namespace Tgstation.Server.Host.Configuration
///
[YamlMember(SerializeAs = typeof(string))]
- public override Uri PublicAddress
+ public override Uri? PublicAddress
{
get => base.PublicAddress;
set => base.PublicAddress = value;
@@ -35,12 +36,12 @@ namespace Tgstation.Server.Host.Configuration
/// The of the swarm controller. If , the current server is considered the controller.
///
[YamlMember(SerializeAs = typeof(string))]
- public Uri ControllerAddress { get; set; }
+ public Uri? ControllerAddress { get; set; }
///
/// The private key used for swarm communication.
///
- public string PrivateKey { get; set; }
+ public string? PrivateKey { get; set; }
///
/// The number of nodes in addition to the controller required to be connected a server swarm before performing an update.
diff --git a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs
index 1e340c84ca..af475412ee 100644
--- a/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/UpdatesConfiguration.cs
@@ -35,11 +35,11 @@ namespace Tgstation.Server.Host.Configuration
///
/// Prefix before the of TGS published in git tags.
///
- public string GitTagPrefix { get; set; } = DefaultGitTagPrefix;
+ public string? GitTagPrefix { get; set; } = DefaultGitTagPrefix;
///
/// Asset package containing the new assembly in zip form.
///
- public string UpdatePackageAssetName { get; set; } = DefaultUpdatePackageAssetName;
+ public string? UpdatePackageAssetName { get; set; } = DefaultUpdatePackageAssetName;
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
index c1da69a69d..2ccc4f4c8b 100644
--- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
@@ -144,8 +144,8 @@ namespace Tgstation.Server.Host.Controllers
{
try
{
- Version greatestVersion = null;
- Uri repoUrl = null;
+ Version? greatestVersion = null;
+ Uri? repoUrl = null;
try
{
var gitHubService = gitHubServiceFactory.CreateService();
@@ -213,10 +213,10 @@ namespace Tgstation.Server.Host.Controllers
var attemptingUpload = model.UploadZip == true;
if (attemptingUpload)
{
- if (!AuthenticationContext.PermissionSet.AdministrationRights.Value.HasFlag(AdministrationRights.UploadVersion))
+ if (!AuthenticationContext.PermissionSet.AdministrationRights!.Value.HasFlag(AdministrationRights.UploadVersion))
return Forbid();
}
- else if (!AuthenticationContext.PermissionSet.AdministrationRights.Value.HasFlag(AdministrationRights.ChangeVersion))
+ else if (!AuthenticationContext.PermissionSet.AdministrationRights!.Value.HasFlag(AdministrationRights.ChangeVersion))
return Forbid();
if (model.NewVersion == null)
@@ -377,7 +377,7 @@ namespace Tgstation.Server.Host.Controllers
/// A resulting in the of the request.
async ValueTask AttemptInitiateUpdate(Version newVersion, bool attemptingUpload, CancellationToken cancellationToken)
{
- IFileUploadTicket uploadTicket = attemptingUpload
+ IFileUploadTicket? uploadTicket = attemptingUpload
? fileTransferService.CreateUpload(FileUploadStreamKind.None)
: null;
@@ -391,7 +391,7 @@ namespace Tgstation.Server.Host.Controllers
catch
{
if (attemptingUpload)
- await uploadTicket.DisposeAsync();
+ await uploadTicket!.DisposeAsync();
throw;
}
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index ffa4e84960..bad2e6c10f 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// The for the operation.
///
- protected ApiHeaders ApiHeaders => ApiHeadersProvider.ApiHeaders;
+ protected ApiHeaders? ApiHeaders => ApiHeadersProvider.ApiHeaders;
///
/// The containing value of .
@@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// The for the operation.
///
- protected Models.Instance Instance { get; }
+ protected Models.Instance? Instance { get; }
///
/// If are required.
@@ -100,13 +100,13 @@ namespace Tgstation.Server.Host.Controllers
ApiHeadersProvider = apiHeadersProvider ?? throw new ArgumentNullException(nameof(apiHeadersProvider));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
- Instance = AuthenticationContext?.InstancePermissionSet?.Instance;
+ Instance = AuthenticationContext.InstancePermissionSet?.Instance;
this.requireHeaders = requireHeaders;
}
///
#pragma warning disable CA1506 // TODO: Decomplexify
- protected override async ValueTask HookExecuteAction(Func executeAction, CancellationToken cancellationToken)
+ protected override async ValueTask HookExecuteAction(Func executeAction, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(executeAction);
@@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Controllers
if (ApiHeaders == null)
{
if (requireHeaders)
- return HeadersIssue(ApiHeadersProvider.HeadersException);
+ return HeadersIssue(ApiHeadersProvider.HeadersException!);
}
var errorCase = await ValidateRequest(cancellationToken);
@@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Controllers
if (ModelState?.IsValid == false)
{
var errorMessages = ModelState
- .SelectMany(x => x.Value.Errors)
+ .SelectMany(x => x.Value!.Errors)
.Select(x => x.ErrorMessage)
// We use RequiredAttributes purely for preventing properties from becoming nullable in the databases
@@ -238,8 +238,8 @@ namespace Tgstation.Server.Host.Controllers
///
/// The for the operation.
/// A resulting in an appropriate on validation failure, otherwise.
- protected virtual ValueTask ValidateRequest(CancellationToken cancellationToken)
- => ValueTask.FromResult(null);
+ protected virtual ValueTask ValidateRequest(CancellationToken cancellationToken)
+ => ValueTask.FromResult(null);
///
/// Response for missing/Invalid headers.
@@ -267,14 +267,14 @@ namespace Tgstation.Server.Host.Controllers
///
/// The of model being generated and returned.
/// A resulting in a resulting in the generated .
- /// A to transform the s after being queried.
+ /// Optional to transform the s after being queried.
/// The requested page from the query.
/// The requested page size from the query.
/// The for the operation.
/// A resulting in the for the operation.
protected ValueTask Paginated(
Func>> queryGenerator,
- Func resultTransformer,
+ Func? resultTransformer,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken) => PaginatedImpl(
@@ -297,7 +297,7 @@ namespace Tgstation.Server.Host.Controllers
/// A resulting in the for the operation.
protected ValueTask Paginated(
Func>> queryGenerator,
- Func resultTransformer,
+ Func? resultTransformer,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken)
@@ -322,7 +322,7 @@ namespace Tgstation.Server.Host.Controllers
/// A resulting in the for the operation.
async ValueTask PaginatedImpl(
Func>> queryGenerator,
- Func resultTransformer,
+ Func? resultTransformer,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken)
@@ -342,7 +342,7 @@ namespace Tgstation.Server.Host.Controllers
var page = pageQuery ?? 1;
var paginationResult = await queryGenerator();
- if (paginationResult.EarlyOut != null)
+ if (!paginationResult.Valid)
return paginationResult.EarlyOut;
var queriedResults = paginationResult
diff --git a/src/Tgstation.Server.Host/Controllers/ApiControllerBase.cs b/src/Tgstation.Server.Host/Controllers/ApiControllerBase.cs
index 9a8b119808..748dd81096 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiControllerBase.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiControllerBase.cs
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Controllers
/// A that should be invoked and its response awaited to continue normal execution of the request. Should NOT be called if this method returns a non- value.
/// The for the operation.
/// A resulting in an that, if not , is executed.
- protected virtual async ValueTask HookExecuteAction(Func executeAction, CancellationToken cancellationToken)
+ protected virtual async ValueTask HookExecuteAction(Func executeAction, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(executeAction);
diff --git a/src/Tgstation.Server.Host/Controllers/ApiRootController.cs b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs
index fbfc1d4465..fa6b3e0c4b 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiRootController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs
@@ -85,11 +85,6 @@ namespace Tgstation.Server.Host.Controllers
///
readonly GeneralConfiguration generalConfiguration;
- ///
- /// The for the .
- ///
- readonly ControlPanelConfiguration controlPanelConfiguration;
-
///
/// Initializes a new instance of the class.
///
@@ -105,7 +100,6 @@ namespace Tgstation.Server.Host.Controllers
/// The value of .
/// The value of .
/// The containing the value of .
- /// The containing the value of .
/// The for the .
/// The for the .
public ApiRootController(
@@ -121,7 +115,6 @@ namespace Tgstation.Server.Host.Controllers
ISwarmService swarmService,
IServerControl serverControl,
IOptions generalConfigurationOptions,
- IOptions controlPanelConfigurationOptions,
ILogger logger,
IApiHeadersProvider apiHeadersProvider)
: base(
@@ -141,7 +134,6 @@ namespace Tgstation.Server.Host.Controllers
this.swarmService = swarmService ?? throw new ArgumentNullException(nameof(swarmService));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
- controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
}
///
@@ -170,7 +162,7 @@ namespace Tgstation.Server.Host.Controllers
return HeadersIssue(ex);
}
- failIfUnauthed = Request.Headers.Authorization.Any();
+ failIfUnauthed = Request.Headers.Authorization.Count > 0;
}
else
failIfUnauthed = ApiHeaders.Token != null;
@@ -213,7 +205,7 @@ namespace Tgstation.Server.Host.Controllers
if (ApiHeaders == null)
{
Response.Headers.Add(HeaderNames.WWWAuthenticate, new StringValues($"basic realm=\"Create TGS {ApiHeaders.BearerAuthenticationScheme} token\""));
- return HeadersIssue(ApiHeadersProvider.HeadersException);
+ return HeadersIssue(ApiHeadersProvider.HeadersException!);
}
if (ApiHeaders.IsTokenAuthentication)
@@ -221,12 +213,12 @@ namespace Tgstation.Server.Host.Controllers
var oAuthLogin = ApiHeaders.OAuthProvider.HasValue;
- ISystemIdentity systemIdentity = null;
+ ISystemIdentity? systemIdentity = null;
if (!oAuthLogin)
try
{
// trust the system over the database because a user's name can change while still having the same SID
- systemIdentity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken);
+ systemIdentity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username!, ApiHeaders.Password!, cancellationToken);
}
catch (NotImplementedException)
{
@@ -239,8 +231,8 @@ namespace Tgstation.Server.Host.Controllers
IQueryable query = DatabaseContext.Users.AsQueryable();
if (oAuthLogin)
{
- var oAuthProvider = ApiHeaders.OAuthProvider.Value;
- string externalUserId;
+ var oAuthProvider = ApiHeaders.OAuthProvider!.Value;
+ string? externalUserId;
try
{
var validator = oAuthProviders
@@ -263,13 +255,13 @@ namespace Tgstation.Server.Host.Controllers
return Unauthorized();
query = query.Where(
- x => x.OAuthConnections.Any(
+ x => x.OAuthConnections!.Any(
y => y.Provider == oAuthProvider
&& y.ExternalUserId == externalUserId));
}
else
{
- var canonicalUserName = Models.User.CanonicalizeName(ApiHeaders.Username);
+ var canonicalUserName = Models.User.CanonicalizeName(ApiHeaders.Username!);
if (canonicalUserName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
return Unauthorized();
@@ -311,7 +303,7 @@ namespace Tgstation.Server.Host.Controllers
if (!usingSystemIdentity)
{
// DB User password check and update
- if (!isLikelyDbUser || !cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
+ if (!isLikelyDbUser || !cryptographySuite.CheckUserPassword(user, ApiHeaders.Password!))
return Unauthorized();
if (user.PasswordHash != originalHash)
{
@@ -327,7 +319,7 @@ namespace Tgstation.Server.Host.Controllers
}
else
{
- var usernameMismatch = systemIdentity.Username != user.Name;
+ var usernameMismatch = systemIdentity!.Username != user.Name;
if (isLikelyDbUser || usernameMismatch)
{
DatabaseContext.Users.Attach(user);
@@ -352,7 +344,7 @@ namespace Tgstation.Server.Host.Controllers
}
// Now that the bookeeping is done, tell them to fuck off if necessary
- if (!user.Enabled.Value)
+ if (!user.Enabled!.Value)
{
Logger.LogTrace("Not logging in disabled user {userId}.", user.Id);
return Forbid();
@@ -365,7 +357,7 @@ namespace Tgstation.Server.Host.Controllers
var identExpiry = token.ParseJwt().ValidTo;
identExpiry += tokenFactory.ValidationParameters.ClockSkew;
identExpiry += TimeSpan.FromSeconds(15);
- identityCache.CacheSystemIdentity(user, systemIdentity, identExpiry);
+ identityCache.CacheSystemIdentity(user, systemIdentity!, identExpiry);
}
Logger.LogDebug("Successfully logged in user {userId}!", user.Id);
diff --git a/src/Tgstation.Server.Host/Controllers/BridgeController.cs b/src/Tgstation.Server.Host/Controllers/BridgeController.cs
index f23d91aab9..4b1a1fac3e 100644
--- a/src/Tgstation.Server.Host/Controllers/BridgeController.cs
+++ b/src/Tgstation.Server.Host/Controllers/BridgeController.cs
@@ -89,7 +89,7 @@ namespace Tgstation.Server.Host.Controllers
{
// Nothing to see here
var remoteIP = Request.HttpContext.Connection.RemoteIpAddress;
- if (!IPAddress.IsLoopback(remoteIP))
+ if (remoteIP == null || !IPAddress.IsLoopback(remoteIP))
{
logger.LogTrace("Rejecting remote bridge request from {remoteIP}", remoteIP);
return Forbid();
@@ -97,10 +97,10 @@ namespace Tgstation.Server.Host.Controllers
using (LogContext.PushProperty(SerilogContextHelper.BridgeRequestIterationContextProperty, Interlocked.Increment(ref requestsProcessed)))
{
- var request = new BridgeParameters();
+ BridgeParameters? request;
try
{
- JsonConvert.PopulateObject(data, request, DMApiConstants.SerializerSettings);
+ request = JsonConvert.DeserializeObject(data, DMApiConstants.SerializerSettings);
}
catch (Exception ex)
{
@@ -109,6 +109,13 @@ namespace Tgstation.Server.Host.Controllers
return BadRequest();
}
+ if (request == null)
+ {
+ if (LogContent)
+ logger.LogWarning("Error deserializing bridge request: {badJson}", data);
+ return BadRequest();
+ }
+
if (LogContent)
logger.LogTrace("Bridge Request: {json}", data);
diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs
index 967971eb24..9ea7f4f5a7 100644
--- a/src/Tgstation.Server.Host/Controllers/ChatController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs
@@ -25,6 +25,7 @@ using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Utils;
+
using Z.EntityFramework.Plus;
namespace Tgstation.Server.Host.Controllers
@@ -118,20 +119,20 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.InstanceId == Instance.Id)
.CountAsync(cancellationToken);
- if (countOfExistingBotsInInstance >= Instance.ChatBotLimit.Value)
+ if (countOfExistingBotsInInstance >= Instance.ChatBotLimit!.Value)
return Conflict(new ErrorMessageResponse(ErrorCode.ChatBotMax));
model.Enabled ??= false;
model.ReconnectionInterval ??= 1;
// try to update das db first
- var dbModel = new ChatBot
+ var newChannels = model.Channels?.Select(x => ConvertApiChatChannel(x, model.Provider!.Value)).ToList() ?? new List(); // important that this isn't null
+ var dbModel = new ChatBot(newChannels)
{
Name = model.Name,
ConnectionString = model.ConnectionString,
Enabled = model.Enabled,
- Channels = model.Channels?.Select(x => ConvertApiChatChannel(x, model.Provider.Value)).ToList() ?? new List(), // important that this isn't null
- InstanceId = Instance.Id.Value,
+ InstanceId = Instance.Id!.Value,
Provider = model.Provider,
ReconnectionInterval = model.ReconnectionInterval,
ChannelLimit = model.ChannelLimit,
@@ -140,7 +141,7 @@ namespace Tgstation.Server.Host.Controllers
DatabaseContext.ChatBots.Add(dbModel);
await DatabaseContext.Save(cancellationToken);
- return await WithComponentInstance(
+ return await WithComponentInstanceNullable(
async instance =>
{
try
@@ -149,7 +150,7 @@ namespace Tgstation.Server.Host.Controllers
await instance.Chat.ChangeSettings(dbModel, cancellationToken);
if (dbModel.Channels.Count > 0)
- await instance.Chat.ChangeChannels(dbModel.Id.Value, dbModel.Channels, cancellationToken);
+ await instance.Chat.ChangeChannels(dbModel.Id!.Value, dbModel.Channels, cancellationToken);
}
catch
{
@@ -158,7 +159,7 @@ namespace Tgstation.Server.Host.Controllers
// DCTx2: Operations must always run
await DatabaseContext.Save(default);
- await instance.Chat.DeleteConnection(dbModel.Id.Value, default);
+ await instance.Chat.DeleteConnection(dbModel.Id!.Value, default);
throw;
}
@@ -179,7 +180,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(ChatBotRights.Delete)]
[ProducesResponseType(204)]
public async ValueTask Delete(long id, CancellationToken cancellationToken)
- => await WithComponentInstance(
+ => await WithComponentInstanceNullable(
async instance =>
{
await Task.WhenAll(
@@ -280,7 +281,7 @@ namespace Tgstation.Server.Host.Controllers
{
ArgumentNullException.ThrowIfNull(model);
- var earlyOut = StandardModelChecks(model, false);
+ IActionResult? earlyOut = StandardModelChecks(model, false);
if (earlyOut != null)
return earlyOut;
@@ -295,7 +296,7 @@ namespace Tgstation.Server.Host.Controllers
if (current == default)
return this.Gone();
- if ((model.Channels?.Count ?? current.Channels.Count) > (model.ChannelLimit ?? current.ChannelLimit.Value))
+ if ((model.Channels?.Count ?? current.Channels!.Count) > (model.ChannelLimit ?? current.ChannelLimit!.Value))
{
// 400 or 409 depends on if the client sent both
var errorMessage = new ErrorMessageResponse(ErrorCode.ChatBotMaxChannels);
@@ -338,28 +339,28 @@ namespace Tgstation.Server.Host.Controllers
var hasChannels = model.Channels != null;
if (hasChannels || (model.Provider.HasValue && model.Provider != oldProvider))
{
- DatabaseContext.ChatChannels.RemoveRange(current.Channels);
+ DatabaseContext.ChatChannels.RemoveRange(current.Channels!);
if (hasChannels)
{
- var dbChannels = model.Channels.Select(x => ConvertApiChatChannel(x, model.Provider ?? current.Provider.Value)).ToList();
+ var dbChannels = model.Channels!.Select(x => ConvertApiChatChannel(x, model.Provider ?? current.Provider!.Value)).ToList();
DatabaseContext.ChatChannels.AddRange(dbChannels);
current.Channels = dbChannels;
}
else
- current.Channels.Clear();
+ current.Channels!.Clear();
}
await DatabaseContext.Save(cancellationToken);
- earlyOut = await WithComponentInstance(
+ earlyOut = await WithComponentInstanceNullable(
async instance =>
{
var chat = instance.Chat;
if (anySettingsModified)
await chat.ChangeSettings(current, cancellationToken); // have to rebuild the thing first
- if ((model.Channels != null || anySettingsModified) && current.Enabled.Value)
- await chat.ChangeChannels(current.Id.Value, current.Channels, cancellationToken);
+ if ((model.Channels != null || anySettingsModified) && current.Enabled!.Value)
+ await chat.ChangeChannels(current.Id!.Value, current.Channels, cancellationToken);
return null;
});
@@ -381,8 +382,8 @@ namespace Tgstation.Server.Host.Controllers
///
/// The to validate.
/// If the is being created.
- /// An to respond with or .
- IActionResult StandardModelChecks(ChatBotApiBase model, bool forCreation)
+ /// An to respond with or .
+ BadRequestObjectResult? StandardModelChecks(ChatBotApiBase model, bool forCreation)
{
if (model.ReconnectionInterval == 0)
throw new InvalidOperationException("RecconnectionInterval cannot be zero!");
diff --git a/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs b/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs
index eef1af8b18..f04e620d06 100644
--- a/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ComponentInterfacingController.cs
@@ -10,6 +10,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Database;
+using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Utils;
@@ -63,21 +64,22 @@ namespace Tgstation.Server.Host.Controllers
}
///
- protected override async ValueTask ValidateRequest(CancellationToken cancellationToken)
+ protected override async ValueTask ValidateRequest(CancellationToken cancellationToken)
{
if (!useInstanceRequestHeader)
return null;
- if (!ApiHeaders.InstanceId.HasValue)
+ if (!ApiHeaders!.InstanceId.HasValue)
return BadRequest(new ErrorMessageResponse(ErrorCode.InstanceHeaderRequired));
if (AuthenticationContext.InstancePermissionSet == null)
return Forbid();
- if (ValidateInstanceOnlineStatus(Instance))
+ var instance = Instance!;
+ if (ValidateInstanceOnlineStatus(instance))
await DatabaseContext.Save(cancellationToken);
- using var instanceReferenceCheck = instanceManager.GetInstanceReference(Instance);
+ using var instanceReferenceCheck = instanceManager.GetInstanceReference(instance);
if (instanceReferenceCheck == null)
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceOffline));
@@ -97,7 +99,7 @@ namespace Tgstation.Server.Host.Controllers
using (var instanceReferenceCheck = instanceManager.GetInstanceReference(metadata))
online = instanceReferenceCheck != null;
- if (metadata.Online.Value == online)
+ if (metadata.Require(x => x.Online) == online)
return false;
const string OfflineWord = "offline";
@@ -120,19 +122,29 @@ namespace Tgstation.Server.Host.Controllers
/// The to grab. If , will be used.
/// A resulting in the that should be returned.
/// The context of should be as small as possible so as to avoid race conditions. This function can return a if the requested instance was offline.
- protected async ValueTask WithComponentInstance(Func> action, Models.Instance instance = null)
+ protected async ValueTask WithComponentInstanceNullable(Func> action, Models.Instance? instance = null)
{
ArgumentNullException.ThrowIfNull(action);
- instance ??= Instance;
+ instance ??= Instance ?? throw new InvalidOperationException("ComponentInterfacingController has no Instance!");
using var instanceReference = instanceManager.GetInstanceReference(instance);
- using (LogContext.PushProperty(SerilogContextHelper.InstanceReferenceContextProperty, instanceReference.Uid))
+ using (LogContext.PushProperty(SerilogContextHelper.InstanceReferenceContextProperty, instanceReference?.Uid))
{
if (instanceReference == null)
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceOffline));
return await action(instanceReference);
}
}
+
+ ///
+ /// Run a given with the relevant .
+ ///
+ /// A accepting the and returning a with the .
+ /// The to grab. If , will be used.
+ /// A resulting in the that should be returned.
+ /// The context of should be as small as possible so as to avoid race conditions. This function can return a if the requested instance was offline.
+ protected async ValueTask WithComponentInstance(Func> action, Models.Instance? instance = null)
+ => (await WithComponentInstanceNullable(async core => await action(core), instance))!;
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
index 37b7e89ed1..8232be095c 100644
--- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
@@ -73,7 +73,7 @@ namespace Tgstation.Server.Host.Controllers
public async ValueTask Update([FromBody] ConfigurationFileRequest model, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(model);
- if (ForbidDueToModeConflicts(model.Path, out var systemIdentity))
+ if (ForbidDueToModeConflicts(model.Path!, out var systemIdentity))
return Forbid();
try
@@ -84,7 +84,7 @@ namespace Tgstation.Server.Host.Controllers
var newFile = await instance
.Configuration
.Write(
- model.Path,
+ model.Path!,
systemIdentity,
model.LastReadHash,
cancellationToken);
@@ -164,7 +164,7 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(typeof(ErrorMessageResponse), 409)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
public ValueTask Directory(
- string directoryPath,
+ string? directoryPath,
[FromQuery] int? page,
[FromQuery] int? pageSize,
CancellationToken cancellationToken)
@@ -240,6 +240,9 @@ namespace Tgstation.Server.Host.Controllers
{
ArgumentNullException.ThrowIfNull(model);
+ if (model.Path == null)
+ return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
+
if (ForbidDueToModeConflicts(model.Path, out var systemIdentity))
return Forbid();
@@ -338,7 +341,7 @@ namespace Tgstation.Server.Host.Controllers
/// The path to validate if any.
/// The to use when calling into .
/// if a should be returned, otherwise.
- bool ForbidDueToModeConflicts(string path, out ISystemIdentity systemIdentityToUse)
+ bool ForbidDueToModeConflicts(string? path, out ISystemIdentity? systemIdentityToUse)
{
if (Instance.ConfigurationType == ConfigurationType.Disallowed
|| (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null)
diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
index ca84dac467..069b272959 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
@@ -90,7 +90,7 @@ namespace Tgstation.Server.Host.Controllers
var job = Job.Create(JobCode.WatchdogLaunch, AuthenticationContext.User, Instance, DreamDaemonRights.Shutdown);
await jobManager.RegisterOperation(
job,
- (core, databaseContextFactory, paramJob, progressHandler, innerCt) => core.Watchdog.Launch(innerCt),
+ (core, databaseContextFactory, paramJob, progressHandler, innerCt) => core!.Watchdog.Launch(innerCt),
cancellationToken);
return Accepted(job.ToApi());
});
@@ -172,7 +172,7 @@ namespace Tgstation.Server.Host.Controllers
if (current == default)
return this.Gone();
- if (model.Port.HasValue && model.Port.Value != current.Port.Value)
+ if (model.Port.HasValue && model.Port.Value != current.Port!.Value)
{
var verifiedPort = await portAllocator
.GetAvailablePort(
@@ -201,14 +201,15 @@ namespace Tgstation.Server.Host.Controllers
return false;
}
+ var ddRights = InstancePermissionSet.DreamDaemonRights!.Value;
if (CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient)
|| CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart)
|| CheckModified(x => x.Port, DreamDaemonRights.SetPort)
|| CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity)
|| CheckModified(x => x.Visibility, DreamDaemonRights.SetVisibility)
- || (model.SoftRestart.HasValue && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftRestart))
- || (model.SoftShutdown.HasValue && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.SoftShutdown))
- || (!String.IsNullOrWhiteSpace(model.BroadcastMessage) && !AuthenticationContext.InstancePermissionSet.DreamDaemonRights.Value.HasFlag(DreamDaemonRights.BroadcastMessage))
+ || (model.SoftRestart.HasValue && !ddRights.HasFlag(DreamDaemonRights.SoftRestart))
+ || (model.SoftShutdown.HasValue && !ddRights.HasFlag(DreamDaemonRights.SoftShutdown))
+ || (!String.IsNullOrWhiteSpace(model.BroadcastMessage) && !ddRights.HasFlag(DreamDaemonRights.BroadcastMessage))
|| CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)
|| CheckModified(x => x.HealthCheckSeconds, DreamDaemonRights.SetHealthCheckInterval)
|| CheckModified(x => x.DumpOnHealthCheckRestart, DreamDaemonRights.CreateDump)
@@ -270,7 +271,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(
job,
- (core, paramJob, databaseContextFactory, progressReporter, ct) => core.Watchdog.Restart(false, ct),
+ (core, paramJob, databaseContextFactory, progressReporter, ct) => core!.Watchdog.Restart(false, ct),
cancellationToken);
return Accepted(job.ToApi());
});
@@ -296,7 +297,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(
job,
- (core, databaseContextFactory, paramJob, progressReporter, ct) => core.Watchdog.CreateDump(ct),
+ (core, databaseContextFactory, paramJob, progressReporter, ct) => core!.Watchdog.CreateDump(ct),
cancellationToken);
return Accepted(job.ToApi());
});
@@ -308,7 +309,7 @@ namespace Tgstation.Server.Host.Controllers
/// If there was a settings change made that forced a switch to .
/// The for the operation.
/// A resulting in the of the operation.
- ValueTask ReadImpl(DreamDaemonSettings settings, bool knownForcedReboot, CancellationToken cancellationToken)
+ ValueTask ReadImpl(DreamDaemonSettings? settings, bool knownForcedReboot, CancellationToken cancellationToken)
=> WithComponentInstance(async instance =>
{
var dd = instance.Watchdog;
@@ -322,7 +323,7 @@ namespace Tgstation.Server.Host.Controllers
.Instances
.AsQueryable()
.Where(x => x.Id == Instance.Id)
- .Select(x => x.DreamDaemonSettings)
+ .Select(x => x.DreamDaemonSettings!)
.FirstOrDefaultAsync(cancellationToken);
if (settings == default)
return this.Gone();
@@ -334,13 +335,13 @@ namespace Tgstation.Server.Host.Controllers
var alphaActive = dd.AlphaIsActive;
var llp = dd.LastLaunchParameters;
var rstate = dd.RebootState;
- result.AutoStart = settings.AutoStart.Value;
- result.CurrentPort = llp?.Port.Value;
- result.CurrentSecurity = llp?.SecurityLevel.Value;
- result.CurrentVisibility = llp?.Visibility.Value;
- result.CurrentAllowWebclient = llp?.AllowWebClient.Value;
- result.Port = settings.Port.Value;
- result.AllowWebClient = settings.AllowWebClient.Value;
+ result.AutoStart = settings.AutoStart!.Value;
+ result.CurrentPort = llp?.Port!.Value;
+ result.CurrentSecurity = llp?.SecurityLevel!.Value;
+ result.CurrentVisibility = llp?.Visibility!.Value;
+ result.CurrentAllowWebclient = llp?.AllowWebClient!.Value;
+ result.Port = settings.Port!.Value;
+ result.AllowWebClient = settings.AllowWebClient!.Value;
var firstIteration = true;
do
@@ -357,18 +358,18 @@ namespace Tgstation.Server.Host.Controllers
}
while (result.Status == WatchdogStatus.Online && !result.SessionId.HasValue); // this is the one invalid combo, it's not that racy
- result.SecurityLevel = settings.SecurityLevel.Value;
- result.Visibility = settings.Visibility.Value;
+ result.SecurityLevel = settings.SecurityLevel!.Value;
+ result.Visibility = settings.Visibility!.Value;
result.SoftRestart = rstate == RebootState.Restart;
result.SoftShutdown = rstate == RebootState.Shutdown;
if (rstate == RebootState.Normal && knownForcedReboot)
result.SoftRestart = true;
- result.StartupTimeout = settings.StartupTimeout.Value;
- result.HealthCheckSeconds = settings.HealthCheckSeconds.Value;
- result.DumpOnHealthCheckRestart = settings.DumpOnHealthCheckRestart.Value;
- result.TopicRequestTimeout = settings.TopicRequestTimeout.Value;
+ result.StartupTimeout = settings.StartupTimeout!.Value;
+ result.HealthCheckSeconds = settings.HealthCheckSeconds!.Value;
+ result.DumpOnHealthCheckRestart = settings.DumpOnHealthCheckRestart!.Value;
+ result.TopicRequestTimeout = settings.TopicRequestTimeout!.Value;
result.AdditionalParameters = settings.AdditionalParameters;
result.StartProfiler = settings.StartProfiler;
result.LogOutput = settings.LogOutput;
diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
index 714713763f..419bc9c90d 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
@@ -27,7 +27,6 @@ namespace Tgstation.Server.Host.Controllers
/// for managing the deployment system.
///
[Route(Routes.DreamMaker)]
-#pragma warning disable CA1506 // TODO: Decomplexify
public sealed class DreamMakerController : InstanceRequiredController
{
///
@@ -85,6 +84,10 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.FirstOrDefaultAsync(cancellationToken);
+
+ if (dreamMakerSettings == null)
+ return this.Gone();
+
return Json(dreamMakerSettings.ToApi());
}
@@ -148,7 +151,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(
job,
(core, databaseContextFactory, paramJob, progressReporter, jobCancellationToken)
- => core.DreamMaker.DeploymentProcess(paramJob, databaseContextFactory, progressReporter, jobCancellationToken),
+ => core!.DreamMaker.DeploymentProcess(paramJob, databaseContextFactory, progressReporter, jobCancellationToken),
cancellationToken);
return Accepted(job.ToApi());
}
@@ -187,9 +190,10 @@ namespace Tgstation.Server.Host.Controllers
if (hostModel == null)
return this.Gone();
+ var dreamMakerRights = InstancePermissionSet.DreamMakerRights!.Value;
if (model.ProjectName != null)
{
- if (!AuthenticationContext.InstancePermissionSet.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetDme))
+ if (!dreamMakerRights.HasFlag(DreamMakerRights.SetDme))
return Forbid();
if (model.ProjectName.Length == 0)
hostModel.ProjectName = null;
@@ -199,10 +203,10 @@ namespace Tgstation.Server.Host.Controllers
if (model.ApiValidationPort.HasValue)
{
- if (!AuthenticationContext.InstancePermissionSet.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetApiValidationPort))
+ if (!dreamMakerRights.HasFlag(DreamMakerRights.SetApiValidationPort))
return Forbid();
- if (model.ApiValidationPort.Value != hostModel.ApiValidationPort.Value)
+ if (model.ApiValidationPort.Value != hostModel.ApiValidationPort!.Value)
{
var verifiedPort = await portAllocator
.GetAvailablePort(
@@ -218,28 +222,28 @@ namespace Tgstation.Server.Host.Controllers
if (model.ApiValidationSecurityLevel.HasValue)
{
- if (!AuthenticationContext.InstancePermissionSet.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetSecurityLevel))
+ if (!dreamMakerRights.HasFlag(DreamMakerRights.SetSecurityLevel))
return Forbid();
hostModel.ApiValidationSecurityLevel = model.ApiValidationSecurityLevel;
}
if (model.RequireDMApiValidation.HasValue)
{
- if (!AuthenticationContext.InstancePermissionSet.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetApiValidationRequirement))
+ if (!dreamMakerRights.HasFlag(DreamMakerRights.SetApiValidationRequirement))
return Forbid();
hostModel.RequireDMApiValidation = model.RequireDMApiValidation;
}
if (model.Timeout.HasValue)
{
- if (!AuthenticationContext.InstancePermissionSet.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetTimeout))
+ if (!dreamMakerRights.HasFlag(DreamMakerRights.SetTimeout))
return Forbid();
hostModel.Timeout = model.Timeout;
}
await DatabaseContext.Save(cancellationToken);
- if ((AuthenticationContext.GetRight(RightsType.DreamMaker) & (ulong)DreamMakerRights.Read) == 0)
+ if (!dreamMakerRights.HasFlag(DreamMakerRights.Read))
return NoContent();
return await Read(cancellationToken);
@@ -252,17 +256,17 @@ namespace Tgstation.Server.Host.Controllers
IQueryable BaseCompileJobsQuery() => DatabaseContext
.CompileJobs
.AsQueryable()
- .Include(x => x.Job)
+ .Include(x => x.Job!)
.ThenInclude(x => x.StartedBy)
- .Include(x => x.Job)
+ .Include(x => x.Job!)
.ThenInclude(x => x.Instance)
- .Include(x => x.RevisionInformation)
- .ThenInclude(x => x.PrimaryTestMerge)
+ .Include(x => x.RevisionInformation!)
+ .ThenInclude(x => x.PrimaryTestMerge!)
.ThenInclude(x => x.MergedBy)
.Include(x => x.RevisionInformation)
- .ThenInclude(x => x.ActiveTestMerges)
- .ThenInclude(x => x.TestMerge)
- .ThenInclude(x => x.MergedBy)
- .Where(x => x.Job.Instance.Id == Instance.Id);
+ .ThenInclude(x => x.ActiveTestMerges!)
+ .ThenInclude(x => x!.TestMerge)
+ .ThenInclude(x => x!.MergedBy)
+ .Where(x => x.Job.Instance!.Id == Instance.Id);
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/EngineController.cs b/src/Tgstation.Server.Host/Controllers/EngineController.cs
index 75c7ef289b..c1a20fe99c 100644
--- a/src/Tgstation.Server.Host/Controllers/EngineController.cs
+++ b/src/Tgstation.Server.Host/Controllers/EngineController.cs
@@ -120,7 +120,7 @@ namespace Tgstation.Server.Host.Controllers
EngineVersion = x,
})
.AsQueryable()
- .OrderBy(x => x.EngineVersion.ToString()))),
+ .OrderBy(x => x.EngineVersion!.ToString()))),
null,
page,
pageSize,
@@ -155,16 +155,16 @@ namespace Tgstation.Server.Host.Controllers
var uploadingZip = model.UploadCustomZip == true;
- var userByondRights = AuthenticationContext.InstancePermissionSet.EngineRights.Value;
- var isByondEngine = model.EngineVersion.Engine.Value == EngineType.Byond;
+ var engineRights = InstancePermissionSet.EngineRights!.Value;
+ var isByondEngine = model.EngineVersion!.Engine!.Value == EngineType.Byond;
var officialPerm = isByondEngine
? EngineRights.InstallOfficialOrChangeActiveByondVersion
: EngineRights.InstallOfficialOrChangeActiveOpenDreamVersion;
var customPerm = isByondEngine
? EngineRights.InstallCustomByondVersion
: EngineRights.InstallCustomOpenDreamVersion;
- if ((!userByondRights.HasFlag(officialPerm) && !uploadingZip)
- || (!userByondRights.HasFlag(customPerm) && uploadingZip))
+ if ((!engineRights.HasFlag(officialPerm) && !uploadingZip)
+ || (!engineRights.HasFlag(customPerm) && uploadingZip))
return Forbid();
// remove cruff fields
@@ -217,7 +217,7 @@ namespace Tgstation.Server.Host.Controllers
EngineRights.CancelInstall);
job.Description += $" {model.EngineVersion}";
- IFileUploadTicket fileUploadTicket = null;
+ IFileUploadTicket? fileUploadTicket = null;
if (uploadingZip)
fileUploadTicket = fileTransferService.CreateUpload(FileUploadStreamKind.None);
@@ -227,7 +227,7 @@ namespace Tgstation.Server.Host.Controllers
job,
async (core, databaseContextFactory, paramJob, progressHandler, jobCancellationToken) =>
{
- MemoryStream zipFileStream = null;
+ MemoryStream? zipFileStream = null;
if (fileUploadTicket != null)
await using (fileUploadTicket)
{
@@ -245,7 +245,7 @@ namespace Tgstation.Server.Host.Controllers
}
await using (zipFileStream)
- await core.EngineManager.ChangeVersion(
+ await core!.EngineManager.ChangeVersion(
progressHandler,
model.EngineVersion,
zipFileStream,
@@ -291,18 +291,19 @@ namespace Tgstation.Server.Host.Controllers
if (earlyOut != null)
return earlyOut;
- var notInstalledResponse = await WithComponentInstance(
+ var engineVersion = model.EngineVersion!;
+ var notInstalledResponse = await WithComponentInstanceNullable(
instance =>
{
var byondManager = instance.EngineManager;
-
- if (model.EngineVersion.Equals(byondManager.ActiveVersion))
- return ValueTask.FromResult(
+ var activeVersion = byondManager.ActiveVersion;
+ if (activeVersion != null && engineVersion.Equals(activeVersion))
+ return ValueTask.FromResult(
Conflict(new ErrorMessageResponse(ErrorCode.EngineCannotDeleteActiveVersion)));
- var versionNotInstalled = !byondManager.InstalledVersions.Any(x => x.Equals(model.EngineVersion));
+ var versionNotInstalled = !byondManager.InstalledVersions.Any(x => x.Equals(engineVersion));
- return ValueTask.FromResult(
+ return ValueTask.FromResult(
versionNotInstalled
? this.Gone()
: null);
@@ -311,24 +312,24 @@ namespace Tgstation.Server.Host.Controllers
if (notInstalledResponse != null)
return notInstalledResponse;
- var isByondVersion = model.EngineVersion.Engine.Value == EngineType.Byond;
+ var isByondVersion = engineVersion.Engine!.Value == EngineType.Byond;
// run the install through the job manager
var cancelRight = isByondVersion
- ? model.EngineVersion.CustomIteration.HasValue
+ ? engineVersion.CustomIteration.HasValue
? EngineRights.InstallCustomByondVersion
: EngineRights.InstallOfficialOrChangeActiveByondVersion
- : model.EngineVersion.CustomIteration.HasValue
+ : engineVersion.CustomIteration.HasValue
? EngineRights.InstallOfficialOrChangeActiveOpenDreamVersion
: EngineRights.InstallCustomOpenDreamVersion;
var job = Models.Job.Create(JobCode.EngineDelete, AuthenticationContext.User, Instance, cancelRight);
- job.Description += $" {model.EngineVersion}";
+ job.Description += $" {engineVersion}";
await jobManager.RegisterOperation(
job,
(instanceCore, databaseContextFactory, job, progressReporter, jobCancellationToken)
- => instanceCore.EngineManager.DeleteVersion(progressReporter, model.EngineVersion, jobCancellationToken),
+ => instanceCore!.EngineManager.DeleteVersion(progressReporter, engineVersion, jobCancellationToken),
cancellationToken);
var apiResponse = job.ToApi();
@@ -339,8 +340,8 @@ namespace Tgstation.Server.Host.Controllers
/// Validate and normalize a given .
///
/// The to validate and normalize.
- /// The to return, if any.
- BadRequestObjectResult ValidateEngineVersion(EngineVersion version)
+ /// The to return, if any. otherwise.
+ BadRequestObjectResult? ValidateEngineVersion(EngineVersion? version)
{
if (version == null || !version.Engine.HasValue)
return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
@@ -357,7 +358,7 @@ namespace Tgstation.Server.Host.Controllers
if (isByond)
{
- version.Version = NormalizeByondVersion(version.Version);
+ version.Version = NormalizeByondVersion(version.Version!);
if (version.Version.Build != -1)
return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
}
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index d63396daf9..ded9313475 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
@@ -43,11 +44,6 @@ namespace Tgstation.Server.Host.Controllers
///
public const string InstanceAttachFileName = "TGS4_ALLOW_INSTANCE_ATTACH";
- ///
- /// Prefix for move s.
- ///
- const string MoveInstanceJobPrefix = "Move instance ID ";
-
///
/// The for the .
///
@@ -145,8 +141,8 @@ namespace Tgstation.Server.Host.Controllers
{
ArgumentNullException.ThrowIfNull(model);
- if (String.IsNullOrWhiteSpace(model.Name))
- return BadRequest(new ErrorMessageResponse(ErrorCode.InstanceWhitespaceName));
+ if (String.IsNullOrWhiteSpace(model.Name) || String.IsNullOrWhiteSpace(model.Path))
+ return BadRequest(new ErrorMessageResponse(ErrorCode.InstanceWhitespaceNameOrPath));
var unNormalizedPath = model.Path;
var targetInstancePath = NormalizePath(unNormalizedPath);
@@ -170,7 +166,7 @@ namespace Tgstation.Server.Host.Controllers
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));
// Validate it's not a child of any other instance
- IActionResult earlyOut = null;
+ IActionResult? earlyOut = null;
ulong countOfOtherInstances = 0;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
@@ -190,7 +186,7 @@ namespace Tgstation.Server.Host.Controllers
{
if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
earlyOut ??= Conflict(new ErrorMessageResponse(ErrorCode.InstanceLimitReached));
- else if (InstanceIsChildOf(otherInstance.Path))
+ else if (InstanceIsChildOf(otherInstance.Path!))
earlyOut ??= Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));
if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
@@ -306,15 +302,16 @@ namespace Tgstation.Server.Host.Controllers
.FirstOrDefaultAsync(cancellationToken);
if (originalModel == default)
return this.Gone();
- if (originalModel.Online.Value)
+ if (originalModel.Online!.Value)
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceDetachOnline));
DatabaseContext.Instances.Remove(originalModel);
- var attachFileName = ioManager.ConcatPath(originalModel.Path, InstanceAttachFileName);
+ var originalPath = originalModel.Path!;
+ var attachFileName = ioManager.ConcatPath(originalPath, InstanceAttachFileName);
try
{
- if (await ioManager.DirectoryExists(originalModel.Path, cancellationToken))
+ if (await ioManager.DirectoryExists(originalPath, cancellationToken))
await ioManager.WriteAllBytes(attachFileName, Array.Empty(), cancellationToken);
}
catch (OperationCanceledException)
@@ -353,14 +350,15 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == model.Id && x.SwarmIdentifer == swarmConfiguration.Identifier);
var moveJob = await InstanceQuery()
- .SelectMany(x => x.Jobs).
- Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix))
- .Select(x => new Job(x.Id.Value)).FirstOrDefaultAsync(cancellationToken);
+ .SelectMany(x => x.Jobs)
+ .Where(x => !x.StoppedAt.HasValue && x.JobCode == JobCode.Move)
+ .Select(x => new Job(x.Id!.Value))
+ .FirstOrDefaultAsync(cancellationToken);
- if (moveJob != default)
+ if (moveJob != null)
{
// don't allow them to cancel it if they can't start it.
- if (!AuthenticationContext.PermissionSet.InstanceManagerRights.Value.HasFlag(InstanceManagerRights.Relocate))
+ if (!AuthenticationContext.PermissionSet.InstanceManagerRights!.Value.HasFlag(InstanceManagerRights.Relocate))
return Forbid();
await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken); // cancel it now
}
@@ -393,8 +391,9 @@ namespace Tgstation.Server.Host.Controllers
return false;
}
- string originalModelPath = null;
- string rawPath = null;
+ string? originalModelPath = null;
+ string? rawPath = null;
+ var originalOnline = originalModel.Online!.Value;
if (model.Path != null)
{
rawPath = NormalizePath(model.Path);
@@ -403,7 +402,7 @@ namespace Tgstation.Server.Host.Controllers
{
if (!userRights.HasFlag(InstanceManagerRights.Relocate))
return Forbid();
- if (originalModel.Online.Value && model.Online != true)
+ if (originalOnline && model.Online != true)
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceRelocateOnline));
var dirExistsTask = ioManager.DirectoryExists(model.Path, cancellationToken);
@@ -415,8 +414,7 @@ namespace Tgstation.Server.Host.Controllers
}
}
- var oldAutoUpdateInterval = originalModel.AutoUpdateInterval.Value;
- var originalOnline = originalModel.Online.Value;
+ var oldAutoUpdateInterval = originalModel.AutoUpdateInterval!.Value;
var renamed = model.Name != null && originalModel.Name != model.Name;
if (CheckModified(x => x.AutoUpdateInterval, InstanceManagerRights.SetAutoUpdate)
@@ -443,16 +441,16 @@ namespace Tgstation.Server.Host.Controllers
if (renamed)
{
// ignoring retval because we don't care if it's offline
- await WithComponentInstance(
+ await WithComponentInstanceNullable(
async componentInstance =>
{
- await componentInstance.InstanceRenamed(originalModel.Name, cancellationToken);
+ await componentInstance.InstanceRenamed(originalModel.Name!, cancellationToken);
return null;
},
originalModel);
}
- var oldAutoStart = originalModel.DreamDaemonSettings.AutoStart;
+ var oldAutoStart = originalModel.DreamDaemonSettings!.AutoStart;
try
{
if (originalOnline && model.Online == false)
@@ -487,14 +485,14 @@ namespace Tgstation.Server.Host.Controllers
var moving = originalModelPath != null;
if (moving)
{
- var description = $"{MoveInstanceJobPrefix}{originalModel.Id} from {originalModelPath} to {rawPath}";
+ var description = $"Move instance ID {originalModel.Id} from {originalModelPath} to {rawPath}";
var job = Job.Create(JobCode.Move, AuthenticationContext.User, originalModel, InstanceManagerRights.Relocate);
job.Description = description;
await jobManager.RegisterOperation(
job,
(core, databaseContextFactory, paramJob, progressHandler, ct) // core will be null here since the instance is offline
- => InstanceOperations.MoveInstance(originalModel, originalModelPath, ct),
+ => InstanceOperations.MoveInstance(originalModel, originalModelPath!, ct),
cancellationToken);
api.MoveJob = job.ToApi();
}
@@ -502,7 +500,7 @@ namespace Tgstation.Server.Host.Controllers
if (model.AutoUpdateInterval.HasValue && oldAutoUpdateInterval != model.AutoUpdateInterval)
{
// ignoring retval because we don't care if it's offline
- await WithComponentInstance(
+ await WithComponentInstanceNullable(
async componentInstance =>
{
await componentInstance.SetAutoUpdateInterval(model.AutoUpdateInterval.Value);
@@ -538,9 +536,9 @@ namespace Tgstation.Server.Host.Controllers
.Instances
.AsQueryable()
.Where(x => x.SwarmIdentifer == swarmConfiguration.Identifier);
- if (!AuthenticationContext.PermissionSet.InstanceManagerRights.Value.HasFlag(InstanceManagerRights.List))
+ if (!AuthenticationContext.PermissionSet.InstanceManagerRights!.Value.HasFlag(InstanceManagerRights.List))
query = query
- .Where(x => x.InstancePermissionSets.Any(y => y.PermissionSetId == AuthenticationContext.PermissionSet.Id.Value))
+ .Where(x => x.InstancePermissionSets.Any(y => y.PermissionSetId == AuthenticationContext.PermissionSet.Id))
.Where(x => x.InstancePermissionSets.Any(instanceUser =>
instanceUser.EngineRights != EngineRights.None ||
instanceUser.ChatBotRights != ChatBotRights.None ||
@@ -555,8 +553,9 @@ namespace Tgstation.Server.Host.Controllers
var moveJobs = await GetBaseQuery()
.SelectMany(x => x.Jobs)
- .Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix))
- .Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy)
+ .Where(x => !x.StoppedAt.HasValue && x.JobCode == JobCode.Move)
+ .Include(x => x.StartedBy!)
+ .ThenInclude(x => x.CreatedBy)
.Include(x => x.Instance)
.ToListAsync(cancellationToken);
@@ -569,7 +568,7 @@ namespace Tgstation.Server.Host.Controllers
async instance =>
{
needsUpdate |= ValidateInstanceOnlineStatus(instance);
- instance.MoveJob = moveJobs.FirstOrDefault(x => x.Instance.Id == instance.Id)?.ToApi();
+ instance.MoveJob = moveJobs.FirstOrDefault(x => x.Instance!.Id == instance.Id)?.ToApi();
await CheckAccessible(instance, cancellationToken);
},
page,
@@ -596,7 +595,7 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
public async ValueTask GetId(long id, CancellationToken cancellationToken)
{
- var cantList = !AuthenticationContext.PermissionSet.InstanceManagerRights.Value.HasFlag(InstanceManagerRights.List);
+ var cantList = !AuthenticationContext.PermissionSet.InstanceManagerRights!.Value.HasFlag(InstanceManagerRights.List);
IQueryable QueryForUser()
{
var query = DatabaseContext
@@ -617,8 +616,8 @@ namespace Tgstation.Server.Host.Controllers
if (ValidateInstanceOnlineStatus(instance))
await DatabaseContext.Save(cancellationToken);
- if (cantList && !instance.InstancePermissionSets.Any(instanceUser => instanceUser.PermissionSetId == AuthenticationContext.PermissionSet.Id.Value &&
- (instanceUser.RepositoryRights != RepositoryRights.None ||
+ if (cantList && !instance.InstancePermissionSets.Any(instanceUser => instanceUser.PermissionSetId == AuthenticationContext.PermissionSet.Require(x => x.Id)
+ && (instanceUser.RepositoryRights != RepositoryRights.None ||
instanceUser.EngineRights != EngineRights.None ||
instanceUser.ChatBotRights != ChatBotRights.None ||
instanceUser.ConfigurationRights != ConfigurationRights.None ||
@@ -631,8 +630,8 @@ namespace Tgstation.Server.Host.Controllers
var moveJob = await QueryForUser()
.SelectMany(x => x.Jobs)
- .Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix))
- .Include(x => x.StartedBy)
+ .Where(x => !x.StoppedAt.HasValue && x.JobCode == JobCode.Move)
+ .Include(x => x.StartedBy!)
.ThenInclude(x => x.CreatedBy)
.Include(x => x.Instance)
.FirstOrDefaultAsync(cancellationToken);
@@ -662,7 +661,7 @@ namespace Tgstation.Server.Host.Controllers
// ensure the current user has write privilege on the instance
var usersInstancePermissionSet = await BaseQuery()
.SelectMany(x => x.InstancePermissionSets)
- .Where(x => x.PermissionSetId == AuthenticationContext.PermissionSet.Id.Value)
+ .Where(x => x.PermissionSetId == AuthenticationContext.PermissionSet.Id)
.FirstOrDefaultAsync(cancellationToken);
if (usersInstancePermissionSet == default)
{
@@ -691,7 +690,7 @@ namespace Tgstation.Server.Host.Controllers
/// The .
/// The for the operation.
/// A resulting in the new or if ports could not be allocated.
- async ValueTask CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken)
+ async ValueTask CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken)
{
var ddPort = await portAllocator.GetAvailablePort(1024, false, cancellationToken);
if (!ddPort.HasValue)
@@ -771,12 +770,12 @@ namespace Tgstation.Server.Host.Controllers
///
/// An optional existing to update.
/// or a new with full rights.
- InstancePermissionSet InstanceAdminPermissionSet(InstancePermissionSet permissionSetToModify)
+ InstancePermissionSet InstanceAdminPermissionSet(InstancePermissionSet? permissionSetToModify)
{
permissionSetToModify ??= new InstancePermissionSet()
{
PermissionSet = AuthenticationContext.PermissionSet,
- PermissionSetId = AuthenticationContext.PermissionSet.Id.Value,
+ PermissionSetId = AuthenticationContext.PermissionSet.Require(x => x.Id),
};
permissionSetToModify.EngineRights = RightsHelper.AllRights();
permissionSetToModify.ChatBotRights = RightsHelper.AllRights();
@@ -793,7 +792,8 @@ namespace Tgstation.Server.Host.Controllers
///
/// The path to normalize.
/// The normalized .
- string NormalizePath(string path)
+ [return: NotNullIfNotNull(nameof(path))]
+ string? NormalizePath(string? path)
{
if (path == null)
return null;
diff --git a/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs b/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs
index 13defd8048..d86f0b7877 100644
--- a/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs
@@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Controllers
RepositoryRights = RightsHelper.Clamp(model.RepositoryRights ?? RepositoryRights.None),
InstancePermissionSetRights = RightsHelper.Clamp(model.InstancePermissionSetRights ?? InstancePermissionSetRights.None),
PermissionSetId = model.PermissionSetId,
- InstanceId = Instance.Id.Value,
+ InstanceId = Instance.Require(x => x.Id),
};
DatabaseContext.InstancePermissionSets.Add(dbUser);
@@ -156,16 +156,16 @@ namespace Tgstation.Server.Host.Controllers
if (originalPermissionSet == null)
return this.Gone();
- originalPermissionSet.EngineRights = RightsHelper.Clamp(model.EngineRights ?? originalPermissionSet.EngineRights.Value);
- originalPermissionSet.RepositoryRights = RightsHelper.Clamp(model.RepositoryRights ?? originalPermissionSet.RepositoryRights.Value);
- originalPermissionSet.InstancePermissionSetRights = RightsHelper.Clamp(model.InstancePermissionSetRights ?? originalPermissionSet.InstancePermissionSetRights.Value);
- originalPermissionSet.ChatBotRights = RightsHelper.Clamp(model.ChatBotRights ?? originalPermissionSet.ChatBotRights.Value);
- originalPermissionSet.ConfigurationRights = RightsHelper.Clamp(model.ConfigurationRights ?? originalPermissionSet.ConfigurationRights.Value);
- originalPermissionSet.DreamDaemonRights = RightsHelper.Clamp(model.DreamDaemonRights ?? originalPermissionSet.DreamDaemonRights.Value);
- originalPermissionSet.DreamMakerRights = RightsHelper.Clamp(model.DreamMakerRights ?? originalPermissionSet.DreamMakerRights.Value);
+ originalPermissionSet.EngineRights = RightsHelper.Clamp(model.EngineRights ?? originalPermissionSet.EngineRights!.Value);
+ originalPermissionSet.RepositoryRights = RightsHelper.Clamp(model.RepositoryRights ?? originalPermissionSet.RepositoryRights!.Value);
+ originalPermissionSet.InstancePermissionSetRights = RightsHelper.Clamp(model.InstancePermissionSetRights ?? originalPermissionSet.InstancePermissionSetRights!.Value);
+ originalPermissionSet.ChatBotRights = RightsHelper.Clamp(model.ChatBotRights ?? originalPermissionSet.ChatBotRights!.Value);
+ originalPermissionSet.ConfigurationRights = RightsHelper.Clamp(model.ConfigurationRights ?? originalPermissionSet.ConfigurationRights!.Value);
+ originalPermissionSet.DreamDaemonRights = RightsHelper.Clamp(model.DreamDaemonRights ?? originalPermissionSet.DreamDaemonRights!.Value);
+ originalPermissionSet.DreamMakerRights = RightsHelper.Clamp(model.DreamMakerRights ?? originalPermissionSet.DreamMakerRights!.Value);
await DatabaseContext.Save(cancellationToken);
- var showFullPermissionSet = originalPermissionSet.PermissionSetId == AuthenticationContext.PermissionSet.Id.Value
+ var showFullPermissionSet = originalPermissionSet.PermissionSetId == AuthenticationContext.PermissionSet.Require(x => x.Id)
|| (AuthenticationContext.GetRight(RightsType.InstancePermissionSet) & (ulong)InstancePermissionSetRights.Read) != 0;
return Json(
showFullPermissionSet
@@ -184,7 +184,7 @@ namespace Tgstation.Server.Host.Controllers
[HttpGet]
[TgsAuthorize]
[ProducesResponseType(typeof(InstancePermissionSetResponse), 200)]
- public IActionResult Read() => Json(AuthenticationContext.InstancePermissionSet.ToApi());
+ public IActionResult Read() => Json(InstancePermissionSet.ToApi());
///
/// Lists s for the instance.
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs b/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs
index cdf3f0e139..f3a6ff9bdc 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceRequiredController.cs
@@ -12,6 +12,16 @@ namespace Tgstation.Server.Host.Controllers
///
public abstract class InstanceRequiredController : ComponentInterfacingController
{
+ ///
+ /// The .
+ ///
+ protected new Models.Instance Instance => base.Instance!;
+
+ ///
+ /// The for the request.
+ ///
+ protected Models.InstancePermissionSet InstancePermissionSet => AuthenticationContext.InstancePermissionSet!;
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs
index 521a560a0b..2b9f1b15f1 100644
--- a/src/Tgstation.Server.Host/Controllers/JobController.cs
+++ b/src/Tgstation.Server.Host/Controllers/JobController.cs
@@ -79,7 +79,7 @@ namespace Tgstation.Server.Host.Controllers
.Include(x => x.StartedBy)
.Include(x => x.CancelledBy)
.Include(x => x.Instance)
- .Where(x => x.Instance.Id == Instance.Id && !x.StoppedAt.HasValue)
+ .Where(x => x.Instance!.Id == Instance.Id && !x.StoppedAt.HasValue)
.OrderByDescending(x => x.StartedAt))),
AddJobProgressResponseTransformer,
page,
@@ -107,7 +107,7 @@ namespace Tgstation.Server.Host.Controllers
.Include(x => x.StartedBy)
.Include(x => x.CancelledBy)
.Include(x => x.Instance)
- .Where(x => x.Instance.Id == Instance.Id)
+ .Where(x => x.Instance!.Id == Instance.Id)
.OrderByDescending(x => x.StartedAt))),
AddJobProgressResponseTransformer,
page,
@@ -135,7 +135,7 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Include(x => x.StartedBy)
.Include(x => x.Instance)
- .Where(x => x.Id == id && x.Instance.Id == Instance.Id)
+ .Where(x => x.Id == id && x.Instance!.Id == Instance.Id)
.FirstOrDefaultAsync(cancellationToken);
if (job == default)
return NotFound();
@@ -167,7 +167,7 @@ namespace Tgstation.Server.Host.Controllers
var job = await DatabaseContext
.Jobs
.AsQueryable()
- .Where(x => x.Id == id && x.Instance.Id == Instance.Id)
+ .Where(x => x.Id == id && x.Instance!.Id == Instance.Id)
.Include(x => x.StartedBy)
.Include(x => x.CancelledBy)
.Include(x => x.Instance)
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index 6bc447a044..5d4d686aaf 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -151,7 +151,7 @@ namespace Tgstation.Server.Host.Controllers
job,
async (core, databaseContextFactory, paramJob, progressReporter, ct) =>
{
- var repoManager = core.RepositoryManager;
+ var repoManager = core!.RepositoryManager;
using var repos = await repoManager.CloneRepository(
origin,
cloneBranch,
@@ -211,13 +211,13 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken);
- Logger.LogInformation("Instance {instanceId} repository delete initiated by user {userId}", Instance.Id, AuthenticationContext.User.Id.Value);
+ Logger.LogInformation("Instance {instanceId} repository delete initiated by user {userId}", Instance.Id, AuthenticationContext.User.Require(x => x.Id));
var job = Job.Create(JobCode.RepositoryDelete, AuthenticationContext.User, Instance);
var api = currentModel.ToApi();
await jobManager.RegisterOperation(
job,
- (core, databaseContextFactory, paramJob, progressReporter, ct) => core.RepositoryManager.DeleteRepository(ct),
+ (core, databaseContextFactory, paramJob, progressReporter, ct) => core!.RepositoryManager.DeleteRepository(ct),
cancellationToken);
api.ActiveJob = job.ToApi();
return Accepted(api);
@@ -376,23 +376,23 @@ namespace Tgstation.Server.Host.Controllers
var api = canRead ? currentModel.ToApi() : new RepositoryResponse();
if (canRead)
{
- var earlyOut = await WithComponentInstance(
- async instance =>
- {
- var repoManager = instance.RepositoryManager;
- if (repoManager.CloneInProgress)
- return Conflict(new ErrorMessageResponse(ErrorCode.RepoCloning));
+ var earlyOut = await WithComponentInstanceNullable(
+ async instance =>
+ {
+ var repoManager = instance.RepositoryManager;
+ if (repoManager.CloneInProgress)
+ return Conflict(new ErrorMessageResponse(ErrorCode.RepoCloning));
- if (repoManager.InUse)
- return Conflict(new ErrorMessageResponse(ErrorCode.RepoBusy));
+ if (repoManager.InUse)
+ return Conflict(new ErrorMessageResponse(ErrorCode.RepoBusy));
- using var repo = await repoManager.LoadRepository(cancellationToken);
- if (repo == null)
- return Conflict(new ErrorMessageResponse(ErrorCode.RepoMissing));
- await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken);
+ using var repo = await repoManager.LoadRepository(cancellationToken);
+ if (repo == null)
+ return Conflict(new ErrorMessageResponse(ErrorCode.RepoMissing));
+ await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken);
- return null;
- });
+ return null;
+ });
if (earlyOut != null)
return earlyOut;
@@ -402,7 +402,7 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken);
// format the job description
- string description = null;
+ string? description = null;
if (model.UpdateFromOrigin == true)
if (model.Reference != null)
description = String.Format(CultureInfo.InvariantCulture, "Fetch and hard reset repository to origin/{0}", model.Reference);
@@ -422,7 +422,7 @@ namespace Tgstation.Server.Host.Controllers
: "T",
String.Join(
", ",
- model.NewTestMerges.Select(
+ model.NewTestMerges!.Select(
x => String.Format(
CultureInfo.InvariantCulture,
"#{0}{1}",
@@ -448,7 +448,7 @@ namespace Tgstation.Server.Host.Controllers
currentModel,
AuthenticationContext.User,
loggerFactory.CreateLogger(),
- Instance.Id.Value);
+ Instance.Require(x => x.Id));
// Time to access git, do it in a job
await jobManager.RegisterOperation(
@@ -484,16 +484,14 @@ namespace Tgstation.Server.Host.Controllers
apiResponse.Reference = repository.Reference;
// rev info stuff
- Models.RevisionInformation revisionInfo = null;
var needsDbUpdate = await RepositoryUpdateService.LoadRevisionInformation(
repository,
databaseContext,
Logger,
instance,
null,
- newRevInfo => revisionInfo = newRevInfo,
+ newRevInfo => apiResponse.RevisionInformation = newRevInfo.ToApi(),
cancellationToken);
- apiResponse.RevisionInformation = revisionInfo.ToApi();
return needsDbUpdate;
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/Results/LimitedStreamResultExecutor.cs b/src/Tgstation.Server.Host/Controllers/Results/LimitedStreamResultExecutor.cs
index 3178db355b..c3d3b7d372 100644
--- a/src/Tgstation.Server.Host/Controllers/Results/LimitedStreamResultExecutor.cs
+++ b/src/Tgstation.Server.Host/Controllers/Results/LimitedStreamResultExecutor.cs
@@ -53,7 +53,7 @@ namespace Tgstation.Server.Host.Controllers.Results
}
else
{
- stream.Seek(range.From.Value, SeekOrigin.Begin);
+ stream.Seek(range.From ?? 0, SeekOrigin.Begin);
await StreamCopyOperation.CopyToAsync(
stream,
outputStream,
diff --git a/src/Tgstation.Server.Host/Controllers/Results/PaginatableResult.cs b/src/Tgstation.Server.Host/Controllers/Results/PaginatableResult.cs
index b936d0f3e6..5d4f80d023 100644
--- a/src/Tgstation.Server.Host/Controllers/Results/PaginatableResult.cs
+++ b/src/Tgstation.Server.Host/Controllers/Results/PaginatableResult.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
@@ -11,15 +12,22 @@ namespace Tgstation.Server.Host.Controllers.Results
/// The of model intended to be returned.
public sealed class PaginatableResult
{
+ ///
+ /// Whether or not the is valid.
+ ///
+ [MemberNotNullWhen(true, nameof(Results))]
+ [MemberNotNullWhen(false, nameof(EarlyOut))]
+ public bool Valid => EarlyOut == null;
+
///
/// The results.
///
- public IOrderedQueryable Results { get; }
+ public IOrderedQueryable? Results { get; }
///
/// An to return immediately.
///
- public IActionResult EarlyOut { get; }
+ public IActionResult? EarlyOut { get; }
///
/// Initializes a new instance of the class.
diff --git a/src/Tgstation.Server.Host/Controllers/RootController.cs b/src/Tgstation.Server.Host/Controllers/RootController.cs
index 3ccca028fe..9ae6e7e942 100644
--- a/src/Tgstation.Server.Host/Controllers/RootController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RootController.cs
@@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Controllers
else
return Redirect(ApiDocumentationRoute);
- Dictionary links;
+ Dictionary? links;
if (panelEnabled)
links = new Dictionary()
{
@@ -134,7 +134,7 @@ namespace Tgstation.Server.Host.Controllers
? LogoSvgWindowsName
: LogoSvgLinuxName;
- return (IActionResult)this.TryServeFile(hostEnvironment, logger, $"{logoFileName}.svg") ?? NotFound();
+ return (IActionResult?)this.TryServeFile(hostEnvironment, logger, $"{logoFileName}.svg") ?? NotFound();
}
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/SwarmController.cs b/src/Tgstation.Server.Host/Controllers/SwarmController.cs
index 2512eda8dd..be171b19d8 100644
--- a/src/Tgstation.Server.Host/Controllers/SwarmController.cs
+++ b/src/Tgstation.Server.Host/Controllers/SwarmController.cs
@@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// Get the current registration from the .
///
- internal Guid RequestRegistrationId => Guid.Parse(Request.Headers[SwarmConstants.RegistrationIdHeader].First());
+ internal Guid RequestRegistrationId => Guid.Parse(Request.Headers[SwarmConstants.RegistrationIdHeader].First()!);
///
/// The for the .
@@ -150,6 +150,9 @@ namespace Tgstation.Server.Host.Controllers
{
ArgumentNullException.ThrowIfNull(serversUpdateRequest);
+ if (serversUpdateRequest.SwarmServers == null)
+ return BadRequest();
+
if (!ValidateRegistration())
return Forbid();
@@ -210,7 +213,7 @@ namespace Tgstation.Server.Host.Controllers
}
///
- protected override async ValueTask HookExecuteAction(Func executeAction, CancellationToken cancellationToken)
+ protected override async ValueTask HookExecuteAction(Func executeAction, CancellationToken cancellationToken)
{
using (LogContext.PushProperty(SerilogContextHelper.RequestPathContextProperty, $"{Request.Method} {Request.Path}"))
{
@@ -241,7 +244,7 @@ namespace Tgstation.Server.Host.Controllers
if (ModelState?.IsValid == false)
{
var errorMessages = ModelState
- .SelectMany(x => x.Value.Errors)
+ .SelectMany(x => x.Value!.Errors)
.Select(x => x.ErrorMessage);
logger.LogDebug(
diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs
index 08c61e05fe..68bccfa169 100644
--- a/src/Tgstation.Server.Host/Controllers/UserController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UserController.cs
@@ -103,7 +103,7 @@ namespace Tgstation.Server.Host.Controllers
return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
if ((model.Password != null && model.SystemIdentifier != null)
- || (model.Password == null && model.SystemIdentifier == null && model.OAuthConnections?.Any() != true))
+ || (model.Password == null && model.SystemIdentifier == null && (model.OAuthConnections?.Count > 0) != true))
return BadRequest(new ErrorMessageResponse(ErrorCode.UserMismatchPasswordSid));
if (model.Group != null && model.PermissionSet != null)
@@ -144,14 +144,14 @@ namespace Tgstation.Server.Host.Controllers
{
return RequiresPosixSystemIdentity(ex);
}
- else if (!(model.Password?.Length == 0 && model.OAuthConnections?.Any() == true))
+ else if (!(model.Password?.Length == 0 && (model.OAuthConnections?.Count > 0) == true))
{
- var result = TrySetPassword(dbUser, model.Password, true);
+ var result = TrySetPassword(dbUser, model.Password!, true);
if (result != null)
return result;
}
- dbUser.CanonicalName = Models.User.CanonicalizeName(dbUser.Name);
+ dbUser.CanonicalName = Models.User.CanonicalizeName(dbUser.Name!);
DatabaseContext.Users.Add(dbUser);
@@ -204,7 +204,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == model.Id)
.Include(x => x.CreatedBy)
.Include(x => x.OAuthConnections)
- .Include(x => x.Group)
+ .Include(x => x.Group!)
.ThenInclude(x => x.PermissionSet)
.Include(x => x.PermissionSet)
.FirstOrDefaultAsync(cancellationToken);
@@ -254,7 +254,7 @@ namespace Tgstation.Server.Host.Controllers
bool userWasDisabled;
if (model.Enabled.HasValue)
{
- userWasDisabled = originalUser.Enabled.Value && !model.Enabled.Value;
+ userWasDisabled = originalUser.Require(x => x.Enabled) && !model.Enabled.Value;
if (userWasDisabled)
originalUser.LastPasswordUpdate = DateTimeOffset.UtcNow;
@@ -264,7 +264,7 @@ namespace Tgstation.Server.Host.Controllers
userWasDisabled = false;
if (model.OAuthConnections != null
- && (model.OAuthConnections.Count != originalUser.OAuthConnections.Count
+ && (model.OAuthConnections.Count != originalUser.OAuthConnections!.Count
|| !model.OAuthConnections.All(x => originalUser.OAuthConnections.Any(y => y.Provider == x.Provider && y.ExternalUserId == x.ExternalUserId))))
{
if (originalUser.CanonicalName == Models.User.CanonicalizeName(DefaultCredentials.AdminUserName))
@@ -372,7 +372,7 @@ namespace Tgstation.Server.Host.Controllers
.Include(x => x.CreatedBy)
.Include(x => x.PermissionSet)
.Include(x => x.OAuthConnections)
- .Include(x => x.Group)
+ .Include(x => x.Group!)
.ThenInclude(x => x.PermissionSet)
.OrderBy(x => x.Id))),
null,
@@ -405,7 +405,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == id)
.Include(x => x.CreatedBy)
.Include(x => x.OAuthConnections)
- .Include(x => x.Group)
+ .Include(x => x.Group!)
.ThenInclude(x => x.PermissionSet)
.Include(x => x.PermissionSet)
.FirstOrDefaultAsync(cancellationToken);
@@ -426,8 +426,8 @@ namespace Tgstation.Server.Host.Controllers
/// A resulting in a new on success, if the requested did not exist.
async ValueTask CreateNewUserFromModel(Api.Models.Internal.UserApiBase model, CancellationToken cancellationToken)
{
- Models.PermissionSet permissionSet = null;
- UserGroup group = null;
+ Models.PermissionSet? permissionSet = null;
+ UserGroup? group = null;
if (model.Group != null)
group = await DatabaseContext
.Groups
@@ -469,7 +469,7 @@ namespace Tgstation.Server.Host.Controllers
/// The to check.
/// If this is a new .
/// if is valid, a otherwise.
- BadRequestObjectResult CheckValidName(UserUpdateRequest model, bool newUser)
+ BadRequestObjectResult? CheckValidName(UserUpdateRequest model, bool newUser)
{
var userInvalidWithNullName = newUser && model.Name == null && model.SystemIdentifier == null;
if (userInvalidWithNullName || (model.Name != null && String.IsNullOrWhiteSpace(model.Name)))
@@ -488,7 +488,7 @@ namespace Tgstation.Server.Host.Controllers
/// The new password.
/// If this is for a new .
/// on success, if is too short.
- BadRequestObjectResult TrySetPassword(User dbUser, string newPassword, bool newUser)
+ BadRequestObjectResult? TrySetPassword(User dbUser, string newPassword, bool newUser)
{
newPassword ??= String.Empty;
if (newPassword.Length < generalConfiguration.MinimumPasswordLength)
diff --git a/src/Tgstation.Server.Host/Controllers/UserGroupController.cs b/src/Tgstation.Server.Host/Controllers/UserGroupController.cs
index 3f2c5ea3a2..b6f48b8197 100644
--- a/src/Tgstation.Server.Host/Controllers/UserGroupController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UserGroupController.cs
@@ -131,7 +131,7 @@ namespace Tgstation.Server.Host.Controllers
if (model.PermissionSet != null)
{
- currentGroup.PermissionSet.AdministrationRights = model.PermissionSet.AdministrationRights ?? currentGroup.PermissionSet.AdministrationRights;
+ currentGroup.PermissionSet!.AdministrationRights = model.PermissionSet.AdministrationRights ?? currentGroup.PermissionSet.AdministrationRights;
currentGroup.PermissionSet.InstanceManagerRights = model.PermissionSet.InstanceManagerRights ?? currentGroup.PermissionSet.InstanceManagerRights;
}
@@ -139,7 +139,7 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken);
- if (!AuthenticationContext.PermissionSet.AdministrationRights.Value.HasFlag(AdministrationRights.ReadUsers))
+ if (!AuthenticationContext.PermissionSet.AdministrationRights!.Value.HasFlag(AdministrationRights.ReadUsers))
return Json(new UserGroupResponse
{
Id = currentGroup.Id,
@@ -220,7 +220,7 @@ namespace Tgstation.Server.Host.Controllers
var numDeleted = await DatabaseContext
.Groups
.AsQueryable()
- .Where(x => x.Id == id && x.Users.Count == 0)
+ .Where(x => x.Id == id && x.Users!.Count == 0)
.DeleteAsync(cancellationToken);
if (numDeleted > 0)
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 10f2728465..3b80d9da38 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -1,7 +1,7 @@
using System;
+using System.Collections.Frozen;
using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
using System.Threading.Tasks;
using Cyberboss.AspNetCore.AsyncInitializer;
@@ -79,7 +79,7 @@ namespace Tgstation.Server.Host.Core
///
/// The for the .
///
- ITokenFactory tokenFactory;
+ ITokenFactory? tokenFactory;
///
/// Create the default .
@@ -381,12 +381,13 @@ namespace Tgstation.Server.Host.Core
openDreamRepositoryDirectory),
new NoopEventConsumer()));
- services.AddSingleton>(
+ services.AddSingleton(
serviceProvider => new Dictionary
{
{ EngineType.Byond, serviceProvider.GetRequiredService() },
{ EngineType.OpenDream, serviceProvider.GetRequiredService() },
- });
+ }
+ .ToFrozenDictionary());
services.AddSingleton();
if (postSetupServices.InternalConfiguration.UsingSystemD)
@@ -537,7 +538,7 @@ namespace Tgstation.Server.Host.Core
applicationBuilder.UseRouting();
// Set up CORS based on configuration if necessary
- Action corsBuilder = null;
+ Action? corsBuilder = null;
if (controlPanelConfiguration.AllowAnyOrigin)
{
logger.LogTrace("Access-Control-Allow-Origin: *");
@@ -546,7 +547,7 @@ namespace Tgstation.Server.Host.Core
else if (controlPanelConfiguration.AllowedOrigins?.Count > 0)
{
logger.LogTrace("Access-Control-Allow-Origin: {allowedOrigins}", String.Join(',', controlPanelConfiguration.AllowedOrigins));
- corsBuilder = builder => builder.WithOrigins(controlPanelConfiguration.AllowedOrigins.ToArray());
+ corsBuilder = builder => builder.WithOrigins([.. controlPanelConfiguration.AllowedOrigins]);
}
var originalBuilder = corsBuilder;
@@ -621,9 +622,9 @@ namespace Tgstation.Server.Host.Core
// return provider.GetRequiredService().CurrentAuthenticationContext
// But M$ said
// https://stackoverflow.com/questions/56792917/scoped-services-in-asp-net-core-with-signalr-hubs
- services.AddScoped(provider => provider
+ services.AddScoped(provider => (provider
.GetRequiredService()
- .HttpContext
+ .HttpContext ?? throw new InvalidOperationException($"Unable to resolve {nameof(IAuthenticationContext)} due to no HttpContext being available!"))
.RequestServices
.GetRequiredService()
.CurrentAuthenticationContext);
diff --git a/src/Tgstation.Server.Host/Core/CommandPipeManager.cs b/src/Tgstation.Server.Host/Core/CommandPipeManager.cs
index d86e8ed69c..6a5c6325c6 100644
--- a/src/Tgstation.Server.Host/Core/CommandPipeManager.cs
+++ b/src/Tgstation.Server.Host/Core/CommandPipeManager.cs
@@ -65,22 +65,24 @@ namespace Tgstation.Server.Host.Core
logger.LogTrace("Starting...");
// grab both pipes asap so we can close them on error
- var supportsPipeCommands = !String.IsNullOrWhiteSpace(internalConfiguration.CommandPipe);
+ var commandPipe = internalConfiguration.CommandPipe;
+ var supportsPipeCommands = !String.IsNullOrWhiteSpace(commandPipe);
await using var commandPipeClient = supportsPipeCommands
? new AnonymousPipeClientStream(
PipeDirection.In,
- internalConfiguration.CommandPipe)
+ commandPipe!)
: null;
if (!supportsPipeCommands)
logger.LogDebug("No command pipe name specified in configuration");
- var supportsReadyNotification = !String.IsNullOrWhiteSpace(internalConfiguration.ReadyPipe);
+ var readyPipe = internalConfiguration.ReadyPipe;
+ var supportsReadyNotification = !String.IsNullOrWhiteSpace(readyPipe);
if (supportsReadyNotification)
{
await using var readyPipeClient = new AnonymousPipeClientStream(
PipeDirection.Out,
- internalConfiguration.ReadyPipe);
+ readyPipe!);
logger.LogTrace("Waiting to send ready notification...");
await instanceManager.Ready.WaitAsync(cancellationToken);
@@ -96,13 +98,13 @@ namespace Tgstation.Server.Host.Core
try
{
- using var streamReader = new StreamReader(commandPipeClient, Encoding.UTF8, leaveOpen: true);
+ using var streamReader = new StreamReader(commandPipeClient!, Encoding.UTF8, leaveOpen: true);
while (!cancellationToken.IsCancellationRequested)
{
logger.LogTrace("Waiting to read command line...");
var line = await streamReader.ReadLineAsync(cancellationToken);
- logger?.LogInformation("Received pipe command: {command}", line);
+ logger.LogInformation("Received pipe command: {command}", line);
switch (line)
{
case PipeCommands.CommandStop:
@@ -118,22 +120,22 @@ namespace Tgstation.Server.Host.Core
logger.LogError("Read null from pipe!");
return;
default:
- logger?.LogWarning("Unrecognized pipe command: {command}", line);
+ logger.LogWarning("Unrecognized pipe command: {command}", line);
break;
}
}
}
catch (OperationCanceledException ex)
{
- logger?.LogTrace(ex, "Command read task cancelled!");
+ logger.LogTrace(ex, "Command read task cancelled!");
}
catch (Exception ex)
{
- logger?.LogError(ex, "Command read task errored!");
+ logger.LogError(ex, "Command read task errored!");
}
finally
{
- logger?.LogTrace("Command read task exiting...");
+ logger.LogTrace("Command read task exiting...");
}
}
}
diff --git a/src/Tgstation.Server.Host/Core/IRestartHandler.cs b/src/Tgstation.Server.Host/Core/IRestartHandler.cs
index e1c8a771ef..b663ae5d22 100644
--- a/src/Tgstation.Server.Host/Core/IRestartHandler.cs
+++ b/src/Tgstation.Server.Host/Core/IRestartHandler.cs
@@ -16,6 +16,6 @@ namespace Tgstation.Server.Host.Core
/// If the should aim to complete the returned from this function ASAP.
/// The for the operation.
/// A representing the running operation.
- ValueTask HandleRestart(Version updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken);
+ ValueTask HandleRestart(Version? updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Core/IServerControl.cs b/src/Tgstation.Server.Host/Core/IServerControl.cs
index d864080db6..92bbfe75f5 100644
--- a/src/Tgstation.Server.Host/Core/IServerControl.cs
+++ b/src/Tgstation.Server.Host/Core/IServerControl.cs
@@ -51,6 +51,6 @@ namespace Tgstation.Server.Host.Core
///
/// The to propagate to the watchdog if any.
/// A representing the running operation.
- ValueTask Die(Exception exception);
+ ValueTask Die(Exception? exception);
}
}
diff --git a/src/Tgstation.Server.Host/Core/IServerUpdateInitiator.cs b/src/Tgstation.Server.Host/Core/IServerUpdateInitiator.cs
index 8b290bdcd2..35aaa4037b 100644
--- a/src/Tgstation.Server.Host/Core/IServerUpdateInitiator.cs
+++ b/src/Tgstation.Server.Host/Core/IServerUpdateInitiator.cs
@@ -18,6 +18,6 @@ namespace Tgstation.Server.Host.Core
/// The TGS to update to.
/// The for the operation.
/// A resulting in the .
- ValueTask InitiateUpdate(IFileStreamProvider fileStreamProvider, Version version, CancellationToken cancellationToken);
+ ValueTask InitiateUpdate(IFileStreamProvider? fileStreamProvider, Version version, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Core/IServerUpdater.cs b/src/Tgstation.Server.Host/Core/IServerUpdater.cs
index 6485ed4713..4a7a7c0519 100644
--- a/src/Tgstation.Server.Host/Core/IServerUpdater.cs
+++ b/src/Tgstation.Server.Host/Core/IServerUpdater.cs
@@ -20,6 +20,6 @@ namespace Tgstation.Server.Host.Core
/// The TGS to update to.
/// The for the operation.
/// A resulting in the .
- ValueTask BeginUpdate(ISwarmService swarmService, IFileStreamProvider fileStreamProvider, Version version, CancellationToken cancellationToken);
+ ValueTask BeginUpdate(ISwarmService swarmService, IFileStreamProvider? fileStreamProvider, Version version, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Core/RestartRegistration.cs b/src/Tgstation.Server.Host/Core/RestartRegistration.cs
index 025c274e07..2cd24e307d 100644
--- a/src/Tgstation.Server.Host/Core/RestartRegistration.cs
+++ b/src/Tgstation.Server.Host/Core/RestartRegistration.cs
@@ -1,4 +1,4 @@
-using System;
+using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Core
{
@@ -6,20 +6,20 @@ namespace Tgstation.Server.Host.Core
sealed class RestartRegistration : IRestartRegistration
{
///
- /// The .
+ /// The .
///
- readonly Action onDispose;
+ readonly DisposeInvoker? disposeInvoker;
///
/// Initializes a new instance of the class.
///
- /// The value of .
- public RestartRegistration(Action onDispose)
+ /// The value of .
+ public RestartRegistration(DisposeInvoker? disposeInvoker)
{
- this.onDispose = onDispose;
+ this.disposeInvoker = disposeInvoker;
}
///
- public void Dispose() => onDispose?.Invoke();
+ public void Dispose() => disposeInvoker?.Dispose();
}
}
diff --git a/src/Tgstation.Server.Host/Core/ServerPortProivder.cs b/src/Tgstation.Server.Host/Core/ServerPortProivder.cs
index e4c4927c53..a3f39a4ab8 100644
--- a/src/Tgstation.Server.Host/Core/ServerPortProivder.cs
+++ b/src/Tgstation.Server.Host/Core/ServerPortProivder.cs
@@ -34,18 +34,17 @@ namespace Tgstation.Server.Host.Core
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
ArgumentNullException.ThrowIfNull(configuration);
+ var usingDefaultPort = generalConfiguration.ApiPort == default;
+ if (!usingDefaultPort)
+ return;
+
var httpEndpoint = configuration
.GetSection("Kestrel")
.GetSection("EndPoints")
.GetSection("Http")
.GetSection("Url")
- .Value;
-
- if (generalConfiguration.ApiPort == default && httpEndpoint == null)
- throw new InvalidOperationException("Missing required configuration option General:ApiPort!");
-
- if (generalConfiguration.ApiPort != default)
- return;
+ .Value
+ ?? throw new InvalidOperationException("Missing required configuration option General:ApiPort!");
logger.LogWarning("The \"Kestrel\" configuration section is deprecated! Please set your API port using the \"General:ApiPort\" configuration option!");
diff --git a/src/Tgstation.Server.Host/Core/ServerUpdateInitiator.cs b/src/Tgstation.Server.Host/Core/ServerUpdateInitiator.cs
index f7fad73122..fc1dc084cc 100644
--- a/src/Tgstation.Server.Host/Core/ServerUpdateInitiator.cs
+++ b/src/Tgstation.Server.Host/Core/ServerUpdateInitiator.cs
@@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Core
}
///
- public ValueTask InitiateUpdate(IFileStreamProvider fileStreamProvider, Version version, CancellationToken cancellationToken)
+ public ValueTask InitiateUpdate(IFileStreamProvider? fileStreamProvider, Version version, CancellationToken cancellationToken)
=> serverUpdater.BeginUpdate(swarmService, fileStreamProvider, version, cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Core/ServerUpdater.cs b/src/Tgstation.Server.Host/Core/ServerUpdater.cs
index aacf44fd19..8c9d968168 100644
--- a/src/Tgstation.Server.Host/Core/ServerUpdater.cs
+++ b/src/Tgstation.Server.Host/Core/ServerUpdater.cs
@@ -59,7 +59,7 @@ namespace Tgstation.Server.Host.Core
///
/// for an in-progress update operation.
///
- ServerUpdateOperation serverUpdateOperation;
+ ServerUpdateOperation? serverUpdateOperation;
///
/// Initializes a new instance of the class.
@@ -92,7 +92,7 @@ namespace Tgstation.Server.Host.Core
}
///
- public async ValueTask BeginUpdate(ISwarmService swarmService, IFileStreamProvider fileStreamProvider, Version version, CancellationToken cancellationToken)
+ public async ValueTask BeginUpdate(ISwarmService swarmService, IFileStreamProvider? fileStreamProvider, Version version, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(swarmService);
@@ -201,7 +201,7 @@ namespace Tgstation.Server.Host.Core
{
try
{
- await serverUpdateOperation.SwarmService.AbortUpdate();
+ await serverUpdateOperation!.SwarmService.AbortUpdate();
}
catch (Exception e2)
{
@@ -214,11 +214,11 @@ namespace Tgstation.Server.Host.Core
///
/// The directory the server update is initially extracted to.
/// The for the operation.
- /// A resulting in containing a new based on the of and if it needs to be kept active until the swarm commit.
+ /// A resulting in containing a new based on the of and if it needs to be kept active until the swarm commit. If , the update failed to prepare.
/// Requires to be populated.
- async ValueTask> PrepareUpdateClearStagingAndBufferStream(string stagingDirectory, CancellationToken cancellationToken)
+ async ValueTask?> PrepareUpdateClearStagingAndBufferStream(string stagingDirectory, CancellationToken cancellationToken)
{
- await using var fileStreamProvider = serverUpdateOperation.FileStreamProvider;
+ await using var fileStreamProvider = serverUpdateOperation!.FileStreamProvider;
var bufferedStream = new BufferedFileStreamProvider(
await fileStreamProvider.GetResult(cancellationToken));
@@ -276,12 +276,12 @@ namespace Tgstation.Server.Host.Core
/// A resulting in the .
async ValueTask BeginUpdateImpl(
ISwarmService swarmService,
- IFileStreamProvider fileStreamProvider,
+ IFileStreamProvider? fileStreamProvider,
Version newVersion,
bool recursed,
CancellationToken cancellationToken)
{
- ServerUpdateOperation ourUpdateOperation = null;
+ ServerUpdateOperation? ourUpdateOperation = null;
try
{
if (fileStreamProvider == null)
diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs
index 3dd14560f6..978a73076b 100644
--- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs
+++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs
@@ -257,21 +257,21 @@ namespace Tgstation.Server.Host.Database
protected DatabaseContext(DbContextOptions dbContextOptions)
: base(dbContextOptions)
{
- usersCollection = new DatabaseCollection(Users);
- instancesCollection = new DatabaseCollection(Instances);
- instancePermissionSetsCollection = new DatabaseCollection(InstancePermissionSets);
- compileJobsCollection = new DatabaseCollection(CompileJobs);
- repositorySettingsCollection = new DatabaseCollection(RepositorySettings);
- dreamMakerSettingsCollection = new DatabaseCollection(DreamMakerSettings);
- dreamDaemonSettingsCollection = new DatabaseCollection(DreamDaemonSettings);
- chatBotsCollection = new DatabaseCollection(ChatBots);
- chatChannelsCollection = new DatabaseCollection(ChatChannels);
- revisionInformationsCollection = new DatabaseCollection(RevisionInformations);
- jobsCollection = new DatabaseCollection(Jobs);
- reattachInformationsCollection = new DatabaseCollection(ReattachInformations);
- oAuthConnections = new DatabaseCollection(OAuthConnections);
- groups = new DatabaseCollection(Groups);
- permissionSets = new DatabaseCollection(PermissionSets);
+ usersCollection = new DatabaseCollection(Users!);
+ instancesCollection = new DatabaseCollection(Instances!);
+ instancePermissionSetsCollection = new DatabaseCollection(InstancePermissionSets!);
+ compileJobsCollection = new DatabaseCollection(CompileJobs!);
+ repositorySettingsCollection = new DatabaseCollection