Create a truly async ZipToDirectory

This commit is contained in:
Cyberboss
2018-07-24 15:25:42 -04:00
parent 2c2680a95f
commit d2d63e235b
7 changed files with 60 additions and 13 deletions
+2 -2
View File
@@ -12,13 +12,13 @@ namespace Tgstation.Server.Host.Startup
/// <summary>
/// The path to the updated assembly to run if any. Populated once <see cref="RunAsync(CancellationToken)"/> returns
/// </summary>
Guid UpdateGuid { get; }
Guid? UpdateGuid { get; }
/// <summary>
/// Runs the <see cref="IServer"/>
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task RunAsync(string updatesPath, CancellationToken cancellationToken);
Task RunAsync(CancellationToken cancellationToken);
}
}
@@ -9,7 +9,8 @@
/// Create a <see cref="IServer"/>
/// </summary>
/// <param name="args">The arguments for the <see cref="IServer"/></param>
/// <param name="updatePath">The directory in which to install server updates</param>
/// <returns>A new <see cref="IServer"/></returns>
IServer CreateServer(string[] args);
IServer CreateServer(string[] args, string updatePath);
}
}
@@ -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);
@@ -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
/// <summary>
/// Run a new <see cref="Host"/> assembly and stop the current one. This will likely trigger all active <see cref="System.Threading.CancellationToken"/>s
/// </summary>
/// <param name="updatePath">The path to the new <see cref="Host"/> assembly</param>
void ApplyUpdate(string updatePath);
/// <param name="updateZipData">The <see cref="byte"/>s of the .zip file that contains the new <see cref="Host"/> assembly</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <param name="ioManager">The <see cref="IIOManager"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken);
/// <summary>
/// Register a given <paramref name="action"/> to run before stopping the server for updates
@@ -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);
}
/// <summary>
/// Opens a <see cref="FileStream"/> for async writing at a given <paramref name="path"/>
/// </summary>
/// <param name="path">The path to open the <see cref="FileStream"/> at</param>
/// <returns>A new <see cref="FileStream"/> ready for async writing</returns>
static FileStream OpenWriteStream(string path) => new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, DefaultBufferSize, true);
/// <summary>
/// Copies a directory from <paramref name="src"/> to <paramref name="dest"/>
@@ -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);
}
/// <inheritdoc />
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);
}
}
}
}
}
@@ -162,5 +162,14 @@ namespace Tgstation.Server.Host.IO
/// <param name="cancellationToken">A <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="byte"/>s of the downloaded file</returns>
Task<byte[]> DownloadFile(Uri url, CancellationToken cancellationToken);
/// <summary>
/// Extract a set of <paramref name="zipFileBytes"/> to a given <paramref name="path"/>
/// </summary>
/// <param name="path">The path to unzip to</param>
/// <param name="zipFileBytes">The <see cref="byte"/>s of the <see cref="System.IO.Compression.ZipArchive"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken);
}
}
+2 -2
View File
@@ -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;