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());