mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-07-15 18:12:50 +01:00
Chungus refactors
- Added a new ErrorCode for when chat bots fail to connect - Made IProvider IAsyncDispsable - Removed IDatabaseContextFactory dependency from watchdogs - Jobs will no longer start until instance manager startup is complete - Chat provider connection is now done as a job, there is no way to retrieve directly this job through the API currently - Fixed disabled chat bots connecting when their instance onlined - Fixed issue with chat provider reconnection channel remapping - IJobManager can now accept a null user in most cases which it will substitute for the TGS user - Fixed some watchdog issues relating to accessing disposed session controllers
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
@@ -557,6 +557,12 @@ namespace Tgstation.Server.Api.Models
|
||||
/// Attempted to perform an instance operation with an offline instance.
|
||||
/// </summary>
|
||||
[Description("The instance associated with the operation is currently offline!")]
|
||||
InstanceOffline
|
||||
InstanceOffline,
|
||||
|
||||
/// <summary>
|
||||
/// An attempt to connect a chat bot failed.
|
||||
/// </summary>
|
||||
[Description("Failed to connect chat bot!")]
|
||||
ChatCannotConnectProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
@@ -117,11 +117,6 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// </summary>
|
||||
long messagesProcessed;
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="StartAsync(CancellationToken)"/> has been called
|
||||
/// </summary>
|
||||
bool started;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ChatManager"/>
|
||||
/// </summary>
|
||||
@@ -159,13 +154,13 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
logger.LogTrace("Disposing...");
|
||||
restartRegistration.Dispose();
|
||||
handlerCts.Dispose();
|
||||
foreach (var I in providers)
|
||||
I.Value.Dispose();
|
||||
await I.Value.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -206,6 +201,21 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
return provider;
|
||||
}
|
||||
|
||||
async Task RemapProvider(IProvider provider, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Remapping channels for provider reconnection...");
|
||||
IEnumerable<Api.Models.ChatChannel> channelsToMap;
|
||||
long providerId;
|
||||
lock (providers)
|
||||
providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
|
||||
|
||||
lock (activeChatBots)
|
||||
channelsToMap = activeChatBots.FirstOrDefault(x => x.Id == providerId)?.Channels;
|
||||
|
||||
if (channelsToMap?.Any() ?? false)
|
||||
await ChangeChannels(providerId, channelsToMap, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a <paramref name="message"/>
|
||||
/// </summary>
|
||||
@@ -213,26 +223,20 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <param name="message">The <see cref="Message"/> to process. If <see langword="null"/>, this indicates the provider reconnected.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
#pragma warning disable CA1502
|
||||
#pragma warning disable CA1502
|
||||
async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken)
|
||||
#pragma warning restore CA1502
|
||||
#pragma warning restore CA1502
|
||||
{
|
||||
if (!provider.Connected)
|
||||
{
|
||||
logger.LogTrace("Abort message processing because provider is disconnected!");
|
||||
return;
|
||||
}
|
||||
|
||||
// provider reconnected, remap channels.
|
||||
if (message == null)
|
||||
{
|
||||
logger.LogTrace("Remapping channels for provider reconnection...");
|
||||
IEnumerable<Api.Models.ChatChannel> channelsToMap;
|
||||
lock (activeChatBots)
|
||||
channelsToMap = activeChatBots.FirstOrDefault()?.Channels;
|
||||
|
||||
if (channelsToMap?.Any() ?? false)
|
||||
{
|
||||
long providerId;
|
||||
lock (providers)
|
||||
providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
|
||||
await ChangeChannels(providerId, channelsToMap, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await RemapProvider(provider, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -243,9 +247,6 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
var enumerable = mappedChannels.Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == message.User.Channel.RealId);
|
||||
if (message.User.Channel.IsPrivateChannel)
|
||||
lock (mappedChannels)
|
||||
{
|
||||
if (!provider.Connected)
|
||||
return;
|
||||
if (!enumerable.Any())
|
||||
{
|
||||
ulong newId;
|
||||
@@ -267,7 +268,6 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
else
|
||||
message.User.Channel.RealId = enumerable.First().Key;
|
||||
}
|
||||
else
|
||||
{
|
||||
// need to add tag and isAdminChannel
|
||||
@@ -285,7 +285,9 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
address = address.ToUpperInvariant();
|
||||
|
||||
var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant();
|
||||
var addressed =
|
||||
address == CommonMention.ToUpperInvariant()
|
||||
|| address == provider.BotMention.ToUpperInvariant();
|
||||
|
||||
// no mention
|
||||
if (!addressed && !message.User.Channel.IsPrivateChannel)
|
||||
@@ -406,7 +408,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// prune disconnected providers
|
||||
foreach (var I in messageTasks.Where(x => !x.Key.Connected).ToList())
|
||||
foreach (var I in messageTasks.Where(x => !x.Key.Disposed).ToList())
|
||||
messageTasks.Remove(I.Key);
|
||||
|
||||
// add new ones
|
||||
@@ -415,7 +417,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
updatedTask = connectionsUpdated.Task;
|
||||
lock (providers)
|
||||
foreach (var I in providers)
|
||||
if (I.Value.Connected && !messageTasks.ContainsKey(I.Value))
|
||||
if (!messageTasks.ContainsKey(I.Value))
|
||||
messageTasks.Add(I.Value, I.Value.NextMessage(cancellationToken));
|
||||
|
||||
if (messageTasks.Count == 0)
|
||||
@@ -521,7 +523,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ChangeSettings(ChatBot newSettings, CancellationToken cancellationToken)
|
||||
public async Task ChangeSettings(Models.ChatBot newSettings, CancellationToken cancellationToken)
|
||||
{
|
||||
if (newSettings == null)
|
||||
throw new ArgumentNullException(nameof(newSettings));
|
||||
@@ -537,7 +539,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
finally
|
||||
{
|
||||
p.Dispose();
|
||||
await p.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,30 +567,23 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
await disconnectTask.ConfigureAwait(false);
|
||||
|
||||
if (started)
|
||||
lock (synchronizationLock)
|
||||
{
|
||||
if (newSettings.Enabled.Value)
|
||||
await provider.Connect(cancellationToken).ConfigureAwait(false);
|
||||
lock (synchronizationLock)
|
||||
{
|
||||
// same thread shennanigans
|
||||
var oldOne = connectionsUpdated;
|
||||
connectionsUpdated = new TaskCompletionSource<object>();
|
||||
oldOne.SetResult(null);
|
||||
}
|
||||
// same thread shennanigans
|
||||
var oldOne = connectionsUpdated;
|
||||
connectionsUpdated = new TaskCompletionSource<object>();
|
||||
oldOne.SetResult(null);
|
||||
}
|
||||
|
||||
Task reconnectionUpdateTask = Task.CompletedTask;
|
||||
var reconnectionUpdateTask = provider?.SetReconnectInterval(
|
||||
newSettings.ReconnectionInterval.Value,
|
||||
newSettings.Enabled.Value)
|
||||
?? Task.CompletedTask;
|
||||
lock (activeChatBots)
|
||||
{
|
||||
var originalChatBot = activeChatBots.FirstOrDefault(bot => bot.Id == newSettings.Id);
|
||||
if (originalChatBot != null)
|
||||
{
|
||||
if (originalChatBot.ReconnectionInterval != newSettings.ReconnectionInterval)
|
||||
reconnectionUpdateTask = provider.SetReconnectInterval(newSettings.ReconnectionInterval.Value);
|
||||
|
||||
activeChatBots.Remove(originalChatBot);
|
||||
}
|
||||
|
||||
activeChatBots.Add(new Models.ChatBot
|
||||
{
|
||||
@@ -716,10 +711,8 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
builtinCommands.Add(I.Name.ToUpperInvariant(), I);
|
||||
var initialChatBots = activeChatBots.ToList();
|
||||
await Task.WhenAll(initialChatBots.Select(x => ChangeSettings(x, cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(initialChatBots.Select(x => ChangeChannels(x.Id, x.Channels, cancellationToken))).ConfigureAwait(false);
|
||||
chatHandler = MonitorMessages(handlerCts.Token);
|
||||
started = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -774,7 +767,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
finally
|
||||
{
|
||||
provider.Dispose();
|
||||
await provider.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Tgstation.Server.Host.Components.Chat.Commands;
|
||||
using Tgstation.Server.Host.Components.Chat.Providers;
|
||||
using Tgstation.Server.Host.Core;
|
||||
@@ -38,7 +39,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
public ChatManagerFactory(IProviderFactory providerFactory, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory)
|
||||
public ChatManagerFactory(
|
||||
IProviderFactory providerFactory,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
@@ -47,6 +52,18 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChatManager CreateChatManager(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable<Models.ChatBot> initialChatBots) => new ChatManager(providerFactory, ioManager, commandFactory, serverControl, asyncDelayer, loggerFactory, loggerFactory.CreateLogger<ChatManager>(), initialChatBots);
|
||||
public IChatManager CreateChatManager(
|
||||
IIOManager ioManager,
|
||||
ICommandFactory commandFactory,
|
||||
IEnumerable<Models.ChatBot> initialChatBots)
|
||||
=> new ChatManager(
|
||||
providerFactory,
|
||||
ioManager,
|
||||
commandFactory,
|
||||
serverControl,
|
||||
asyncDelayer,
|
||||
loggerFactory,
|
||||
loggerFactory.CreateLogger<ChatManager>(),
|
||||
initialChatBots.Where(x => x.Enabled.Value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <summary>
|
||||
/// For managing connected chat services
|
||||
/// </summary>
|
||||
public interface IChatManager : IHostedService, IDisposable
|
||||
public interface IChatManager : IHostedService, IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a <paramref name="customCommandHandler"/> to use
|
||||
@@ -21,10 +21,10 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <summary>
|
||||
/// Change chat settings. If the <see cref="ChatBot.Id"/> is not currently in use, a new connection will be made instead
|
||||
/// </summary>
|
||||
/// <param name="newSettings">The new <see cref="ChatBot"/></param>
|
||||
/// <param name="newSettings">The new <see cref="Models.ChatBot"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation. Will complete immediately if the <see cref="ChatBot.Enabled"/> property of <paramref name="newSettings"/> is <see langword="false"/></returns>
|
||||
Task ChangeSettings(ChatBot newSettings, CancellationToken cancellationToken);
|
||||
Task ChangeSettings(Models.ChatBot newSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects and deletes a given connection
|
||||
@@ -87,4 +87,4 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <returns>A new <see cref="IChatTrackingContext"/>.</returns>
|
||||
IChatTrackingContext CreateTrackingContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Discord;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
@@ -6,6 +6,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
@@ -30,6 +32,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Discord bot token.
|
||||
/// </summary>
|
||||
string BotToken => ChatBot.ConnectionString;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="DiscordProvider"/>.
|
||||
/// </summary>
|
||||
@@ -40,11 +47,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// </summary>
|
||||
readonly DiscordSocketClient client;
|
||||
|
||||
/// <summary>
|
||||
/// The token used for connecting to discord
|
||||
/// </summary>
|
||||
readonly string botToken;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="List{T}"/> of mapped <see cref="ITextChannel"/> <see cref="IEntity{TId}.Id"/>s
|
||||
/// </summary>
|
||||
@@ -60,30 +62,28 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DiscordProvider"/>
|
||||
/// </summary>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="Provider"/>.</param>
|
||||
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="Logger"/></param>
|
||||
/// <param name="botToken">The value of <see cref="botToken"/></param>
|
||||
/// <param name="reconnectInterval">The initial reconnect interval in minutes.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="Provider"/>.</param>
|
||||
/// <param name="chatBot">The <see cref="ChatBot"/> for the <see cref="Provider"/>.</param>
|
||||
public DiscordProvider(
|
||||
IJobManager jobManager,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
ILogger<DiscordProvider> logger,
|
||||
string botToken,
|
||||
uint reconnectInterval)
|
||||
: base(logger, reconnectInterval)
|
||||
Models.ChatBot chatBot)
|
||||
: base(jobManager, logger, chatBot)
|
||||
{
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
this.botToken = botToken ?? throw new ArgumentNullException(nameof(botToken));
|
||||
client = new DiscordSocketClient();
|
||||
client.MessageReceived += Client_MessageReceived;
|
||||
mappedChannels = new List<ulong>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dispose()
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
await base.DisposeAsync().ConfigureAwait(false);
|
||||
client.Dispose();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,18 +135,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<bool> Connect(CancellationToken cancellationToken)
|
||||
protected override async Task Connect(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("Connecting...");
|
||||
if (Connected)
|
||||
{
|
||||
Logger.LogTrace("Already connected not doing connection attempt!");
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await client.LoginAsync(TokenType.Bot, botToken, true).ConfigureAwait(false);
|
||||
await client.LoginAsync(TokenType.Bot, BotToken, true).ConfigureAwait(false);
|
||||
|
||||
Logger.LogTrace("Logged in.");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -171,11 +165,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogWarning("Error connecting to Discord: {0}", e);
|
||||
return false;
|
||||
throw new JobException(ErrorCode.ChatCannotConnectProvider, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -190,9 +181,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await client.StopAsync().ConfigureAwait(false);
|
||||
Logger.LogTrace("Stopped.");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await client.LogoutAsync().ConfigureAwait(false);
|
||||
Logger.LogDebug("Disconnected!");
|
||||
}
|
||||
@@ -278,7 +269,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Func<string, string, Task>> SendUpdateMessage(
|
||||
RevisionInformation revisionInformation,
|
||||
Models.RevisionInformation revisionInformation,
|
||||
Version byondVersion,
|
||||
DateTimeOffset? estimatedCompletionTime,
|
||||
string gitHubOwner,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -9,13 +9,18 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <summary>
|
||||
/// For interacting with a chat service
|
||||
/// </summary>
|
||||
interface IProvider : IDisposable
|
||||
interface IProvider : IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// If the <see cref="IProvider"/>
|
||||
/// If the <see cref="IProvider"/> is currently connected.
|
||||
/// </summary>
|
||||
bool Connected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="IProvider"/> was disposed.
|
||||
/// </summary>
|
||||
bool Disposed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="string"/> that indicates the <see cref="IProvider"/> was mentioned
|
||||
/// </summary>
|
||||
@@ -29,13 +34,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <remarks>Note that private messages will come in the form of <see cref="ChannelRepresentation"/>s not returned in <see cref="MapChannels(IEnumerable{Api.Models.ChatChannel}, CancellationToken)"/>. Do not <see cref="IDisposable.Dispose"/> the <see cref="IProvider"/> on continuations run from the returned <see cref="Task"/>.</remarks>
|
||||
Task<Message> NextMessage(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to connect the <see cref="IProvider"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> on success, <see langword="false"/> otherwise</returns>
|
||||
Task<bool> Connect(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gracefully disconnects the provider. Permanently stops the reconnection timer.
|
||||
/// </summary>
|
||||
@@ -61,11 +59,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Set the interval at which the provider tries to reconnect.
|
||||
/// Set the interval at which the provider starts jobs to try to reconnect.
|
||||
/// </summary>
|
||||
/// <param name="reconnectInterval">The reconnection interval in minutes.</param>
|
||||
/// <param name="connectNow">If a connection attempt should be made now.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task SetReconnectInterval(uint reconnectInterval);
|
||||
Task SetReconnectInterval(uint reconnectInterval, bool connectNow);
|
||||
|
||||
/// <summary>
|
||||
/// Send the message for a deployment.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Meebey.SmartIrc4net;
|
||||
using Meebey.SmartIrc4net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
@@ -90,45 +91,33 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <summary>
|
||||
/// Construct an <see cref="IrcProvider"/>
|
||||
/// </summary>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the provider.</param>
|
||||
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> to get the <see cref="IAssemblyInformationProvider.VersionString"/> from</param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
|
||||
/// <param name="logger">The value of logger</param>
|
||||
/// <param name="address">The value of <see cref="address"/></param>
|
||||
/// <param name="port">The value of <see cref="port"/></param>
|
||||
/// <param name="nickname">The value of <see cref="nickname"/></param>
|
||||
/// <param name="password">The value of <see cref="password"/></param>
|
||||
/// <param name="passwordType">The value of <see cref="passwordType"/></param>
|
||||
/// <param name="reconnectInterval">The initial reconnect interval in minutes.</param>
|
||||
/// <param name="useSsl">If <see cref="IrcConnection.UseSsl"/> should be used</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="Provider"/>.</param>
|
||||
/// <param name="chatBot">The <see cref="Models.ChatBot"/> for the <see cref="Provider"/>.</param>
|
||||
public IrcProvider(
|
||||
IJobManager jobManager,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
ILogger<IrcProvider> logger,
|
||||
string address,
|
||||
ushort port,
|
||||
string nickname,
|
||||
string password,
|
||||
IrcPasswordType? passwordType,
|
||||
uint reconnectInterval,
|
||||
bool useSsl)
|
||||
: base(logger, reconnectInterval)
|
||||
Models.ChatBot chatBot)
|
||||
: base(jobManager, logger, chatBot)
|
||||
{
|
||||
if (assemblyInformationProvider == null)
|
||||
throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
|
||||
this.address = address ?? throw new ArgumentNullException(nameof(address));
|
||||
this.port = port;
|
||||
this.nickname = nickname ?? throw new ArgumentNullException(nameof(nickname));
|
||||
var builder = chatBot.CreateConnectionStringBuilder();
|
||||
if (builder == null || !builder.Valid || !(builder is IrcConnectionStringBuilder ircBuilder))
|
||||
throw new InvalidOperationException("Invalid ChatConnectionStringBuilder!");
|
||||
|
||||
if (passwordType.HasValue && password == null)
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
address = ircBuilder.Address;
|
||||
port = ircBuilder.Port.Value;
|
||||
nickname = ircBuilder.Nickname;
|
||||
|
||||
if (password != null && !passwordType.HasValue)
|
||||
throw new ArgumentNullException(nameof(passwordType));
|
||||
|
||||
this.password = password;
|
||||
this.passwordType = passwordType;
|
||||
password = ircBuilder.Password;
|
||||
passwordType = ircBuilder.PasswordType.Value;
|
||||
|
||||
client = new IrcFeatures
|
||||
{
|
||||
@@ -143,9 +132,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
ActiveChannelSyncing = true,
|
||||
AutoNickHandling = true,
|
||||
CtcpVersion = assemblyInformationProvider.VersionString,
|
||||
UseSsl = useSsl
|
||||
UseSsl = ircBuilder.UseSsl.Value
|
||||
};
|
||||
if (useSsl)
|
||||
if (ircBuilder.UseSsl.Value)
|
||||
client.ValidateServerCertificate = true; // dunno if it defaults to that or what
|
||||
|
||||
client.OnChannelMessage += Client_OnChannelMessage;
|
||||
@@ -154,19 +143,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
channelIdMap = new Dictionary<ulong, string>();
|
||||
queryChannelIdMap = new Dictionary<ulong, string>();
|
||||
channelIdCounter = 1;
|
||||
disconnecting = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dispose()
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
if (Connected)
|
||||
{
|
||||
disconnecting = true;
|
||||
client.Disconnect(); // just closes the socket
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
await base.DisposeAsync().ConfigureAwait(false);
|
||||
await HardDisconnect().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -247,7 +230,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
void Client_OnChannelMessage(object sender, IrcEventArgs e) => HandleMessage(e, false);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<bool> Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
protected override Task Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
disconnecting = false;
|
||||
lock (client)
|
||||
@@ -338,11 +321,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogWarning("Unable to connect to IRC: {0}", e);
|
||||
return false;
|
||||
throw new JobException(ErrorCode.ChatCannotConnectProvider, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -363,8 +343,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Logger.LogWarning("Error quitting IRC: {0}", e);
|
||||
}
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
Dispose();
|
||||
await listenTask.ConfigureAwait(false);
|
||||
await HardDisconnect().ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -376,6 +355,16 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
}
|
||||
|
||||
async Task HardDisconnect()
|
||||
{
|
||||
if (!Connected)
|
||||
return;
|
||||
|
||||
disconnecting = true;
|
||||
client.Disconnect();
|
||||
await listenTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IReadOnlyCollection<ChannelRepresentation>> MapChannels(
|
||||
IEnumerable<ChatChannel> channels,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
@@ -10,10 +12,20 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <inheritdoc />
|
||||
abstract class Provider : IProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ChatBot"/> the <see cref="Provider"/> is for.
|
||||
/// </summary>
|
||||
protected ChatBot ChatBot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="Provider"/>.
|
||||
/// </summary>
|
||||
protected ILogger Logger { get; }
|
||||
protected ILogger<Provider> Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="Provider"/>.
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Queue{T}"/> of received <see cref="Message"/>s
|
||||
@@ -43,18 +55,20 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <summary>
|
||||
/// Construct a <see cref="Provider"/>
|
||||
/// </summary>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="Logger"/>.</param>
|
||||
/// <param name="reconnectInterval">The initial reconnection interval.</param>
|
||||
protected Provider(ILogger logger, uint reconnectInterval)
|
||||
/// <param name="chatBot">The value of <paramref name="chatBot"/>.</param>
|
||||
protected Provider(IJobManager jobManager, ILogger<Provider> logger, ChatBot chatBot)
|
||||
{
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
ChatBot = chatBot ?? throw new ArgumentNullException(nameof(chatBot));
|
||||
|
||||
messageQueue = new Queue<Message>();
|
||||
nextMessage = new TaskCompletionSource<object>();
|
||||
|
||||
reconnectTaskLock = new object();
|
||||
|
||||
SetReconnectInterval(reconnectInterval).GetAwaiter().GetResult();
|
||||
logger.LogTrace("Created.");
|
||||
}
|
||||
|
||||
@@ -64,6 +78,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <inheritdoc />
|
||||
public abstract string BotMention { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Queues a <paramref name="message"/> for <see cref="NextMessage(CancellationToken)"/>
|
||||
/// </summary>
|
||||
@@ -78,14 +95,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Dispose()
|
||||
public virtual async ValueTask DisposeAsync()
|
||||
{
|
||||
StopReconnectionTimer().GetAwaiter().GetResult();
|
||||
Disposed = true;
|
||||
await StopReconnectionTimer().ConfigureAwait(false);
|
||||
Logger.LogTrace("Disposed");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task<bool> Connect(CancellationToken cancellationToken);
|
||||
/// <summary>
|
||||
/// Attempt to connect the <see cref="Provider"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected abstract Task Connect(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gracefully disconnects the provider.
|
||||
@@ -97,8 +119,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <inheritdoc />
|
||||
public async Task Disconnect(CancellationToken cancellationToken)
|
||||
{
|
||||
if(Connected)
|
||||
await DisconnectImpl(cancellationToken).ConfigureAwait(false);
|
||||
await StopReconnectionTimer().ConfigureAwait(false);
|
||||
await DisconnectImpl(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -141,7 +164,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SetReconnectInterval(uint reconnectInterval)
|
||||
public Task SetReconnectInterval(uint reconnectInterval, bool connectNow)
|
||||
{
|
||||
if (reconnectInterval == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(reconnectInterval), reconnectInterval, "Reconnect interval cannot be zero!");
|
||||
@@ -151,7 +174,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
stopOldTimerTask = StopReconnectionTimer();
|
||||
reconnectCts = new CancellationTokenSource();
|
||||
reconnectTask = ReconnectionLoop(reconnectInterval, reconnectCts.Token);
|
||||
reconnectTask = ReconnectionLoop(reconnectInterval, connectNow, reconnectCts.Token);
|
||||
}
|
||||
|
||||
return stopOldTimerTask;
|
||||
@@ -161,21 +184,42 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// Creates a <see cref="Task"/> that will attempt to reconnect the <see cref="Provider"/> every <paramref name="reconnectInterval"/> minutes.
|
||||
/// </summary>
|
||||
/// <param name="reconnectInterval">The amount of minutes to wait between reconnection attempts.</param>
|
||||
/// <param name="connectNow">If a connection attempt should be immediately made.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task ReconnectionLoop(uint reconnectInterval, CancellationToken cancellationToken)
|
||||
async Task ReconnectionLoop(uint reconnectInterval, bool connectNow, CancellationToken cancellationToken)
|
||||
{
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(reconnectInterval), cancellationToken).ConfigureAwait(false);
|
||||
if (!connectNow)
|
||||
await Task.Delay(TimeSpan.FromMinutes(reconnectInterval), cancellationToken).ConfigureAwait(false);
|
||||
else
|
||||
connectNow = false;
|
||||
if (!Connected)
|
||||
{
|
||||
Logger.LogInformation("Attempting to reconnect provider...");
|
||||
await Disconnect(cancellationToken).ConfigureAwait(false);
|
||||
if (await Connect(cancellationToken).ConfigureAwait(false))
|
||||
EnqueueMessage(null);
|
||||
var job = new Job
|
||||
{
|
||||
Description = $"Reconnect chat bot: {ChatBot.Name}",
|
||||
CancelRight = (ulong)ChatBotRights.WriteEnabled,
|
||||
CancelRightsType = RightsType.ChatBots,
|
||||
Instance = ChatBot.Instance
|
||||
};
|
||||
|
||||
await jobManager.RegisterOperation(
|
||||
job,
|
||||
async (core, databaseContextFactory, paramJob, progressReporter, jobCancellationToken) =>
|
||||
{
|
||||
await DisconnectImpl(jobCancellationToken).ConfigureAwait(false);
|
||||
await Connect(jobCancellationToken).ConfigureAwait(false);
|
||||
EnqueueMessage(null);
|
||||
},
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// DCT: Always wait for the job to complete here
|
||||
await jobManager.WaitForJobCompletion(job, null, cancellationToken, default).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
@@ -20,6 +21,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// </summary>
|
||||
readonly IAsyncDelayer asyncDelayer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="ProviderFactory"/>.
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> for the <see cref="ProviderFactory"/>
|
||||
/// </summary>
|
||||
@@ -28,42 +34,42 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ProviderFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/>.</param>
|
||||
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/></param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
public ProviderFactory(
|
||||
IJobManager jobManager,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IProvider CreateProvider(Api.Models.Internal.ChatBot settings)
|
||||
public IProvider CreateProvider(Models.ChatBot settings)
|
||||
{
|
||||
if (settings == null)
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
var builder = settings.CreateConnectionStringBuilder();
|
||||
if (builder == null || !builder.Valid)
|
||||
throw new InvalidOperationException("Invalid ChatConnectionStringBuilder!");
|
||||
switch (settings.Provider)
|
||||
return settings.Provider switch
|
||||
{
|
||||
case ChatProvider.Irc:
|
||||
var ircBuilder = (IrcConnectionStringBuilder)builder;
|
||||
return new IrcProvider(assemblyInformationProvider, asyncDelayer, loggerFactory.CreateLogger<IrcProvider>(), ircBuilder.Address, ircBuilder.Port.Value, ircBuilder.Nickname, ircBuilder.Password, ircBuilder.PasswordType, settings.ReconnectionInterval.Value, ircBuilder.UseSsl.Value);
|
||||
case ChatProvider.Discord:
|
||||
var discordBuilder = (DiscordConnectionStringBuilder)builder;
|
||||
return new DiscordProvider(
|
||||
assemblyInformationProvider,
|
||||
loggerFactory.CreateLogger<DiscordProvider>(),
|
||||
discordBuilder.BotToken,
|
||||
settings.ReconnectionInterval.Value);
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid ChatProvider: {0}", settings.Provider));
|
||||
}
|
||||
ChatProvider.Irc => new IrcProvider(
|
||||
jobManager,
|
||||
assemblyInformationProvider,
|
||||
asyncDelayer,
|
||||
loggerFactory.CreateLogger<IrcProvider>(),
|
||||
settings),
|
||||
ChatProvider.Discord => new DiscordProvider(
|
||||
jobManager,
|
||||
assemblyInformationProvider,
|
||||
loggerFactory.CreateLogger<DiscordProvider>(),
|
||||
settings),
|
||||
_ => throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid ChatProvider: {0}", settings.Provider)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
@@ -13,7 +13,6 @@ using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
@@ -46,11 +45,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public IDreamMaker DreamMaker { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDmbFactory"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
@@ -101,7 +95,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="watchdog">The value of <see cref="Watchdog"/></param>
|
||||
/// <param name="chat">The value of <see cref="Chat"/></param>
|
||||
/// <param name="configuration">The value of <see cref="Configuration"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="dmbFactory">The value of <see cref="dmbFactory"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
@@ -115,7 +108,6 @@ namespace Tgstation.Server.Host.Components
|
||||
IChatManager chat,
|
||||
StaticFiles.IConfiguration
|
||||
configuration,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IDmbFactory dmbFactory,
|
||||
IJobManager jobManager,
|
||||
IEventConsumer eventConsumer,
|
||||
@@ -128,7 +120,6 @@ namespace Tgstation.Server.Host.Components
|
||||
Watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
@@ -144,7 +135,7 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
timerCts?.Dispose();
|
||||
Configuration.Dispose();
|
||||
Chat.Dispose();
|
||||
await Chat.DisposeAsync().ConfigureAwait(false);
|
||||
await Watchdog.DisposeAsync().ConfigureAwait(false);
|
||||
dmbFactory.Dispose();
|
||||
RepositoryManager.Dispose();
|
||||
@@ -169,15 +160,6 @@ namespace Tgstation.Server.Host.Components
|
||||
await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, new List<string>(), cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
User systemUser = null;
|
||||
await databaseContextFactory.UseContext(
|
||||
async (db) => systemUser = await db
|
||||
.Users
|
||||
.AsQueryable()
|
||||
.Where(x => x.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName))
|
||||
.FirstAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
.ConfigureAwait(false);
|
||||
var repositoryUpdateJob = new Job
|
||||
{
|
||||
Instance = new Models.Instance
|
||||
@@ -186,8 +168,7 @@ namespace Tgstation.Server.Host.Components
|
||||
},
|
||||
Description = "Scheduled repository update",
|
||||
CancelRightsType = RightsType.Repository,
|
||||
CancelRight = (ulong)RepositoryRights.CancelPendingChanges,
|
||||
StartedBy = systemUser
|
||||
CancelRight = (ulong)RepositoryRights.CancelPendingChanges
|
||||
};
|
||||
|
||||
string deploySha = null;
|
||||
@@ -355,7 +336,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// DCT: First token will cancel the job, second is for cancelling the cancellation, unwanted
|
||||
await jobManager.WaitForJobCompletion(repositoryUpdateJob, systemUser, cancellationToken, default).ConfigureAwait(false);
|
||||
await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, default).ConfigureAwait(false);
|
||||
|
||||
if (deploySha == null)
|
||||
{
|
||||
@@ -372,7 +353,6 @@ namespace Tgstation.Server.Host.Components
|
||||
// finally set up the job
|
||||
var compileProcessJob = new Job
|
||||
{
|
||||
StartedBy = systemUser,
|
||||
Instance = repositoryUpdateJob.Instance,
|
||||
Description = "Scheduled code deployment",
|
||||
CancelRightsType = RightsType.DreamMaker,
|
||||
@@ -393,7 +373,7 @@ namespace Tgstation.Server.Host.Components
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await jobManager.WaitForJobCompletion(compileProcessJob, systemUser, default, cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -284,7 +284,6 @@ namespace Tgstation.Server.Host.Components
|
||||
watchdog,
|
||||
chatManager,
|
||||
configuration,
|
||||
databaseContextFactory,
|
||||
dmbFactory,
|
||||
jobManager,
|
||||
eventConsumer,
|
||||
@@ -306,7 +305,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
catch
|
||||
{
|
||||
chatManager.Dispose();
|
||||
await chatManager.DisposeAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -424,6 +424,9 @@ namespace Tgstation.Server.Host.Components
|
||||
})
|
||||
.ToList();
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
jobManager.Activate();
|
||||
|
||||
logger.LogInformation("Server ready!");
|
||||
readyTcs.SetResult(null);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Interop.Topic;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Session
|
||||
@@ -33,9 +34,9 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
Version DMApiVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDmbProvider"/> being used
|
||||
/// Gets the <see cref="CompileJob"/> associated with the <see cref="ISessionController"/>.
|
||||
/// </summary>
|
||||
IDmbProvider Dmb { get; }
|
||||
CompileJob CompileJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current port DreamDaemon is listening on
|
||||
@@ -108,7 +109,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
void EnableCustomChatCommands();
|
||||
|
||||
/// <summary>
|
||||
/// Replace <see cref="Dmb"/> with a given <paramref name="newProvider"/>, disposing the old one.
|
||||
/// Replace the <see cref="IDmbProvider"/> in use with a given <paramref name="newProvider"/>, disposing the old one.
|
||||
/// </summary>
|
||||
/// <param name="newProvider">The new <see cref="IDmbProvider"/>.</param>
|
||||
void ReplaceDmbProvider(IDmbProvider newProvider);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Byond.TopicSender;
|
||||
using Byond.TopicSender;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Context;
|
||||
@@ -39,14 +39,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDmbProvider Dmb
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckDisposed();
|
||||
return reattachInformation.Dmb;
|
||||
}
|
||||
}
|
||||
public Models.CompileJob CompileJob => reattachInformation.Dmb.CompileJob;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort? Port
|
||||
@@ -346,7 +339,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
else if (reattachResponse.InteropResponse != null)
|
||||
logger.LogWarning(
|
||||
"DMAPI v{0} isn't returning the TGS custom commands list. Functionality added in v5.2.0.",
|
||||
Dmb.CompileJob.DMApiVersion.Semver());
|
||||
CompileJob.DMApiVersion.Semver());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
@@ -9,7 +9,6 @@ using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -23,9 +22,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
public sealed override bool AlphaIsActive => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override Models.CompileJob ActiveCompileJob => Server?.Dmb.CompileJob;
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override RebootState? RebootState => Server?.RebootState;
|
||||
|
||||
@@ -46,7 +42,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="sessionPersistor">The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
@@ -61,7 +56,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IDmbFactory dmbFactory,
|
||||
ISessionPersistor sessionPersistor,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
@@ -76,7 +70,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
sessionPersistor,
|
||||
databaseContextFactory,
|
||||
jobManager,
|
||||
serverControl,
|
||||
asyncDelayer,
|
||||
@@ -261,7 +254,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
protected virtual Task HandleNewDmbAvailable(CancellationToken cancellationToken)
|
||||
{
|
||||
gracefulRebootRequired = true;
|
||||
if (Server.Dmb.CompileJob.DMApiVersion == null)
|
||||
if (Server.CompileJob.DMApiVersion == null)
|
||||
return Chat.SendWatchdogMessage(
|
||||
"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.",
|
||||
true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
bool AlphaIsActive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.CompileJob"/> currently running on the server
|
||||
/// Retrieves the <see cref="Models.CompileJob"/> currently running on the server
|
||||
/// </summary>
|
||||
Models.CompileJob ActiveCompileJob { get; }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
@@ -7,7 +7,6 @@ using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -30,7 +29,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="sessionPersistor">The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
@@ -47,7 +45,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IDmbFactory dmbFactory,
|
||||
ISessionPersistor sessionPersistor,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
@@ -64,7 +61,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
sessionPersistor,
|
||||
databaseContextFactory,
|
||||
jobManager,
|
||||
serverControl,
|
||||
asyncDelayer,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
@@ -8,7 +8,6 @@ using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -24,7 +23,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="symlinkFactory">The <see cref="ISymlinkFactory"/> for the <see cref="WindowsWatchdogFactory"/>.</param>
|
||||
@@ -32,7 +30,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
public PosixWatchdogFactory(
|
||||
IServerControl serverControl,
|
||||
ILoggerFactory loggerFactory,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IJobManager jobManager,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
ISymlinkFactory symlinkFactory,
|
||||
@@ -40,7 +37,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
: base(
|
||||
serverControl,
|
||||
loggerFactory,
|
||||
databaseContextFactory,
|
||||
jobManager,
|
||||
asyncDelayer,
|
||||
symlinkFactory,
|
||||
@@ -63,7 +59,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
sessionPersistor,
|
||||
DatabaseContextFactory,
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
@@ -15,7 +14,6 @@ using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Interop.Topic;
|
||||
using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
@@ -42,15 +40,15 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
public abstract bool AlphaIsActive { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Models.CompileJob ActiveCompileJob { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DreamDaemonLaunchParameters ActiveLaunchParameters { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DreamDaemonLaunchParameters LastLaunchParameters { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Models.CompileJob ActiveCompileJob => GetActiveController()?.CompileJob;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract RebootState? RebootState { get; }
|
||||
|
||||
@@ -104,11 +102,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly ISessionPersistor sessionPersistor;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.
|
||||
/// </summary>
|
||||
@@ -176,7 +169,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="sessionControllerFactory">The value of <see cref="SessionControllerFactory"/></param>
|
||||
/// <param name="dmbFactory">The value of <see cref="DmbFactory"/></param>
|
||||
/// <param name="sessionPersistor">The value of <see cref="sessionPersistor"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> to populate <see cref="restartRegistration"/> with</param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="AsyncDelayer"/>.</param>
|
||||
@@ -191,7 +183,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IDmbFactory dmbFactory,
|
||||
ISessionPersistor sessionPersistor,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
@@ -206,7 +197,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
SessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
|
||||
DmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
|
||||
this.sessionPersistor = sessionPersistor ?? throw new ArgumentNullException(nameof(sessionPersistor));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
this.diagnosticsIOManager = diagnosticsIOManager ?? throw new ArgumentNullException(nameof(diagnosticsIOManager));
|
||||
@@ -892,18 +882,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (!autoStart && reattachInfo == null)
|
||||
return;
|
||||
|
||||
Models.User systemUser = null;
|
||||
await databaseContextFactory.UseContext(
|
||||
async db => systemUser = await db
|
||||
.Users
|
||||
.AsQueryable()
|
||||
.Where(x => x.CanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
|
||||
.FirstAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
.ConfigureAwait(false);
|
||||
var job = new Models.Job
|
||||
{
|
||||
StartedBy = systemUser,
|
||||
Instance = new Models.Instance
|
||||
{
|
||||
Id = instance.Id
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
@@ -8,7 +8,6 @@ using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -27,11 +26,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
protected ILoggerFactory LoggerFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
protected IDatabaseContextFactory DatabaseContextFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
@@ -52,21 +46,18 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
/// <param name="serverControl">The value of <see cref="ServerControl"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="LoggerFactory"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="DatabaseContextFactory"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="JobManager"/></param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="AsyncDelayer"/></param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="GeneralConfiguration"/></param>
|
||||
public WatchdogFactory(
|
||||
IServerControl serverControl,
|
||||
ILoggerFactory loggerFactory,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IJobManager jobManager,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions)
|
||||
{
|
||||
ServerControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
DatabaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
JobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
GeneralConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
@@ -88,7 +79,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
sessionPersistor,
|
||||
DatabaseContextFactory,
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -8,7 +8,6 @@ using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -51,7 +50,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="sessionPersistor">The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
@@ -68,7 +66,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IDmbFactory dmbFactory,
|
||||
ISessionPersistor sessionPersistor,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
@@ -84,7 +81,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
sessionPersistor,
|
||||
databaseContextFactory,
|
||||
jobManager,
|
||||
serverControl,
|
||||
asyncDelayer,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
@@ -8,7 +8,6 @@ using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -29,7 +28,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="symlinkFactory">The value of <see cref="SymlinkFactory"/>.</param>
|
||||
@@ -37,7 +35,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
public WindowsWatchdogFactory(
|
||||
IServerControl serverControl,
|
||||
ILoggerFactory loggerFactory,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IJobManager jobManager,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
ISymlinkFactory symlinkFactory,
|
||||
@@ -45,7 +42,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
: base(
|
||||
serverControl,
|
||||
loggerFactory,
|
||||
databaseContextFactory,
|
||||
jobManager,
|
||||
asyncDelayer,
|
||||
generalConfigurationOptions)
|
||||
@@ -69,7 +65,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
sessionPersistor,
|
||||
DatabaseContextFactory,
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="IDatabaseCollection{TModel}"/> <see langword="interface"/>.
|
||||
/// </summary>
|
||||
static class DatabaseCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unattached, unpopulated <see cref="User"/> with the name <see cref="User.TgsSystemUserName"/>.
|
||||
/// </summary>
|
||||
/// <param name="databaseCollection">The <see cref="IDatabaseCollection{TModel}"/> of <see cref="User"/>s to operate on.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the unattached TGS <see cref="User"/> on success, <see langword="null"/> on failure.</returns>
|
||||
public static Task<User> GetTgsUser(this IDatabaseCollection<User> databaseCollection, CancellationToken cancellationToken)
|
||||
=> databaseCollection
|
||||
.AsQueryable()
|
||||
.Where(x => x.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName))
|
||||
.Select(x => new User
|
||||
{
|
||||
Id = x.Id
|
||||
})
|
||||
.FirstAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// <summary>
|
||||
/// Registers a given <see cref="Job"/> and begins running it
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/></param>
|
||||
/// <param name="job">The <see cref="Job"/>. Should at least have <see cref="Job.Instance"/> and <see cref="Api.Models.Internal.Job.Description"/>. If <see cref="Job.StartedBy"/> is <see langword="null"/>, the TGS user will be used.</param>
|
||||
/// <param name="operation">The <see cref="JobEntrypoint"/> for the <paramref name="job"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing a running operation</returns>
|
||||
@@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// Wait for a given <paramref name="job"/> to complete
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/> to wait for </param>
|
||||
/// <param name="canceller">The <see cref="User"/> to cancel the <paramref name="job"/></param>
|
||||
/// <param name="canceller">The <see cref="User"/> to cancel the <paramref name="job"/>. If <see langword="null"/> the TGS user will be used.</param>
|
||||
/// <param name="jobCancellationToken">A <see cref="CancellationToken"/> that will cancel the <paramref name="job"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the <see cref="Job"/></returns>
|
||||
@@ -42,10 +42,15 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// Cancels a give <paramref name="job"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/> to cancel</param>
|
||||
/// <param name="user">The <see cref="User"/> who cancelled the <paramref name="job"/></param>
|
||||
/// <param name="user">The <see cref="User"/> who cancelled the <paramref name="job"/>. If <see langword="null"/> the TGS user will be used.</param>
|
||||
/// <param name="blocking">If the operation should wait until the job exits before completing</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the updated <paramref name="job"/> if it was cancelled, <see langword="null"/> if it couldn't be found.</returns>
|
||||
Task<Job> CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Activate the <see cref="IJobManager"/>.
|
||||
/// </summary>
|
||||
void Activate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using System;
|
||||
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Jobs
|
||||
@@ -35,6 +36,11 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// </summary>
|
||||
readonly Dictionary<long, JobHandler> jobs;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="TaskCompletionSource{TResult}"/> to delay starting jobs until the server is ready.
|
||||
/// </summary>
|
||||
readonly TaskCompletionSource<object> activationTcs;
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="lock"/> <see cref="object"/> for various operations.
|
||||
/// </summary>
|
||||
@@ -52,6 +58,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
this.instanceCoreProvider = instanceCoreProvider ?? throw new ArgumentNullException(nameof(instanceCoreProvider));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
jobs = new Dictionary<long, JobHandler>();
|
||||
activationTcs = new TaskCompletionSource<object>();
|
||||
synchronizationLock = new object();
|
||||
}
|
||||
|
||||
@@ -89,7 +96,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
using (LogContext.PushProperty("Job", job.Id))
|
||||
try
|
||||
{
|
||||
void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails);
|
||||
void LogException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails);
|
||||
try
|
||||
{
|
||||
var oldJob = job;
|
||||
@@ -102,6 +109,8 @@ namespace Tgstation.Server.Host.Jobs
|
||||
handler.Progress = progress;
|
||||
}
|
||||
|
||||
await activationTcs.Task.WithToken(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await operation(
|
||||
instanceCoreProvider.Value.GetInstance(oldJob.Instance),
|
||||
databaseContextFactory,
|
||||
@@ -120,20 +129,13 @@ namespace Tgstation.Server.Host.Jobs
|
||||
catch (JobException e)
|
||||
{
|
||||
job.ErrorCode = e.ErrorCode;
|
||||
job.ExceptionDetails = e.Message;
|
||||
LogRegularException();
|
||||
if (e.InnerException != null)
|
||||
logger.LogDebug(
|
||||
"Inner exception for job {0}: {1}",
|
||||
job.Id,
|
||||
e.InnerException is JobException
|
||||
? e.InnerException.Message
|
||||
: e.InnerException.ToString());
|
||||
job.ExceptionDetails = String.IsNullOrWhiteSpace(e.Message) ? e.InnerException?.Message : e.Message;
|
||||
LogException();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
job.ExceptionDetails = e.ToString();
|
||||
LogRegularException();
|
||||
LogException();
|
||||
}
|
||||
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
@@ -183,10 +185,16 @@ namespace Tgstation.Server.Host.Jobs
|
||||
};
|
||||
databaseContext.Instances.Attach(job.Instance);
|
||||
|
||||
job.StartedBy = new User
|
||||
{
|
||||
Id = job.StartedBy.Id
|
||||
};
|
||||
if (job.StartedBy == null)
|
||||
job.StartedBy = await databaseContext
|
||||
.Users
|
||||
.GetTgsUser(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
job.StartedBy = new User
|
||||
{
|
||||
Id = job.StartedBy.Id
|
||||
};
|
||||
databaseContext.Users.Attach(job.StartedBy);
|
||||
|
||||
databaseContext.Jobs.Add(job);
|
||||
@@ -244,11 +252,14 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var joinTasks = jobs.Select(x =>
|
||||
{
|
||||
x.Value.Cancel();
|
||||
return x.Value.Wait(cancellationToken);
|
||||
});
|
||||
var joinTasks = jobs.Select(x => CancelJob(
|
||||
new Job
|
||||
{
|
||||
Id = x.Key,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
cancellationToken));
|
||||
await Task.WhenAll(joinTasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -257,8 +268,6 @@ namespace Tgstation.Server.Host.Jobs
|
||||
{
|
||||
if (job == null)
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
if (user == null)
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
JobHandler handler;
|
||||
try
|
||||
{
|
||||
@@ -273,6 +282,12 @@ namespace Tgstation.Server.Host.Jobs
|
||||
handler.Cancel(); // this will ensure the db update is only done once
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
user = await databaseContext.Users.GetTgsUser(cancellationToken).ConfigureAwait(false);
|
||||
databaseContext.Users.Attach(user);
|
||||
}
|
||||
|
||||
var updatedJob = new Job { Id = job.Id };
|
||||
databaseContext.Jobs.Attach(job);
|
||||
var attachedUser = new User { Id = user.Id };
|
||||
@@ -322,5 +337,12 @@ namespace Tgstation.Server.Host.Jobs
|
||||
if (cancelTask != null)
|
||||
await cancelTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Activate()
|
||||
{
|
||||
logger.LogTrace("Activating job manager...");
|
||||
activationTcs.SetResult(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-20
@@ -1,9 +1,12 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
|
||||
@@ -11,52 +14,75 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
|
||||
[TestClass]
|
||||
public sealed class TestDiscordProvider
|
||||
{
|
||||
string testToken1;
|
||||
ChatBot testToken1;
|
||||
IJobManager mockJobManager;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
testToken1 = Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_TOKEN_1");
|
||||
var actualToken = Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_TOKEN");
|
||||
if(!String.IsNullOrWhiteSpace(actualToken))
|
||||
testToken1 = new ChatBot
|
||||
{
|
||||
ConnectionString = actualToken,
|
||||
ReconnectionInterval = 1
|
||||
};
|
||||
|
||||
var mockSetup = new Mock<IJobManager>();
|
||||
mockSetup
|
||||
.Setup(x => x.RegisterOperation(It.IsNotNull<Job>(), It.IsNotNull<JobEntrypoint>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<Job, JobEntrypoint, CancellationToken>((job, entrypoint, cancellationToken) => job.StartedBy ??= new User { })
|
||||
.Returns(Task.CompletedTask);
|
||||
mockSetup
|
||||
.Setup(x => x.WaitForJobCompletion(It.IsNotNull<Job>(), It.IsAny<User>(), It.IsAny<CancellationToken>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
mockJobManager = mockSetup.Object;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestConstructionAndDisposal()
|
||||
public async Task TestConstructionAndDisposal()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(null, null, null, 1));
|
||||
if (testToken1 == null)
|
||||
Assert.Inconclusive("Required environment variable TGS4_TEST_DISCORD_TOKEN isn't set!");
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, null, null, null));
|
||||
var mockAss = new Mock<IAssemblyInformationProvider>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockAss.Object, null, null, 1));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, mockAss.Object, null, null));
|
||||
var mockLogger = new Mock<ILogger<DiscordProvider>>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockAss.Object, mockLogger.Object, null, 1));
|
||||
var mockToken = "asdf";
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new DiscordProvider(mockAss.Object, mockLogger.Object, mockToken, 0));
|
||||
new DiscordProvider(mockAss.Object, mockLogger.Object, mockToken, 1).Dispose();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, null, mockLogger.Object, null));
|
||||
await new DiscordProvider(mockJobManager, mockAss.Object, mockLogger.Object, testToken1).DisposeAsync();
|
||||
}
|
||||
|
||||
static Task InvokeConnect(IProvider provider, CancellationToken cancellationToken = default) => (Task)provider.GetType().GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(provider, new object[] { cancellationToken });
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestConnectWithFakeTokenFails()
|
||||
{
|
||||
var mockLogger = new Mock<ILogger<DiscordProvider>>();
|
||||
using var provider = new DiscordProvider(Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, "asdf", 1);
|
||||
Assert.IsFalse(await provider.Connect(default).ConfigureAwait(false));
|
||||
await using var provider = new DiscordProvider(mockJobManager, Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, new ChatBot
|
||||
{
|
||||
ReconnectionInterval = 1,
|
||||
ConnectionString = "asdf"
|
||||
});
|
||||
await Assert.ThrowsExceptionAsync<JobException>(() => InvokeConnect(provider));
|
||||
Assert.IsFalse(provider.Connected);
|
||||
}
|
||||
|
||||
[Ignore("Broken due to dependency issues after first call to .Connect()")]
|
||||
[TestMethod]
|
||||
public async Task TestConnectAndDisconnect()
|
||||
{
|
||||
if (testToken1 == null)
|
||||
Assert.Inconclusive("Required environment variable TGS4_TEST_DISCORD_TOKEN_1 isn't set!");
|
||||
|
||||
Assert.Inconclusive("Required environment variable TGS4_TEST_DISCORD_TOKEN isn't set!");
|
||||
|
||||
var mockLogger = new Mock<ILogger<DiscordProvider>>();
|
||||
using var provider = new DiscordProvider(Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, testToken1, 1);
|
||||
await using var provider = new DiscordProvider(mockJobManager, Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, testToken1);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
await InvokeConnect(provider).ConfigureAwait(false);
|
||||
Assert.IsTrue(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
await InvokeConnect(provider).ConfigureAwait(false);
|
||||
Assert.IsTrue(provider.Connected);
|
||||
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
@@ -68,9 +94,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
var cancellationToken = cts.Token;
|
||||
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => provider.Connect(cancellationToken)).ConfigureAwait(false);
|
||||
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => InvokeConnect(provider, cancellationToken)).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
await InvokeConnect(provider).ConfigureAwait(false);
|
||||
Assert.IsTrue(provider.Connected);
|
||||
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => provider.Disconnect(cancellationToken)).ConfigureAwait(false);
|
||||
Assert.IsTrue(provider.Connected);
|
||||
|
||||
@@ -95,7 +95,13 @@ namespace Tgstation.Server.Tests
|
||||
{
|
||||
try
|
||||
{
|
||||
return await clientFactory.CreateFromLogin(url, User.AdminName, User.DefaultAdminPassword, attemptLoginRefresh: false).ConfigureAwait(false);
|
||||
return await clientFactory.CreateFromLogin(
|
||||
url,
|
||||
User.AdminName,
|
||||
User.DefaultAdminPassword,
|
||||
attemptLoginRefresh: false,
|
||||
cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user