From cd2233d85cf0322199aacaac4a44ffd5b40ea6bc Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Fri, 1 Aug 2025 10:57:26 -0400 Subject: [PATCH] Properly make `ZipToDirectory` use `IFileSystem` --- .../Engine/IEngineInstallationData.cs | 4 +- .../RepositoryEngineInstallationData.cs | 5 +- .../Engine/ZipStreamEngineInstallationData.cs | 2 +- .../IO/DefaultIOManager.cs | 49 +++++++++++++------ src/Tgstation.Server.Host/IO/IIOManager.cs | 4 +- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallationData.cs b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallationData.cs index b3d8458973..0194c10d52 100644 --- a/src/Tgstation.Server.Host/Components/Engine/IEngineInstallationData.cs +++ b/src/Tgstation.Server.Host/Components/Engine/IEngineInstallationData.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Components.Engine /// /// The full path to extract to. /// The for the operation. - /// A representing the running operation. - Task ExtractToPath(string path, CancellationToken cancellationToken); + /// A representing the running operation. + ValueTask ExtractToPath(string path, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Engine/RepositoryEngineInstallationData.cs b/src/Tgstation.Server.Host/Components/Engine/RepositoryEngineInstallationData.cs index 88fbb77911..8b89075d4e 100644 --- a/src/Tgstation.Server.Host/Components/Engine/RepositoryEngineInstallationData.cs +++ b/src/Tgstation.Server.Host/Components/Engine/RepositoryEngineInstallationData.cs @@ -48,12 +48,11 @@ namespace Tgstation.Server.Host.Components.Engine } /// - public Task ExtractToPath(string path, CancellationToken cancellationToken) + public ValueTask ExtractToPath(string path, CancellationToken cancellationToken) => repository.CopyTo( ioManager.ConcatPath( path, targetSubDirectory), - cancellationToken) - .AsTask(); + cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Engine/ZipStreamEngineInstallationData.cs b/src/Tgstation.Server.Host/Components/Engine/ZipStreamEngineInstallationData.cs index 116689f8de..195c2b3b04 100644 --- a/src/Tgstation.Server.Host/Components/Engine/ZipStreamEngineInstallationData.cs +++ b/src/Tgstation.Server.Host/Components/Engine/ZipStreamEngineInstallationData.cs @@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Components.Engine public ValueTask DisposeAsync() => zipStream.DisposeAsync(); /// - public Task ExtractToPath(string path, CancellationToken cancellationToken) + public ValueTask ExtractToPath(string path, CancellationToken cancellationToken) => ioManager.ZipToDirectory(path, zipStream, cancellationToken); } } diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 68b6aac015..3ec8aef858 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Utils; namespace Tgstation.Server.Host.IO @@ -335,26 +336,46 @@ namespace Tgstation.Server.Host.IO TaskScheduler.Current); /// - public Task ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken) => Task.Factory.StartNew( - () => - { - path = ResolvePath(path); - ArgumentNullException.ThrowIfNull(zipFile); + public async ValueTask ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken) + { + path = ResolvePath(path); + ArgumentNullException.ThrowIfNull(zipFile); #if NET9_0_OR_GREATER #error Check if zip file seeking has been addressesed. See https://github.com/tgstation/tgstation-server/issues/1531 #endif - // ZipArchive does a synchronous copy on unseekable streams we want to avoid - if (!zipFile.CanSeek) - throw new ArgumentException("Stream does not support seeking!", nameof(zipFile)); + // ZipArchive does a synchronous copy on unseekable streams we want to avoid + if (!zipFile.CanSeek) + throw new ArgumentException("Stream does not support seeking!", nameof(zipFile)); - using var archive = new ZipArchive(zipFile, ZipArchiveMode.Read, true); - archive.ExtractToDirectory(path); - }, - cancellationToken, - BlockingTaskCreationOptions, - TaskScheduler.Current); + using var archive = new ZipArchive(zipFile, ZipArchiveMode.Read, true); + + // start async context + await Task.Yield(); + foreach (var entry in archive.Entries) + { + var entryPath = fileSystem.Path.Combine(path, entry.FullName); + + if (string.IsNullOrEmpty(entry.Name)) + { + fileSystem.Directory.CreateDirectory(entryPath); + continue; + } + + var directoryPath = fileSystem.Path.GetDirectoryName(entryPath); + if (directoryPath == null) + { + throw new JobException("Zip archive concatenation resulted in a null directory path!"); + } + + fileSystem.Directory.CreateDirectory(directoryPath); + + using var entryStream = entry.Open(); + using var outputStream = fileSystem.File.Create(entryPath); + await entryStream.CopyToAsync(outputStream, cancellationToken); + } + } /// public bool PathContainsParentAccess(string path) => path diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index 3b81869fb8..6d505faec0 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -238,8 +238,8 @@ namespace Tgstation.Server.Host.IO /// The path to unzip to. /// The of the . Must have set to . Will be read completely and left open. will be indeterminate. /// The for the operation. - /// A representing the running operation. - Task ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken); + /// A representing the running operation. + ValueTask ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken); /// /// Get the of when a given was last modified.