diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
index b8e50b458c..63ef457aee 100644
--- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
@@ -19,7 +19,6 @@ using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
-using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.System;
@@ -40,9 +39,9 @@ namespace Tgstation.Server.Host.Controllers
const string OctokitException = "Bad GitHub API response, check configuration!";
///
- /// The for the .
+ /// The for the .
///
- readonly IGitHubClientFactory gitHubClientFactory;
+ readonly IGitHubService gitHubService;
///
/// The for the .
@@ -74,11 +73,6 @@ namespace Tgstation.Server.Host.Controllers
///
readonly IFileTransferTicketProvider fileTransferService;
- ///
- /// The for the .
- ///
- readonly UpdatesConfiguration updatesConfiguration;
-
///
/// The for the .
///
@@ -89,7 +83,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// The for the .
/// The for the .
- /// The value of .
+ /// The value of .
/// The value of .
/// The value of .
/// The value of .
@@ -97,12 +91,11 @@ namespace Tgstation.Server.Host.Controllers
/// The value of .
/// The value of .
/// The for the .
- /// The containing value of .
/// The containing value of .
public AdministrationController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
- IGitHubClientFactory gitHubClientFactory,
+ IGitHubService gitHubService,
IServerControl serverControl,
IServerUpdateInitiator serverUpdateInitiator,
IAssemblyInformationProvider assemblyInformationProvider,
@@ -110,7 +103,6 @@ namespace Tgstation.Server.Host.Controllers
IPlatformIdentifier platformIdentifier,
IFileTransferTicketProvider fileTransferService,
ILogger logger,
- IOptions updatesConfigurationOptions,
IOptions fileLoggingConfigurationOptions)
: base(
databaseContext,
@@ -118,14 +110,13 @@ namespace Tgstation.Server.Host.Controllers
logger,
true)
{
- this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
+ this.gitHubService = gitHubService ?? throw new ArgumentNullException(nameof(gitHubService));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
this.serverUpdateInitiator = serverUpdateInitiator ?? throw new ArgumentNullException(nameof(serverUpdateInitiator));
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
- updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
fileLoggingConfiguration = fileLoggingConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(fileLoggingConfigurationOptions));
}
@@ -150,26 +141,19 @@ namespace Tgstation.Server.Host.Controllers
Uri repoUrl = null;
try
{
- var gitHubClient = gitHubClientFactory.CreateClient();
- var repositoryTask = gitHubClient
- .Repository
- .Get(updatesConfiguration.GitHubRepositoryId)
- .WithToken(cancellationToken);
- var releases = (await gitHubClient
- .Repository
- .Release
- .GetAll(updatesConfiguration.GitHubRepositoryId)
- .WithToken(cancellationToken))
- .Where(x => x.TagName.StartsWith(
- updatesConfiguration.GitTagPrefix,
- StringComparison.InvariantCulture));
+ var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken);
+ var releases = await gitHubService.GetTgsReleases(cancellationToken);
- foreach (var release in releases)
- if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version)
- && version.Major > 3 // Forward/backward compatible but not before TGS4
+ foreach (var kvp in releases)
+ {
+ var version = kvp.Key;
+ var release = kvp.Value;
+ if (version.Major > 3 // Forward/backward compatible but not before TGS4
&& (greatestVersion == null || version > greatestVersion))
greatestVersion = version;
- repoUrl = new Uri((await repositoryTask).HtmlUrl);
+ }
+
+ repoUrl = await repositoryUrlTask;
}
catch (NotFoundException e)
{
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 552ab73290..2bdac39c75 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -370,7 +370,10 @@ namespace Tgstation.Server.Host.Core
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+
services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton(x => x.GetRequiredService().CreateService());
// configure root services
services.AddSingleton();
diff --git a/src/Tgstation.Server.Host/Utils/GitHubClientFactory.cs b/src/Tgstation.Server.Host/Utils/GitHubClientFactory.cs
index 5bdb7091e1..da74748e6f 100644
--- a/src/Tgstation.Server.Host/Utils/GitHubClientFactory.cs
+++ b/src/Tgstation.Server.Host/Utils/GitHubClientFactory.cs
@@ -67,7 +67,9 @@ namespace Tgstation.Server.Host.Utils
public IGitHubClient CreateClient() => GetOrCreateClient(generalConfiguration.GitHubAccessToken);
///
- public IGitHubClient CreateClient(string accessToken) => GetOrCreateClient(accessToken ?? throw new ArgumentNullException(nameof(accessToken)));
+ public IGitHubClient CreateClient(string accessToken)
+ => GetOrCreateClient(
+ accessToken ?? throw new ArgumentNullException(nameof(accessToken)));
///
/// Retrieve a from the or add a new one based on a given .
diff --git a/src/Tgstation.Server.Host/Utils/GitHubService.cs b/src/Tgstation.Server.Host/Utils/GitHubService.cs
new file mode 100644
index 0000000000..5d1d6ef5d4
--- /dev/null
+++ b/src/Tgstation.Server.Host/Utils/GitHubService.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Extensions.Logging;
+
+using Octokit;
+
+using Tgstation.Server.Host.Configuration;
+using Tgstation.Server.Host.Extensions;
+
+namespace Tgstation.Server.Host.Utils
+{
+ ///
+ sealed class GitHubService : IGitHubService
+ {
+ ///
+ /// The for the .
+ ///
+ readonly IGitHubClient gitHubClient;
+
+ ///
+ /// The for the .
+ ///
+ readonly ILogger logger;
+
+ ///
+ /// The for the .
+ ///
+ readonly UpdatesConfiguration updatesConfiguration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ public GitHubService(IGitHubClient gitHubClient, ILogger logger, UpdatesConfiguration updatesConfiguration)
+ {
+ this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient));
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ this.updatesConfiguration = updatesConfiguration ?? throw new ArgumentNullException(nameof(updatesConfiguration));
+ }
+
+ ///
+ public async Task> GetTgsReleases(CancellationToken cancellationToken)
+ {
+ logger.LogTrace("GetTgsReleases");
+ var allReleases = await gitHubClient
+ .Repository
+ .Release
+ .GetAll(updatesConfiguration.GitHubRepositoryId)
+ .WithToken(cancellationToken);
+
+ logger.LogTrace("{totalReleases} total releases", allReleases.Count);
+ var releases = allReleases
+ .Select(release =>
+ {
+ if (!release.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture)
+ || !Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version))
+ return null;
+
+ return Tuple.Create(version, release);
+ })
+ .Where(tuple => tuple != null)
+ .ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);
+
+ logger.LogTrace("{parsedReleases} parsed releases", releases.Count);
+ return releases;
+ }
+
+ ///
+ public async Task GetUpdatesRepositoryUrl(CancellationToken cancellationToken)
+ {
+ logger.LogTrace("GetUpdatesRepositoryUrl");
+ var repository = await gitHubClient
+ .Repository
+ .Get(updatesConfiguration.GitHubRepositoryId)
+ .WithToken(cancellationToken);
+
+ var repoUrl = new Uri(repository.HtmlUrl);
+ logger.LogTrace("Maps to {repostioryUrl}", repoUrl);
+
+ return repoUrl;
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Utils/GitHubServiceFactory.cs b/src/Tgstation.Server.Host/Utils/GitHubServiceFactory.cs
new file mode 100644
index 0000000000..85332165c8
--- /dev/null
+++ b/src/Tgstation.Server.Host/Utils/GitHubServiceFactory.cs
@@ -0,0 +1,66 @@
+using System;
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+using Octokit;
+
+using Tgstation.Server.Host.Configuration;
+
+namespace Tgstation.Server.Host.Utils
+{
+ ///
+ sealed class GitHubServiceFactory : IGitHubServiceFactory
+ {
+ ///
+ /// The for the .
+ ///
+ readonly IGitHubClientFactory gitHubClientFactory;
+
+ ///
+ /// The for the .
+ ///
+ readonly ILoggerFactory loggerFactory;
+
+ ///
+ /// The for the .
+ ///
+ readonly UpdatesConfiguration updatesConfiguration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ /// The containing value of .
+ public GitHubServiceFactory(
+ IGitHubClientFactory gitHubClientFactory,
+ ILoggerFactory loggerFactory,
+ IOptions updatesConfigurationOptions)
+ {
+ this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
+ this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
+ updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
+ }
+
+ ///
+ public IGitHubService CreateService() => CreateServiceImpl(gitHubClientFactory.CreateClient());
+
+ ///
+ public IGitHubService CreateService(string accessToken)
+ => CreateServiceImpl(
+ gitHubClientFactory.CreateClient(
+ accessToken ?? throw new ArgumentNullException(nameof(accessToken))));
+
+ ///
+ /// Create a .
+ ///
+ /// The for the .
+ /// A new .
+ GitHubService CreateServiceImpl(IGitHubClient gitHubClient)
+ => new (
+ gitHubClient,
+ loggerFactory.CreateLogger(),
+ updatesConfiguration);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Utils/IGitHubService.cs b/src/Tgstation.Server.Host/Utils/IGitHubService.cs
new file mode 100644
index 0000000000..2a6da1aef4
--- /dev/null
+++ b/src/Tgstation.Server.Host/Utils/IGitHubService.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Octokit;
+
+namespace Tgstation.Server.Host.Utils
+{
+ ///
+ /// Service for interacting with GitHub.
+ ///
+ public interface IGitHubService
+ {
+ ///
+ /// Gets the of the repository designated as the updates repository.
+ ///
+ /// The for the operation.
+ /// A resulting in the of the designated updates repository.
+ Task GetUpdatesRepositoryUrl(CancellationToken cancellationToken);
+
+ ///
+ /// Get all valid TGS s from the configured update source.
+ ///
+ /// The for the operation.
+ /// A resulting in a of TGS s keyed by their .
+ Task> GetTgsReleases(CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Utils/IGitHubServiceFactory.cs b/src/Tgstation.Server.Host/Utils/IGitHubServiceFactory.cs
new file mode 100644
index 0000000000..c6997f9d5f
--- /dev/null
+++ b/src/Tgstation.Server.Host/Utils/IGitHubServiceFactory.cs
@@ -0,0 +1,21 @@
+namespace Tgstation.Server.Host.Utils
+{
+ ///
+ /// Factory for s.
+ ///
+ public interface IGitHubServiceFactory
+ {
+ ///
+ /// Create a .
+ ///
+ /// A new .
+ public IGitHubService CreateService();
+
+ ///
+ /// Create a .
+ ///
+ /// The access token to use for communication with GitHub.
+ /// A new .
+ public IGitHubService CreateService(string accessToken);
+ }
+}
diff --git a/tests/Tgstation.Server.Host.Tests/Utils/TestGitHubServiceFactory.cs b/tests/Tgstation.Server.Host.Tests/Utils/TestGitHubServiceFactory.cs
new file mode 100644
index 0000000000..6287e3b271
--- /dev/null
+++ b/tests/Tgstation.Server.Host.Tests/Utils/TestGitHubServiceFactory.cs
@@ -0,0 +1,57 @@
+using System;
+
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Moq;
+
+using Octokit;
+
+using Tgstation.Server.Host.Configuration;
+
+namespace Tgstation.Server.Host.Utils.Tests
+{
+ [TestClass]
+ public sealed class TestGitHubServiceFactory
+ {
+ [TestMethod]
+ public void TestConstructor()
+ {
+ Assert.ThrowsException(() => new GitHubServiceFactory(null, null, null));
+ Assert.ThrowsException(() => new GitHubServiceFactory(Mock.Of(), null, null));
+ Assert.ThrowsException(() => new GitHubServiceFactory(Mock.Of(), Mock.Of(), null));
+ var mockOptions = new Mock>();
+ mockOptions.SetupGet(x => x.Value).Returns(new UpdatesConfiguration());
+
+ _ = new GitHubServiceFactory(Mock.Of(), Mock.Of(), mockOptions.Object);
+ }
+
+ [TestMethod]
+ public void TestCreateService()
+ {
+ var mockFactory = new Mock();
+
+ mockFactory.Setup(x => x.CreateClient()).Returns(Mock.Of()).Verifiable();
+
+ var mockToken = "asdf";
+ mockFactory.Setup(x => x.CreateClient(mockToken)).Returns(Mock.Of()).Verifiable();
+
+ var mockOptions = new Mock>();
+ mockOptions.SetupGet(x => x.Value).Returns(new UpdatesConfiguration());
+
+ var factory = new GitHubServiceFactory(mockFactory.Object, Mock.Of(), mockOptions.Object);
+
+ Assert.ThrowsException(() => factory.CreateService(null));
+ Assert.AreEqual(0, mockFactory.Invocations.Count);
+
+ var result1 = factory.CreateService();
+ Assert.IsNotNull(result1);
+
+ var result2 = factory.CreateService(mockToken);
+ Assert.IsNotNull(result2);
+
+ mockFactory.VerifyAll();
+ }
+ }
+}