diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index d9a87d4643..b0b1647831 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -9,12 +9,6 @@ namespace Tgstation.Server.Api.Models [Model(RightsType.Byond, RequiresInstance = true)] public sealed class Byond { - /// - /// The for the installation - /// - [Permissions(DenyWrite = true, ReadRight = ByondRights.ReadStatus)] - public ByondStatus ByondStatus { get; set; } - /// /// The of the installation used for new compiles. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers /// diff --git a/src/Tgstation.Server.Api/Models/ByondStatus.cs b/src/Tgstation.Server.Api/Models/ByondStatus.cs deleted file mode 100644 index 638a91e475..0000000000 --- a/src/Tgstation.Server.Api/Models/ByondStatus.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Tgstation.Server.Api.Models -{ - /// - /// The status of a update job - /// -#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names - public enum ByondStatus -#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names - { - /// - /// No update in progress - /// - Idle, - /// - /// Preparing to update - /// - Starting, - /// - /// Revision is downloading - /// - Downloading, - /// - /// Revision is deflating - /// - Staging, - /// - /// Revision is ready and waiting for DreamDaemon reboot - /// - Staged, - /// - /// Revision is being applied - /// - Updating, - /// - /// User does not have permission to view the - /// - Hidden, - } -} diff --git a/src/Tgstation.Server.Host/Components/Byond.cs b/src/Tgstation.Server.Host/Components/Byond.cs deleted file mode 100644 index 1e3e5e39df..0000000000 --- a/src/Tgstation.Server.Host/Components/Byond.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Tgstation.Server.Host.IO; - -namespace Tgstation.Server.Host.Components -{ - /// - sealed class Byond : IByond - { - /// - /// The for - /// - readonly IIOManager ioManager; - - /// - /// The for - /// - readonly ILogger logger; - - /// - /// List of installed BYOND s - /// - readonly List installedVersions; - - /// - /// Construct - /// - /// The value of - /// The value of - public Byond(IIOManager ioManager, ILogger logger) - { - this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - public Task ChangeVersion(Version version, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public Task ClearCache(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public Task GetVersion(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public IByondExecutableLock UseExecutables(Version requiredVersion) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs new file mode 100644 index 0000000000..aff60de879 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -0,0 +1,113 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; + +namespace Tgstation.Server.Host.Components +{ + /// + sealed class ByondManager : IByondManager + { + const string VersionFileName = "Version.txt"; + + /// + /// The for the + /// + readonly IIOManager ioManager; + + /// + /// The for the + /// + readonly IByondInstaller byondInstaller; + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Map of byond s to s that complete when they are installed + /// + readonly Dictionary installedVersions; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + public ByondManager(IIOManager ioManager, IByondInstaller byondInstaller, ILogger logger) + { + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + static string VersionKey(Version version) => new Version(version.Major, version.Minor).ToString(); + + async Task InstallVersion(Version version, CancellationToken cancellationToken) + { + var ourTcs = new TaskCompletionSource(); + Task inProgressTask; + + var versionKey = VersionKey(version); + bool installed; + lock (installedVersions) + { + installed = installedVersions.TryGetValue(versionKey, out inProgressTask); + if (!installed) + installedVersions.Add(versionKey, ourTcs.Task); + } + if(installed) + using (cancellationToken.Register(() => ourTcs.SetCanceled())) + { + await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false); + return; + } + + var downloadTask = byondInstaller.DownloadVersion(version, cancellationToken); + + //okay up to us to install it then + await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false); + await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false); + + var resolvedPath = ioManager.ResolvePath(versionKey); + using (var zipBytes = await downloadTask.ConfigureAwait(false)) + using (var archive = new ZipArchive(zipBytes)) + await Task.Factory.StartNew(() => archive.ExtractToDirectory(resolvedPath), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + + await byondInstaller.InstallByond(resolvedPath, cancellationToken).ConfigureAwait(false); + + //make sure to do this last because this is what tells us we have a valid version + await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); + } + + /// + public Task ChangeVersion(Version version, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task ClearCache(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public Task GetVersion(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public IByondExecutableLock UseExecutables(Version requiredVersion) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/DreamMaker.cs b/src/Tgstation.Server.Host/Components/DreamMaker.cs index 06cd345bf7..58dbccfd38 100644 --- a/src/Tgstation.Server.Host/Components/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/DreamMaker.cs @@ -39,9 +39,9 @@ namespace Tgstation.Server.Host.Components public CompilerStatus Status { get; private set; } /// - /// The for + /// The for /// - readonly IByond byond; + readonly IByondManager byond; /// /// The for /// @@ -82,7 +82,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public DreamMaker(IByond byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger logger) + public DreamMaker(IByondManager byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger logger) { this.byond = byond; this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); diff --git a/src/Tgstation.Server.Host/Components/IByondInstaller.cs b/src/Tgstation.Server.Host/Components/IByondInstaller.cs new file mode 100644 index 0000000000..98a8e4831d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/IByondInstaller.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using Stream = System.IO.Stream; + +namespace Tgstation.Server.Host.Components +{ + /// + /// For downloading and installing BYOND extractions + /// + interface IByondInstaller + { + /// + /// Download a given BYOND + /// + /// The of BYOND to download + /// The for the operation + /// A resulting in a of the zipfile + Task DownloadVersion(Version version, CancellationToken cancellationToken); + + /// + /// Does actions necessary to get an extracted BYOND installation working + /// + /// The path to the BYOND installation + /// The for the operation + /// + Task InstallByond(string path, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/IByond.cs b/src/Tgstation.Server.Host/Components/IByondManager.cs similarity index 91% rename from src/Tgstation.Server.Host/Components/IByond.cs rename to src/Tgstation.Server.Host/Components/IByondManager.cs index 105d6dac74..a0226edd64 100644 --- a/src/Tgstation.Server.Host/Components/IByond.cs +++ b/src/Tgstation.Server.Host/Components/IByondManager.cs @@ -7,17 +7,17 @@ namespace Tgstation.Server.Host.Components /// /// For managing the BYOND installation /// - public interface IByond + public interface IByondManager { /// - /// Change the current BYOND version + /// Change the active BYOND version /// /// The new /// The for the operation Task ChangeVersion(Version version, CancellationToken cancellationToken); /// - /// Get the currently installed BYOND version + /// Get the currently active BYOND version /// /// The for the operation /// The current BYOND version diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs index 9868430342..ffcf471681 100644 --- a/src/Tgstation.Server.Host/Components/IInstance.cs +++ b/src/Tgstation.Server.Host/Components/IInstance.cs @@ -16,9 +16,9 @@ namespace Tgstation.Server.Host.Components IRepositoryManager RepositoryManager { get; } /// - /// The for the + /// The for the /// - IByond Byond { get; } + IByondManager ByondManager { get; } /// /// The for the diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index d607053ab2..80e0a8bf3a 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components public IRepositoryManager RepositoryManager { get; } /// - public IByond Byond { get; } + public IByondManager ByondManager { get; } /// public IDreamMaker DreamMaker { get; } @@ -59,11 +59,11 @@ namespace Tgstation.Server.Host.Components /// CancellationTokenSource timerCts; - public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory) + public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory) { this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); - Byond = byond ?? throw new ArgumentNullException(nameof(byond)); + ByondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager)); DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)); watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); Chat = chat ?? throw new ArgumentNullException(nameof(chat)); diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 391b06d5db..ec9021ff1d 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Components var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager); - IByond byond = null; + IByondManager byond = null; var configuration = new Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger()); var chat = chatFactory.CreateChat(); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index b84535a1fa..d5f6063c75 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -24,9 +24,9 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly IExecutor executor; /// - /// The for the + /// The for the /// - readonly IByond byond; + readonly IByondManager byond; /// /// The for the @@ -81,7 +81,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public SessionControllerFactory(IExecutor executor, IByond byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Models.Instance instance) + public SessionControllerFactory(IExecutor executor, IByondManager byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Models.Instance instance) { this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond));