Add support for changing console title

- Move `VersionExtensions` to `Tgstation.Server.Common`.
- Fix Host watchdog API version specifying `Revision`.
This commit is contained in:
Jordan Dominion
2023-07-02 09:13:27 -04:00
parent 609fea8fde
commit 2e6e2841e5
27 changed files with 133 additions and 50 deletions
+1
View File
@@ -12,6 +12,7 @@ using Microsoft.Net.Http.Headers;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Properties;
using Tgstation.Server.Common.Extensions;
namespace Tgstation.Server.Api
{
@@ -48,4 +48,8 @@
<ItemGroup>
<AdditionalFiles Include="../../build/stylecop.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
</ItemGroup>
</Project>
@@ -27,7 +27,6 @@
<ItemGroup>
<ProjectReference Include="..\Tgstation.Server.Api\Tgstation.Server.Api.csproj" />
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
</ItemGroup>
<ItemGroup>
@@ -1,6 +1,6 @@
using System;
namespace Tgstation.Server.Api
namespace Tgstation.Server.Common.Extensions
{
/// <summary>
/// Extensions for the <see cref="Version"/> class.
+4 -1
View File
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Watchdog;
namespace Tgstation.Server.Host.Console
@@ -34,6 +35,8 @@ namespace Tgstation.Server.Host.Console
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
internal static async Task<int> Main(string[] args)
{
System.Console.Title = $"tgstation-server Host Watchdog v{Assembly.GetExecutingAssembly().GetName().Version.Semver()}";
var arguments = new List<string>(args);
var trace = arguments.Remove("--trace-host-watchdog");
var debug = arguments.Remove("--debug-host-watchdog");
@@ -44,6 +44,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
<ProjectReference Include="..\Tgstation.Server.Host.Common\Tgstation.Server.Host.Common.csproj" />
</ItemGroup>
</Project>
@@ -10,7 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Common;
namespace Tgstation.Server.Host.Watchdog
@@ -109,7 +109,7 @@ namespace Tgstation.Server.Host.Watchdog
return false;
}
var watchdogVersion = executingAssembly.GetName().Version.ToString();
var watchdogVersion = executingAssembly.GetName().Version.Semver().ToString();
while (!cancellationToken.IsCancellationRequested)
using (logger.BeginScope("Host invocation"))
@@ -87,6 +87,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly ISwarmServiceController swarmServiceController;
/// <summary>
/// The <see cref="IConsole"/> for the <see cref="InstanceManager"/>.
/// </summary>
readonly IConsole console;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="InstanceManager"/>.
/// </summary>
@@ -132,6 +137,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly CancellationTokenSource shutdownCancellationTokenSource;
/// <summary>
/// The original <see cref="IConsole.Title"/> of <see cref="console"/>.
/// </summary>
readonly string originalConsoleTitle;
/// <summary>
/// The <see cref="Task"/> returned by <see cref="Initialize(CancellationToken)"/>.
/// </summary>
@@ -155,6 +165,7 @@ namespace Tgstation.Server.Host.Components
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
/// <param name="serverPortProvider">The value of <see cref="serverPortProvider"/>.</param>
/// <param name="swarmServiceController">The value of <see cref="swarmServiceController"/>.</param>
/// <param name="console">The value of <see cref="console"/>.</param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
/// <param name="swarmConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="swarmConfiguration"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
@@ -169,6 +180,7 @@ namespace Tgstation.Server.Host.Components
IAsyncDelayer asyncDelayer,
IServerPortProvider serverPortProvider,
ISwarmServiceController swarmServiceController,
IConsole console,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<SwarmConfiguration> swarmConfigurationOptions,
ILogger<InstanceManager> logger)
@@ -183,10 +195,13 @@ namespace Tgstation.Server.Host.Components
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider));
this.swarmServiceController = swarmServiceController ?? throw new ArgumentNullException(nameof(swarmServiceController));
this.console = console ?? throw new ArgumentNullException(nameof(console));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
originalConsoleTitle = console.Title;
instances = new Dictionary<long, ReferenceCountingContainer<IInstance, InstanceWrapper>>();
bridgeHandlers = new Dictionary<string, IBridgeHandler>();
readyTcs = new TaskCompletionSource();
@@ -426,42 +441,49 @@ namespace Tgstation.Server.Host.Components
/// <inheritdoc />
public async Task StopAsync(CancellationToken cancellationToken)
{
using (cancellationToken.Register(shutdownCancellationTokenSource.Cancel))
try
{
logger.LogDebug("Stopping instance manager...");
if (!startupTask.IsCompleted)
try
{
using (cancellationToken.Register(shutdownCancellationTokenSource.Cancel))
try
{
logger.LogTrace("Interrupting startup task...");
startupCancellationTokenSource.Cancel();
await startupTask;
logger.LogDebug("Stopping instance manager...");
if (!startupTask.IsCompleted)
{
logger.LogTrace("Interrupting startup task...");
startupCancellationTokenSource.Cancel();
await startupTask;
}
var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken);
await jobService.StopAsync(cancellationToken);
async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken)
{
try
{
await instance.StopAsync(cancellationToken);
}
catch (Exception ex)
{
logger.LogError(ex, "Instance shutdown exception!");
}
}
await Task.WhenAll(instances.Select(x => OfflineInstanceImmediate(x.Value.Instance, cancellationToken)));
await instanceFactoryStopTask;
await swarmServiceController.Shutdown(cancellationToken);
}
var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken);
await jobService.StopAsync(cancellationToken);
async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken)
finally
{
try
{
await instance.StopAsync(cancellationToken);
}
catch (Exception ex)
{
logger.LogError(ex, "Instance shutdown exception!");
}
console.Title = originalConsoleTitle;
}
await Task.WhenAll(instances.Select(x => OfflineInstanceImmediate(x.Value.Instance, cancellationToken)));
await instanceFactoryStopTask;
await swarmServiceController.Shutdown(cancellationToken);
}
catch (Exception ex)
{
logger.LogCritical(ex, "Instance manager stop exception!");
}
}
catch (Exception ex)
{
logger.LogCritical(ex, "Instance manager stop exception!");
}
}
/// <inheritdoc />
@@ -535,6 +557,7 @@ namespace Tgstation.Server.Host.Components
try
{
logger.LogInformation("{versionString}", assemblyInformationProvider.VersionString);
console.Title = assemblyInformationProvider.VersionString;
CheckSystemCompatibility();
@@ -13,8 +13,8 @@ using Newtonsoft.Json;
using Serilog.Context;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Components.Chat;
using Tgstation.Server.Host.Components.Deployment;
@@ -10,9 +10,9 @@ using Byond.TopicSender;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Components.Chat;
using Tgstation.Server.Host.Components.Deployment;
@@ -22,6 +22,7 @@ using Serilog.Context;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Models;
@@ -2,7 +2,7 @@
using Newtonsoft.Json;
using Tgstation.Server.Api;
using Tgstation.Server.Common.Extensions;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
+19 -2
View File
@@ -3,19 +3,33 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.IO
{
/// <inheritdoc />
sealed class Console : IConsole, IDisposable
{
/// <inheritdoc />
public string Title
{
get => platformIdentifier.IsWindows
? global::System.Console.Title
: null;
set => global::System.Console.Title = value;
}
/// <inheritdoc />
public bool Available => Environment.UserInteractive;
/// <inheritdoc />
public CancellationToken CancelKeyPress => cancelKeyCts.Token;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="Console"/>.
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="CancellationTokenSource"/> for <see cref="CancelKeyPress"/>.
/// </summary>
@@ -29,8 +43,11 @@ namespace Tgstation.Server.Host.IO
/// <summary>
/// Initializes a new instance of the <see cref="Console"/> class.
/// </summary>
public Console()
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
public Console(IPlatformIdentifier platformIdentifier)
{
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
cancelKeyCts = new CancellationTokenSource();
global::System.Console.CancelKeyPress += (sender, e) =>
{
+5
View File
@@ -8,6 +8,11 @@ namespace Tgstation.Server.Host.IO
/// </summary>
interface IConsole
{
/// <summary>
/// Gets or sets the <see cref="IConsole"/> window's title. Can return <see langword="null"/> if getting the console title is not supported.
/// </summary>
string Title { get; set; }
/// <summary>
/// If the <see cref="IConsole"/> is visible to the user.
/// </summary>
+16 -1
View File
@@ -1073,8 +1073,18 @@ namespace Tgstation.Server.Host.Setup
await console.WriteAsync("Aborting setup!", true, default);
}
// Link passed cancellationToken with cancel key press
Task finalTask = Task.CompletedTask;
string originalConsoleTitle = null;
void SetConsoleTitle()
{
if (originalConsoleTitle != null)
return;
originalConsoleTitle = console.Title;
console.Title = $"{assemblyInformationProvider.VersionString} Setup Wizard";
}
// Link passed cancellationToken with cancel key press
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, console.CancelKeyPress))
using ((cancellationToken = cts.Token).Register(() => finalTask = HandleSetupCancel()))
try
@@ -1106,6 +1116,7 @@ namespace Tgstation.Server.Host.Setup
{
if (forceRun)
{
SetConsoleTitle();
await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "The configuration settings are requesting the setup wizard be run, but you already appear to have a configuration file ({0})!", userConfigFileName), true, cancellationToken);
forceRun = await PromptYesNo("Continue running setup wizard?", false, cancellationToken);
@@ -1115,6 +1126,8 @@ namespace Tgstation.Server.Host.Setup
return;
}
SetConsoleTitle();
// flush the logs to prevent console conflicts
await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken);
@@ -1123,6 +1136,8 @@ namespace Tgstation.Server.Host.Setup
finally
{
await finalTask;
if (originalConsoleTitle != null)
console.Title = originalConsoleTitle;
}
}
}
@@ -2,7 +2,7 @@
using System.Net.Http.Headers;
using System.Reflection;
using Tgstation.Server.Api;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Common;
namespace Tgstation.Server.Host.System
@@ -140,7 +140,6 @@
<ItemGroup>
<ProjectReference Include="..\Tgstation.Server.Api\Tgstation.Server.Api.csproj" />
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
<ProjectReference Include="..\Tgstation.Server.Host.Common\Tgstation.Server.Host.Common.csproj" />
</ItemGroup>
@@ -16,6 +16,7 @@ using Swashbuckle.AspNetCore.SwaggerGen;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Controllers;
namespace Tgstation.Server.Host.Utils
@@ -1,7 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Tgstation.Server.Api;
using Tgstation.Server.Common.Extensions;
using YamlDotNet.Serialization;
@@ -4,15 +4,23 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.IO.Tests
{
[TestClass]
public sealed class TestConsole
{
[TestMethod]
public void TestContructionThrows()
{
Assert.ThrowsException<ArgumentNullException>(() => new Console(null));
}
[TestMethod]
public async Task TestWriteLine()
{
var console = new Console();
var console = new Console(new PlatformIdentifier());
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => console.WriteAsync(null, false, default));
try
{
@@ -28,7 +36,7 @@ namespace Tgstation.Server.Host.IO.Tests
[TestMethod]
public void TestUserInteractive()
{
var console = new Console();
var console = new Console(new PlatformIdentifier());
Assert.AreEqual(Environment.UserInteractive, console.Available);
}
}
@@ -9,11 +9,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.IO;
@@ -24,6 +24,7 @@ using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Components.Chat;
using Tgstation.Server.Host.Components.Interop;
@@ -14,6 +14,7 @@ using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Client;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host;
namespace Tgstation.Server.Tests.Live
@@ -31,6 +31,7 @@ using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Database;
@@ -9,6 +9,7 @@ using System.Xml.Linq;
using Tgstation.Server.Api;
using Tgstation.Server.Client;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Configuration;
@@ -15,6 +15,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Client;
using Tgstation.Server.Common.Extensions;
static class Program
{
@@ -21,6 +21,7 @@ using Octokit;
using Tgstation.Server.Api;
using Tgstation.Server.Client;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Common.Http;
using Tgstation.Server.Host.Common;
using Tgstation.Server.Host.IO;