Skip to content

Commit 1c27a0c

Browse files
authored
Add progress to Copy-Item (#18735)
1 parent 44f2792 commit 1c27a0c

File tree

4 files changed

+93
-2
lines changed

4 files changed

+93
-2
lines changed

Diff for: src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ internal static bool IsMinimalProgressRenderingEnabled()
424424

425425
sb.Append(secRemain);
426426

427-
if (PercentComplete > 0 && PercentComplete < 100 && barWidth > 0)
427+
if (PercentComplete >= 0 && PercentComplete < 100 && barWidth > 0)
428428
{
429429
int barLength = PercentComplete * barWidth / 100;
430430
if (barLength >= barWidth)

Diff for: src/System.Management.Automation/namespaces/FileSystemProvider.cs

+85
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Collections.ObjectModel;
88
using System.ComponentModel;
9+
using System.Diagnostics;
910
using System.Diagnostics.CodeAnalysis;
1011
using System.Globalization;
1112
using System.IO;
@@ -18,6 +19,7 @@
1819
using System.Security;
1920
using System.Security.AccessControl;
2021
using System.Text;
22+
using System.Threading.Tasks;
2123
using System.Xml;
2224
using System.Xml.XPath;
2325

@@ -58,6 +60,8 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider,
5860
// copy script will accommodate the new value.
5961
private const int FILETRANSFERSIZE = 4 * 1024 * 1024;
6062

63+
private const int COPY_FILE_ACTIVITY_ID = 0;
64+
6165
// The name of the key in an exception's Data dictionary when attempting
6266
// to copy an item onto itself.
6367
private const string SelfCopyDataKey = "SelfCopy";
@@ -3548,14 +3552,73 @@ protected override void CopyItem(
35483552
}
35493553
else // Copy-Item local
35503554
{
3555+
if (Context != null && Context.ExecutionContext.SessionState.PSVariable.Get(SpecialVariables.ProgressPreferenceVarPath.UserPath).Value is ActionPreference progressPreference && progressPreference == ActionPreference.Continue)
3556+
{
3557+
{
3558+
Task.Run(() =>
3559+
{
3560+
GetTotalFiles(path, recurse);
3561+
});
3562+
_copyStopwatch.Start();
3563+
}
3564+
}
3565+
35513566
CopyItemLocalOrToSession(path, destinationPath, recurse, Force, null);
3567+
if (_totalFiles > 0)
3568+
{
3569+
_copyStopwatch.Stop();
3570+
var progress = new ProgressRecord(COPY_FILE_ACTIVITY_ID, " ", " ");
3571+
progress.RecordType = ProgressRecordType.Completed;
3572+
WriteProgress(progress);
3573+
}
35523574
}
35533575
}
35543576

35553577
_excludeMatcher.Clear();
35563578
_excludeMatcher = null;
35573579
}
35583580

3581+
private void GetTotalFiles(string path, bool recurse)
3582+
{
3583+
bool isContainer = IsItemContainer(path);
3584+
3585+
try
3586+
{
3587+
if (isContainer)
3588+
{
3589+
var enumOptions = new EnumerationOptions()
3590+
{
3591+
IgnoreInaccessible = true,
3592+
AttributesToSkip = 0,
3593+
RecurseSubdirectories = recurse
3594+
};
3595+
3596+
var directory = new DirectoryInfo(path);
3597+
foreach (var file in directory.EnumerateFiles("*", enumOptions))
3598+
{
3599+
if (!SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false))
3600+
{
3601+
_totalFiles++;
3602+
_totalBytes += file.Length;
3603+
}
3604+
}
3605+
}
3606+
else
3607+
{
3608+
var file = new FileInfo(path);
3609+
if (!SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false))
3610+
{
3611+
_totalFiles++;
3612+
_totalBytes += file.Length;
3613+
}
3614+
}
3615+
}
3616+
catch
3617+
{
3618+
// ignore exception
3619+
}
3620+
}
3621+
35593622
private void CopyItemFromRemoteSession(string path, string destinationPath, bool recurse, bool force, PSSession fromSession)
35603623
{
35613624
using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
@@ -3864,6 +3927,22 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force,
38643927

38653928
FileInfo result = new FileInfo(destinationPath);
38663929
WriteItemObject(result, destinationPath, false);
3930+
3931+
if (_totalFiles > 0)
3932+
{
3933+
_copiedFiles++;
3934+
_copiedBytes += file.Length;
3935+
double speed = (double)(_copiedBytes / 1024 / 1024) / _copyStopwatch.Elapsed.TotalSeconds;
3936+
var progress = new ProgressRecord(
3937+
COPY_FILE_ACTIVITY_ID,
3938+
StringUtil.Format(FileSystemProviderStrings.CopyingLocalFileActivity, _copiedFiles, _totalFiles),
3939+
StringUtil.Format(FileSystemProviderStrings.CopyingLocalBytesStatus, Utils.DisplayHumanReadableFileSize(_copiedBytes), Utils.DisplayHumanReadableFileSize(_totalBytes), speed)
3940+
);
3941+
var percentComplete = (int)Math.Min(_copiedBytes * 100 / _totalBytes, 100);
3942+
progress.PercentComplete = percentComplete;
3943+
progress.RecordType = ProgressRecordType.Processing;
3944+
WriteProgress(progress);
3945+
}
38673946
}
38683947
else
38693948
{
@@ -4788,6 +4867,12 @@ private bool PathIsReservedDeviceName(string destinationPath, string errorId)
47884867
return pathIsReservedDeviceName;
47894868
}
47904869

4870+
private long _totalFiles;
4871+
private long _totalBytes;
4872+
private long _copiedFiles;
4873+
private long _copiedBytes;
4874+
private readonly Stopwatch _copyStopwatch = new Stopwatch();
4875+
47914876
#endregion CopyItem
47924877

47934878
#endregion ContainerCmdletProvider members

Diff for: src/System.Management.Automation/resources/FileSystemProviderStrings.resx

+6
Original file line numberDiff line numberDiff line change
@@ -339,4 +339,10 @@
339339
<data name="NewItemTargetIsSameAsLink" xml:space="preserve">
340340
<value>The target and path cannot be the same.</value>
341341
</data>
342+
<data name="CopyingLocalFileActivity" xml:space="preserve">
343+
<value>Copied {0} of {1} files</value>
344+
</data>
345+
<data name="CopyingLocalBytesStatus" xml:space="preserve">
346+
<value>{0} of {1} ({2:0.0} MB/s)</value>
347+
</data>
342348
</root>

Diff for: test/xUnit/csharp/test_Prediction.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContex
7979
{
8080
// The delay is exaggerated to make the test reliable.
8181
// xUnit must spin up a lot tasks, which makes the test unreliable when the time difference between 'delay' and 'timeout' is small.
82-
Thread.Sleep(2000);
82+
Thread.Sleep(3000);
8383
}
8484

8585
// You can get the user input from the AST.

0 commit comments

Comments
 (0)