From d2d63e235b91bcf280c27e139985c66e0bce1530 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 24 Jul 2018 15:25:42 -0400 Subject: [PATCH] Create a truly async ZipToDirectory --- src/Tgstation.Server.Host.Startup/IServer.cs | 4 +-- .../IServerFactory.cs | 3 +- .../Components/ByondManager.cs | 7 ++-- .../Core/IServerUpdater.cs | 10 ++++-- .../IO/DefaultIOManager.cs | 36 ++++++++++++++++++- src/Tgstation.Server.Host/IO/IIOManager.cs | 9 +++++ src/Tgstation.Server.Host/Server.cs | 4 +-- 7 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/Tgstation.Server.Host.Startup/IServer.cs b/src/Tgstation.Server.Host.Startup/IServer.cs index 1fd97d4930..3ab78291f5 100644 --- a/src/Tgstation.Server.Host.Startup/IServer.cs +++ b/src/Tgstation.Server.Host.Startup/IServer.cs @@ -12,13 +12,13 @@ namespace Tgstation.Server.Host.Startup /// /// The path to the updated assembly to run if any. Populated once returns /// - Guid UpdateGuid { get; } + Guid? UpdateGuid { get; } /// /// Runs the /// /// The for the operation /// A representing the running operation - Task RunAsync(string updatesPath, CancellationToken cancellationToken); + Task RunAsync(CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host.Startup/IServerFactory.cs b/src/Tgstation.Server.Host.Startup/IServerFactory.cs index ed92399240..a787e083f8 100644 --- a/src/Tgstation.Server.Host.Startup/IServerFactory.cs +++ b/src/Tgstation.Server.Host.Startup/IServerFactory.cs @@ -9,7 +9,8 @@ /// Create a /// /// The arguments for the + /// The directory in which to install server updates /// A new - IServer CreateServer(string[] args); + IServer CreateServer(string[] args, string updatePath); } } diff --git a/src/Tgstation.Server.Host/Components/ByondManager.cs b/src/Tgstation.Server.Host/Components/ByondManager.cs index e046bcf56c..4b85832126 100644 --- a/src/Tgstation.Server.Host/Components/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/ByondManager.cs @@ -86,12 +86,9 @@ namespace Tgstation.Server.Host.Components await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false); await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false); - var resolvedPath = ioManager.ResolvePath(versionKey); - using (var zipBytes = new MemoryStream(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 ioManager.ZipToDirectory(versionKey, await downloadTask.ConfigureAwait(false), cancellationToken).ConfigureAwait(false); - await byondInstaller.InstallByond(resolvedPath, version, cancellationToken).ConfigureAwait(false); + await byondInstaller.InstallByond(ioManager.ResolvePath(versionKey), version, 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); diff --git a/src/Tgstation.Server.Host/Core/IServerUpdater.cs b/src/Tgstation.Server.Host/Core/IServerUpdater.cs index c4b43eb1b2..ed5a4e76f0 100644 --- a/src/Tgstation.Server.Host/Core/IServerUpdater.cs +++ b/src/Tgstation.Server.Host/Core/IServerUpdater.cs @@ -1,4 +1,7 @@ using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Core { @@ -10,8 +13,11 @@ namespace Tgstation.Server.Host.Core /// /// Run a new assembly and stop the current one. This will likely trigger all active s /// - /// The path to the new assembly - void ApplyUpdate(string updatePath); + /// The s of the .zip file that contains the new assembly + /// The for the operation + /// The for the operation + /// A representing the running operation + Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken); /// /// Register a given to run before stopping the server for updates diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 793b5881e2..b24653f8fe 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using System.Net; using System.Text; @@ -46,6 +47,12 @@ namespace Tgstation.Server.Host.IO dir.Delete(true); } + /// + /// Opens a for async writing at a given + /// + /// The path to open the at + /// A new ready for async writing + static FileStream OpenWriteStream(string path) => new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, DefaultBufferSize, true); /// /// Copies a directory from to @@ -205,7 +212,7 @@ namespace Tgstation.Server.Host.IO public async Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken) { path = ResolvePath(path); - using (var file = File.Open(path, FileMode.Create, FileAccess.Write)) + using (var file = OpenWriteStream(path)) await file.WriteAsync(contents, 0, contents.Length, cancellationToken).ConfigureAwait(false); } @@ -252,5 +259,32 @@ namespace Tgstation.Server.Host.IO using (cancellationToken.Register(() => wc.CancelAsync())) return await wc.DownloadDataTaskAsync(url).ConfigureAwait(false); } + + /// + public async Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken) + { + path = ResolvePath(path); + if (zipFileBytes == null) + throw new ArgumentNullException(nameof(zipFileBytes)); + + using (var ms = new MemoryStream(zipFileBytes)) + { + zipFileBytes = null; + using (var archive = new ZipArchive(ms)) + { + string GetEntryName(ZipArchiveEntry entry) => ConcatPath(path, GetDirectoryName(entry.FullName)); + //create directories first + await Task.WhenAll(archive.Entries.Where(x => x.Name.Length == 0).Select(x => CreateDirectory(GetEntryName(x), cancellationToken))).ConfigureAwait(false); + //extract files + await Task.WhenAll(archive.Entries.Where(x => x.Name.Length > 0).Select(async x => + { + var entryPath = GetEntryName(x); + using (var stream = x.Open()) + using (var file = OpenWriteStream(entryPath)) + await stream.CopyToAsync(file).ConfigureAwait(false); + })).ConfigureAwait(false); + } + } + } } } diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index ad320805d3..3654fed7f5 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -162,5 +162,14 @@ namespace Tgstation.Server.Host.IO /// A for the operation /// A resulting in the s of the downloaded file Task DownloadFile(Uri url, CancellationToken cancellationToken); + + /// + /// Extract a set of to a given + /// + /// The path to unzip to + /// The s of the + /// The for the operation + /// A representing the running operation + Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 5516c3287f..42f9cafeae 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -75,9 +75,9 @@ namespace Tgstation.Server.Host UpdateGuid = Guid.NewGuid(); try { - await ioManager.WriteAllBytes(serverUpdatePath, updateData, cancellationToken).ConfigureAwait(false); + await ioManager.ZipToDirectory(updatePath, updateZipData, cancellationToken).ConfigureAwait(false); } - catch (OperationCanceledException) + catch { UpdateGuid = null; throw;