mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Display download progress in human readable format for Invoke-WebRequest (#14611)
This commit is contained in:
+1
-1
@@ -458,7 +458,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
else if (ShouldSaveToOutFile)
|
||||
{
|
||||
StreamHelper.SaveStreamToFile(baseResponseStream, QualifiedOutFile, this, _cancelToken.Token);
|
||||
StreamHelper.SaveStreamToFile(baseResponseStream, QualifiedOutFile, this, response.Content.Headers.ContentLength.GetValueOrDefault(), _cancelToken.Token);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(StatusCodeVariable))
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
|
||||
int initialCapacity = (int)Math.Min(contentLength, StreamHelper.DefaultReadBuffer);
|
||||
_rawContentStream = new WebResponseContentMemoryStream(st, initialCapacity, null);
|
||||
_rawContentStream = new WebResponseContentMemoryStream(st, initialCapacity, cmdlet: null, response.Content.Headers.ContentLength.GetValueOrDefault());
|
||||
}
|
||||
// set the position of the content stream to the beginning
|
||||
_rawContentStream.Position = 0;
|
||||
|
||||
+6
-2
@@ -38,7 +38,11 @@ namespace Microsoft.PowerShell.Commands
|
||||
if (ShouldWriteToPipeline)
|
||||
{
|
||||
// creating a MemoryStream wrapper to response stream here to support IsStopping.
|
||||
responseStream = new WebResponseContentMemoryStream(responseStream, StreamHelper.ChunkSize, this);
|
||||
responseStream = new WebResponseContentMemoryStream(
|
||||
responseStream,
|
||||
StreamHelper.ChunkSize,
|
||||
this,
|
||||
response.Content.Headers.ContentLength.GetValueOrDefault());
|
||||
WebResponseObject ro = WebResponseObjectFactory.GetResponseObject(response, responseStream, this.Context);
|
||||
ro.RelationLink = _relationLink;
|
||||
WriteObject(ro);
|
||||
@@ -52,7 +56,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
if (ShouldSaveToOutFile)
|
||||
{
|
||||
StreamHelper.SaveStreamToFile(responseStream, QualifiedOutFile, this, _cancelToken.Token);
|
||||
StreamHelper.SaveStreamToFile(responseStream, QualifiedOutFile, this, response.Content.Headers.ContentLength.GetValueOrDefault(), _cancelToken.Token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-10
@@ -24,6 +24,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
#region Data
|
||||
|
||||
private readonly long? _contentLength;
|
||||
private readonly Stream _originalStreamToProxy;
|
||||
private bool _isInitialized = false;
|
||||
private readonly Cmdlet _ownerCmdlet;
|
||||
@@ -37,9 +38,11 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="initialCapacity"></param>
|
||||
/// <param name="cmdlet">Owner cmdlet if any.</param>
|
||||
internal WebResponseContentMemoryStream(Stream stream, int initialCapacity, Cmdlet cmdlet)
|
||||
/// <param name="contentLength">Expected download size in Bytes.</param>
|
||||
internal WebResponseContentMemoryStream(Stream stream, int initialCapacity, Cmdlet cmdlet, long? contentLength)
|
||||
: base(initialCapacity)
|
||||
{
|
||||
this._contentLength = contentLength;
|
||||
_originalStreamToProxy = stream;
|
||||
_ownerCmdlet = cmdlet;
|
||||
}
|
||||
@@ -218,14 +221,24 @@ namespace Microsoft.PowerShell.Commands
|
||||
_isInitialized = true;
|
||||
try
|
||||
{
|
||||
long totalLength = 0;
|
||||
long totalRead = 0;
|
||||
byte[] buffer = new byte[StreamHelper.ChunkSize];
|
||||
ProgressRecord record = new(StreamHelper.ActivityId, WebCmdletStrings.ReadResponseProgressActivity, "statusDescriptionPlaceholder");
|
||||
for (int read = 1; read > 0; totalLength += read)
|
||||
string totalDownloadSize = _contentLength is null ? "???" : Utils.DisplayHumanReadableFileSize((long)_contentLength);
|
||||
for (int read = 1; read > 0; totalRead += read)
|
||||
{
|
||||
if (_ownerCmdlet != null)
|
||||
{
|
||||
record.StatusDescription = StringUtil.Format(WebCmdletStrings.ReadResponseProgressStatus, totalLength);
|
||||
record.StatusDescription = StringUtil.Format(
|
||||
WebCmdletStrings.ReadResponseProgressStatus,
|
||||
Utils.DisplayHumanReadableFileSize(totalRead),
|
||||
totalDownloadSize);
|
||||
|
||||
if (_contentLength > 0)
|
||||
{
|
||||
record.PercentComplete = Math.Min((int)(totalRead * 100 / (long)_contentLength), 100);
|
||||
}
|
||||
|
||||
_ownerCmdlet.WriteProgress(record);
|
||||
|
||||
if (_ownerCmdlet.IsStopping)
|
||||
@@ -244,13 +257,13 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
if (_ownerCmdlet != null)
|
||||
{
|
||||
record.StatusDescription = StringUtil.Format(WebCmdletStrings.ReadResponseComplete, totalLength);
|
||||
record.StatusDescription = StringUtil.Format(WebCmdletStrings.ReadResponseComplete, totalRead);
|
||||
record.RecordType = ProgressRecordType.Completed;
|
||||
_ownerCmdlet.WriteProgress(record);
|
||||
}
|
||||
|
||||
// make sure the length is set appropriately
|
||||
base.SetLength(totalLength);
|
||||
base.SetLength(totalRead);
|
||||
base.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -276,7 +289,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
#region Static Methods
|
||||
|
||||
internal static void WriteToStream(Stream input, Stream output, PSCmdlet cmdlet, CancellationToken cancellationToken)
|
||||
internal static void WriteToStream(Stream input, Stream output, PSCmdlet cmdlet, long? contentLength, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cmdlet == null)
|
||||
{
|
||||
@@ -289,12 +302,22 @@ namespace Microsoft.PowerShell.Commands
|
||||
ActivityId,
|
||||
WebCmdletStrings.WriteRequestProgressActivity,
|
||||
WebCmdletStrings.WriteRequestProgressStatus);
|
||||
string totalDownloadSize = contentLength is null ? "???" : Utils.DisplayHumanReadableFileSize((long)contentLength);
|
||||
|
||||
try
|
||||
{
|
||||
while (!copyTask.Wait(1000, cancellationToken))
|
||||
{
|
||||
record.StatusDescription = StringUtil.Format(WebCmdletStrings.WriteRequestProgressStatus, output.Position);
|
||||
record.StatusDescription = StringUtil.Format(
|
||||
WebCmdletStrings.WriteRequestProgressStatus,
|
||||
Utils.DisplayHumanReadableFileSize(output.Position),
|
||||
totalDownloadSize);
|
||||
|
||||
if (contentLength != null && contentLength > 0)
|
||||
{
|
||||
record.PercentComplete = Math.Min((int)(output.Position * 100 / (long)contentLength), 100);
|
||||
}
|
||||
|
||||
cmdlet.WriteProgress(record);
|
||||
}
|
||||
|
||||
@@ -316,13 +339,14 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// <param name="stream">Input stream.</param>
|
||||
/// <param name="filePath">Output file name.</param>
|
||||
/// <param name="cmdlet">Current cmdlet (Invoke-WebRequest or Invoke-RestMethod).</param>
|
||||
/// <param name="contentLength">Expected download size in Bytes.</param>
|
||||
/// <param name="cancellationToken">CancellationToken to track the cmdlet cancellation.</param>
|
||||
internal static void SaveStreamToFile(Stream stream, string filePath, PSCmdlet cmdlet, CancellationToken cancellationToken)
|
||||
internal static void SaveStreamToFile(Stream stream, string filePath, PSCmdlet cmdlet, long? contentLength, CancellationToken cancellationToken)
|
||||
{
|
||||
// If the web cmdlet should resume, append the file instead of overwriting.
|
||||
FileMode fileMode = cmdlet is WebRequestPSCmdlet webCmdlet && webCmdlet.ShouldResume ? FileMode.Append : FileMode.Create;
|
||||
using FileStream output = new(filePath, fileMode, FileAccess.Write, FileShare.Read);
|
||||
WriteToStream(stream, output, cmdlet, cancellationToken);
|
||||
WriteToStream(stream, output, cmdlet, contentLength, cancellationToken);
|
||||
}
|
||||
|
||||
private static string StreamToString(Stream stream, Encoding encoding)
|
||||
|
||||
@@ -199,13 +199,13 @@
|
||||
<value>The cmdlet cannot run because the following parameter is missing: Proxy. Provide a valid proxy URI for the Proxy parameter when using the ProxyCredential or ProxyUseDefaultCredentials parameters, then retry.</value>
|
||||
</data>
|
||||
<data name="ReadResponseComplete" xml:space="preserve">
|
||||
<value>Reading web response completed. (Number of bytes read: {0})</value>
|
||||
<value>Reading web response stream completed. Downloaded: {0}</value>
|
||||
</data>
|
||||
<data name="ReadResponseProgressActivity" xml:space="preserve">
|
||||
<value>Reading web response</value>
|
||||
<value>Reading web response stream</value>
|
||||
</data>
|
||||
<data name="ReadResponseProgressStatus" xml:space="preserve">
|
||||
<value>Reading response stream... (Number of bytes read: {0})</value>
|
||||
<value>Downloaded: {0} of {1}</value>
|
||||
</data>
|
||||
<data name="RequestTimeout" xml:space="preserve">
|
||||
<value>The operation has timed out.</value>
|
||||
@@ -223,7 +223,7 @@
|
||||
<value>Web request status</value>
|
||||
</data>
|
||||
<data name="WriteRequestProgressStatus" xml:space="preserve">
|
||||
<value>Number of bytes processed: {0}</value>
|
||||
<value>Downloaded: {0} of {1}</value>
|
||||
</data>
|
||||
<data name="JsonNetModuleRequired" xml:space="preserve">
|
||||
<value>The ConvertTo-Json and ConvertFrom-Json cmdlets require the 'Json.Net' module. {0}</value>
|
||||
|
||||
@@ -5,8 +5,6 @@ using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -17,7 +15,6 @@ using System.Management.Automation.Remoting;
|
||||
using System.Management.Automation.Security;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
#if !UNIX
|
||||
@@ -1547,6 +1544,21 @@ namespace System.Management.Automation
|
||||
|
||||
return oldMode;
|
||||
}
|
||||
|
||||
internal static string DisplayHumanReadableFileSize(long bytes)
|
||||
{
|
||||
return bytes switch
|
||||
{
|
||||
< 1024 and >= 0 => $"{bytes} Bytes",
|
||||
< 1048576 and >= 1024 => $"{(bytes / 1024.0).ToString("0.0")} KB",
|
||||
< 1073741824 and >= 1048576 => $"{(bytes / 1048576.0).ToString("0.0")} MB",
|
||||
< 1099511627776 and >= 1073741824 => $"{(bytes / 1073741824.0).ToString("0.000")} GB",
|
||||
< 1125899906842624 and >= 1099511627776 => $"{(bytes / 1099511627776.0).ToString("0.00000")} TB",
|
||||
< 1152921504606847000 and >= 1125899906842624 => $"{(bytes / 1125899906842624.0).ToString("0.0000000")} PB",
|
||||
>= 1152921504606847000 => $"{(bytes / 1152921504606847000.0).ToString("0.000000000")} EB",
|
||||
_ => $"0 Bytes",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,23 @@ namespace PSTests.Parallel
|
||||
Assert.False(Utils.IsWinPEHost());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(long.MinValue, "0 Bytes")]
|
||||
[InlineData(-1, "0 Bytes")]
|
||||
[InlineData(0, "0 Bytes")]
|
||||
[InlineData(1, "1 Bytes")]
|
||||
[InlineData(1024, "1.0 KB")]
|
||||
[InlineData(3000, "2.9 KB")]
|
||||
[InlineData(1024 * 1024, "1.0 MB")]
|
||||
[InlineData(1024 * 1024 * 1024, "1.000 GB")]
|
||||
[InlineData((long)(1024 * 1024 * 1024) * 1024, "1.00000 TB")]
|
||||
[InlineData((long)(1024 * 1024 * 1024) * 1024 * 1024, "1.0000000 PB")]
|
||||
[InlineData(long.MaxValue, "8.000000000 EB")]
|
||||
public static void DisplayHumanReadableFileSize(long bytes, string expected)
|
||||
{
|
||||
Assert.Equal(expected, Utils.DisplayHumanReadableFileSize(bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TestHistoryStack()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user