From 79894150d9c1f33b475474d30ae6c75c08f4a2e2 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 3 May 2021 17:24:41 +0200 Subject: [PATCH] Prerequisites - Fixed install dialog not updating to next action Prerequisites - Finished download file action Prerequisites - Added extract ZIP action --- .../Extensions/StreamExtensions.cs | 47 ++++++----- .../PrerequisiteAction/DownloadFileAction.cs | 26 +++--- .../ExtractArchiveAction.cs | 83 +++++++++++++++++++ .../Plugins/PluginPrerequisiteViewModel.cs | 10 ++- 4 files changed, 128 insertions(+), 38 deletions(-) create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/ExtractArchiveAction.cs diff --git a/src/Artemis.Core/Extensions/StreamExtensions.cs b/src/Artemis.Core/Extensions/StreamExtensions.cs index f4d5b9a5f..a6b350fd2 100644 --- a/src/Artemis.Core/Extensions/StreamExtensions.cs +++ b/src/Artemis.Core/Extensions/StreamExtensions.cs @@ -1,4 +1,4 @@ -// Based on: https://www.codeproject.com/Tips/5274597/An-Improved-Stream-CopyToAsync-that-Reports-Progre +// Based on: https://www.codeproject.com/Tips/5274597/An-Improved-Stream-CopyToAsync-that-Reports-Progre // The MIT License // // Copyright (c) 2020 honey the codewitch @@ -37,7 +37,7 @@ namespace Artemis.Core private const int DefaultBufferSize = 81920; /// - /// Copys a stream to another stream + /// Copies a stream to another stream /// /// The source to copy from /// The length of the source stream, if known - used for progress reporting @@ -54,32 +54,33 @@ namespace Artemis.Core IProgress<(long, long)> progress, CancellationToken cancellationToken) { - if (0 == bufferSize) + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (!source.CanRead) + throw new ArgumentException("Has to be readable", nameof(source)); + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + if (!destination.CanWrite) + throw new ArgumentException("Has to be writable", nameof(destination)); + if (bufferSize <= 0) bufferSize = DefaultBufferSize; - byte[]? buffer = new byte[bufferSize]; - if (0 > sourceLength && source.CanSeek) - sourceLength = source.Length - source.Position; - long totalBytesCopied = 0L; - if (null != progress) - progress.Report((totalBytesCopied, sourceLength)); - int bytesRead = -1; - while (0 != bytesRead && !cancellationToken.IsCancellationRequested) + + byte[] buffer = new byte[bufferSize]; + long totalBytesRead = 0; + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { - bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken); - if (0 == bytesRead || cancellationToken.IsCancellationRequested) - break; - await destination.WriteAsync(buffer, 0, buffer.Length, cancellationToken); - totalBytesCopied += bytesRead; - progress?.Report((totalBytesCopied, sourceLength)); + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + totalBytesRead += bytesRead; + progress?.Report((totalBytesRead, sourceLength)); } - if (0 < totalBytesCopied) - progress?.Report((totalBytesCopied, sourceLength)); + progress?.Report((totalBytesRead, sourceLength)); cancellationToken.ThrowIfCancellationRequested(); } /// - /// Copys a stream to another stream + /// Copies a stream to another stream /// /// The source to copy from /// The length of the source stream, if known - used for progress reporting @@ -93,7 +94,7 @@ namespace Artemis.Core } /// - /// Copys a stream to another stream + /// Copies a stream to another stream /// /// The source to copy from /// The destination to copy to @@ -106,7 +107,7 @@ namespace Artemis.Core } /// - /// Copys a stream to another stream + /// Copies a stream to another stream /// /// The source to copy from /// The length of the source stream, if known - used for progress reporting @@ -119,7 +120,7 @@ namespace Artemis.Core } /// - /// Copys a stream to another stream + /// Copies a stream to another stream /// /// The source to copy from /// The destination to copy to diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DownloadFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DownloadFileAction.cs index a4daf9532..0707abfd6 100644 --- a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DownloadFileAction.cs +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DownloadFileAction.cs @@ -16,12 +16,12 @@ namespace Artemis.Core /// Creates a new instance of a copy folder action /// /// The name of the action - /// The source URL to download - /// The target file to save as (will be created if needed) - public DownloadFileAction(string name, string source, string target) : base(name) + /// The source URL to download + /// The target file to save as (will be created if needed) + public DownloadFileAction(string name, string url, string fileName) : base(name) { - Source = source ?? throw new ArgumentNullException(nameof(source)); - Target = target ?? throw new ArgumentNullException(nameof(target)); + Url = url ?? throw new ArgumentNullException(nameof(url)); + FileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); ShowProgressBar = true; } @@ -29,31 +29,31 @@ namespace Artemis.Core /// /// Gets the source URL to download /// - public string Source { get; } + public string Url { get; } /// /// Gets the target file to save as (will be created if needed) /// - public string Target { get; } + public string FileName { get; } /// public override async Task Execute(CancellationToken cancellationToken) { using HttpClient client = new(); - await using FileStream destinationStream = File.Create(Target); + await using FileStream destinationStream = new(FileName, FileMode.OpenOrCreate); void ProgressOnProgressReported(object? sender, EventArgs e) { if (Progress.ProgressPerSecond != 0) - Status = $"Downloading {Target} - {Progress.ProgressPerSecond.Bytes().Humanize("#.##")}/sec"; + Status = $"Downloading {Url} - {Progress.ProgressPerSecond.Bytes().Humanize("#.##")}/sec"; else - Status = $"Downloading {Target}"; + Status = $"Downloading {Url}"; } Progress.ProgressReported += ProgressOnProgressReported; // Get the http headers first to examine the content length - using HttpResponseMessage response = await client.GetAsync(Target, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + using HttpResponseMessage response = await client.GetAsync(Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); await using Stream download = await response.Content.ReadAsStreamAsync(cancellationToken); long? contentLength = response.Content.Headers.ContentLength; @@ -63,6 +63,7 @@ namespace Artemis.Core { ProgressIndeterminate = true; await download.CopyToAsync(destinationStream, Progress, cancellationToken); + ProgressIndeterminate = false; } else { @@ -70,8 +71,9 @@ namespace Artemis.Core await download.CopyToAsync(contentLength.Value, destinationStream, Progress, cancellationToken); } + cancellationToken.ThrowIfCancellationRequested(); + Progress.ProgressReported -= ProgressOnProgressReported; - Progress.Report((1, 1)); Status = "Finished downloading"; } diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/ExtractArchiveAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/ExtractArchiveAction.cs new file mode 100644 index 000000000..b09f076b9 --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/ExtractArchiveAction.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + /// + /// Represents a plugin prerequisite action that extracts a ZIP file + /// + public class ExtractArchiveAction : PluginPrerequisiteAction + { + /// + /// Creates a new instance of . + /// + /// The name of the action + /// The ZIP file to extract + /// The folder into which to extract the file + public ExtractArchiveAction(string name, string fileName, string target) : base(name) + { + FileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); + Target = target ?? throw new ArgumentNullException(nameof(target)); + + ShowProgressBar = true; + } + + /// + /// Gets the file to extract + /// + public string FileName { get; } + + /// + /// Gets the folder into which to extract the file + /// + public string Target { get; } + + /// + public override async Task Execute(CancellationToken cancellationToken) + { + using HttpClient client = new(); + + ShowSubProgressBar = true; + Status = $"Extracting {FileName}"; + + Utilities.CreateAccessibleDirectory(Target); + + await using (FileStream fileStream = new(FileName, FileMode.Open)) + { + ZipArchive archive = new(fileStream); + long count = 0; + foreach (ZipArchiveEntry entry in archive.Entries) + { + await using Stream unzippedEntryStream = entry.Open(); + Progress.Report((count, archive.Entries.Count)); + if (entry.Length > 0) + { + string path = Path.Combine(Target, entry.FullName); + CreateDirectoryForFile(path); + await using Stream extractStream = new FileStream(path, FileMode.OpenOrCreate); + await unzippedEntryStream.CopyToAsync(entry.Length, extractStream, SubProgress, cancellationToken); + } + + count++; + } + } + + Progress.Report((1, 1)); + ShowSubProgressBar = false; + Status = "Finished extracting"; + } + + private static void CreateDirectoryForFile(string path) + { + string? directory = Path.GetDirectoryName(path); + if (directory == null) + throw new ArtemisCoreException($"Failed to get directory from path {path}"); + + Utilities.CreateAccessibleDirectory(directory); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs index 27205b135..841f79385 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs @@ -114,7 +114,11 @@ namespace Artemis.UI.Screens.Plugins private void ActivateCurrentAction() { - ActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction); + PluginPrerequisiteActionViewModel newActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction); + if (newActiveItem == null) + return; + + ActiveItem = newActiveItem; NotifyOfPropertyChange(nameof(ActiveStemNumber)); } @@ -123,14 +127,14 @@ namespace Artemis.UI.Screens.Plugins /// protected override void OnClose() { - PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged; + PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; base.OnClose(); } /// protected override void OnInitialActivate() { - PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; + PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged; // Could be slow so take it off of the UI thread Task.Run(() => IsMet = PluginPrerequisite.IsMet());