From 6ac424f31d470af85f643ead42066e86e8c87bdd Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:55:12 +0200 Subject: [PATCH] Remote: Unzip while downloading --- RemoteDownloaderPlugin/Game/GameDownload.cs | 62 ++++++++++++------- .../RemoteDownloaderPlugin.csproj | 4 ++ .../Utils/StreamInterceptor.cs | 47 ++++++++++++++ 3 files changed, 89 insertions(+), 24 deletions(-) create mode 100644 RemoteDownloaderPlugin/Utils/StreamInterceptor.cs diff --git a/RemoteDownloaderPlugin/Game/GameDownload.cs b/RemoteDownloaderPlugin/Game/GameDownload.cs index a97deda..8d34e41 100644 --- a/RemoteDownloaderPlugin/Game/GameDownload.cs +++ b/RemoteDownloaderPlugin/Game/GameDownload.cs @@ -1,7 +1,8 @@ -using System.IO.Compression; +using ICSharpCode.SharpZipLib.Zip; using LauncherGamePlugin; using LauncherGamePlugin.Interfaces; using RemoteDownloaderPlugin.Utils; +using ZipFile = System.IO.Compression.ZipFile; namespace RemoteDownloaderPlugin.Game; @@ -128,40 +129,53 @@ private async Task DownloadPc(IApp app, PcEntry entry) Type = GameType.Pc; BasePath = Path.Join(app.GameDir, "Remote", "Pc", entry.GameId); Directory.CreateDirectory(BasePath); - var zipFilePath = Path.Join(BasePath, "__game__.zip"); using HttpClient client = new(); - var fs = new FileStream(zipFilePath, FileMode.Create); - Progress progress = new(); progress.ProgressChanged += OnProgressUpdate; try { - await client.DownloadAsync(entry.Url, fs, progress, _cts.Token); - } - catch (TaskCanceledException e) - { - await Task.Run(() => fs.Dispose()); + using HttpResponseMessage response = await client.GetAsync(entry.Url, HttpCompletionOption.ResponseHeadersRead, _cts.Token); + response.EnsureSuccessStatusCode(); + await using var responseStream = await response.Content.ReadAsStreamAsync(); + var interceptor = + new StreamInterceptor(responseStream, progress, response.Content.Headers.ContentLength!.Value); + await using var zipInputStream = new ZipInputStream(interceptor); + + while (zipInputStream.GetNextEntry() is { } zipEntry) + { + var destinationPath = Path.Combine(BasePath, zipEntry.Name); - Directory.Delete(BasePath); - - throw; - } + // Ensure the parent directory exists + var directoryPath = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath)) + { + Directory.CreateDirectory(directoryPath); + } - - Percentage = 100; - Line1 = "Saving..."; - InvokeOnUpdate(); - await Task.Run(() => fs.Dispose()); - Line1 = "Unzipping..."; - InvokeOnUpdate(); - await Task.Run(() => ZipFile.ExtractToDirectory(zipFilePath, BasePath)); - File.Delete(zipFilePath); + // Skip directory entries + if (zipEntry.IsDirectory) + { + continue; + } - if (_cts.IsCancellationRequested) + var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write); + try + { + await zipInputStream.CopyToAsync(fileStream, _cts.Token); + } + finally + { + await Task.Run(() => fileStream.Dispose()); + } + } + } + catch (Exception e) { - Directory.Delete(BasePath); + app.Logger.Log($"Download failed: {e.Message}", LogType.Error, "Remote"); + Directory.Delete(BasePath, true); + throw; } TotalSize = await Task.Run(() => LauncherGamePlugin.Utils.DirSize(new(BasePath))); diff --git a/RemoteDownloaderPlugin/RemoteDownloaderPlugin.csproj b/RemoteDownloaderPlugin/RemoteDownloaderPlugin.csproj index cc146bd..d3c0c64 100644 --- a/RemoteDownloaderPlugin/RemoteDownloaderPlugin.csproj +++ b/RemoteDownloaderPlugin/RemoteDownloaderPlugin.csproj @@ -13,5 +13,9 @@ runtime + + + + diff --git a/RemoteDownloaderPlugin/Utils/StreamInterceptor.cs b/RemoteDownloaderPlugin/Utils/StreamInterceptor.cs new file mode 100644 index 0000000..4869318 --- /dev/null +++ b/RemoteDownloaderPlugin/Utils/StreamInterceptor.cs @@ -0,0 +1,47 @@ +namespace RemoteDownloaderPlugin.Utils; + +public class StreamInterceptor : Stream +{ + private Stream _stream; + private IProgress _progress; + private long _contentLength; + private long _totalRead; + + public StreamInterceptor(Stream stream, IProgress progress, long contentLength) + { + _stream = stream; + _progress = progress; + _contentLength = contentLength; + } + + public override void Flush() + => _stream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = _stream.Read(buffer, offset, count); + _totalRead += bytesRead; + _progress.Report((float)_totalRead / _contentLength); + return bytesRead; + } + + public override long Seek(long offset, SeekOrigin origin) + => _stream.Seek(offset, origin); + + public override void SetLength(long value) + => _stream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) + => _stream.Write(buffer, offset, count); + + public override bool CanRead => _stream.CanRead; + public override bool CanSeek => _stream.CanSeek; + public override bool CanWrite => _stream.CanWrite; + public override long Length => _stream.Length; + + public override long Position + { + get => _stream.Position; + set => _stream.Position = value; + } +} \ No newline at end of file