From 026a5f213e08d263cb48e95f1e5be2f8ca7d449c Mon Sep 17 00:00:00 2001 From: Steve Date: Sun, 5 Sep 2021 20:49:02 +0800 Subject: [PATCH] Improve performance of file operations --- .../FilesystemItemsOperationDataModel.cs | 23 ++-- Files/Helpers/UIFilesystemHelpers.cs | 128 ++++++++++-------- .../BaseLayoutCommandImplementationModel.cs | 7 +- Files/Views/ColumnShellPage.xaml.cs | 14 +- Files/Views/ModernShellPage.xaml.cs | 14 +- 5 files changed, 101 insertions(+), 85 deletions(-) diff --git a/Files/DataModels/FilesystemItemsOperationDataModel.cs b/Files/DataModels/FilesystemItemsOperationDataModel.cs index 64a542571ae6..58be5f0e7b7e 100644 --- a/Files/DataModels/FilesystemItemsOperationDataModel.cs +++ b/Files/DataModels/FilesystemItemsOperationDataModel.cs @@ -2,6 +2,7 @@ using Files.Helpers; using Files.ViewModels.Dialogs; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -59,16 +60,16 @@ public FilesystemItemsOperationDataModel(FilesystemOperationType operationType, public async Task> ToItems(Action updatePrimaryButtonEnabled, Action optionGenerateNewName, Action optionReplaceExisting, Action optionSkip) { - List items = new List(); + ConcurrentBag<(int Index, FilesystemOperationItemViewModel Model)> items = new ConcurrentBag<(int Index, FilesystemOperationItemViewModel Model)>(); List nonConflictingItems = IncomingItems.Except(ConflictingItems).ToList(); // Add conflicting items first - foreach (var item in ConflictingItems) + await Task.WhenAll(ConflictingItems.Select(async (item, index) => { var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(item.SourcePath, 64u, Windows.Storage.FileProperties.ThumbnailMode.ListView); - items.Add(new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip) + items.Add((index, new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip) { IsConflict = true, ItemIcon = iconData != null ? await iconData.ToBitmapAsync() : null, @@ -78,15 +79,17 @@ public async Task> ToItems(Action updateP ConflictResolveOption = FileNameConflictResolveOptionType.GenerateNewName, ItemOperation = item.OperationType, ActionTaken = false - }); - } + })); + })); + + var baseIndex = ConflictingItems.Count; // Then add non-conflicting items - foreach (var item in nonConflictingItems) + await Task.WhenAll(nonConflictingItems.Select(async (item, index) => { var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(item.SourcePath, 64u, Windows.Storage.FileProperties.ThumbnailMode.ListView); - items.Add(new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip) + items.Add((baseIndex + index, new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip) { IsConflict = false, ItemIcon = iconData != null ? await iconData.ToBitmapAsync() : null, @@ -96,10 +99,10 @@ public async Task> ToItems(Action updateP ConflictResolveOption = FileNameConflictResolveOptionType.NotAConflict, ItemOperation = item.OperationType, ActionTaken = true - }); - } + })); + })); - return items; + return items.OrderBy(i => i.Index).Select(i => i.Model).ToList(); } private string GetOperationIconGlyph(FilesystemOperationType operationType) diff --git a/Files/Helpers/UIFilesystemHelpers.cs b/Files/Helpers/UIFilesystemHelpers.cs index 291970e7804a..480d9187e231 100644 --- a/Files/Helpers/UIFilesystemHelpers.cs +++ b/Files/Helpers/UIFilesystemHelpers.cs @@ -6,7 +6,9 @@ using Files.Interacts; using Microsoft.Toolkit.Uwp; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using Windows.ApplicationModel.AppService; @@ -24,7 +26,7 @@ public static async void CutItem(IShellPage associatedInstance) { RequestedOperation = DataPackageOperation.Move }; - List items = new List(); + ConcurrentBag items = new ConcurrentBag(); FilesystemResult result = (FilesystemResult)false; var canFlush = true; @@ -33,46 +35,54 @@ public static async void CutItem(IShellPage associatedInstance) // First, reset DataGrid Rows that may be in "cut" command mode associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity(); - foreach (ListedItem listedItem in associatedInstance.SlimContentPage.SelectedItems.ToList()) + try { + await Task.WhenAll(associatedInstance.SlimContentPage.SelectedItems.ToList().Select(async listedItem => + { // FTP don't support cut, fallback to copy if (listedItem is not FtpItem) - { + { // Dim opacities accordingly listedItem.Opacity = Constants.UI.DimItemOpacity; - } - - if (listedItem is FtpItem ftpItem) - { - canFlush = false; - if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) - { - items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync()); } - else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder) + + if (listedItem is FtpItem ftpItem) { - items.Add(new FtpStorageFolder(ftpItem)); + canFlush = false; + if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) + { + items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync()); + } + else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder) + { + items.Add(new FtpStorageFolder(ftpItem)); + } } - } - else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem) - { - result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - if (!result) + else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem) { - break; + result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath) + .OnSuccess(t => items.Add(t)); + if (!result) + { + throw new IOException($"Failed to process {listedItem.ItemPath}."); + } } - } - else - { - result = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - if (!result) + else { - break; + result = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath) + .OnSuccess(t => items.Add(t)); + if (!result) + { + throw new IOException($"Failed to process {listedItem.ItemPath}."); + } } - } + })); + } + catch + { + return; } + if (result.ErrorCode == FileSystemStatusCode.NotFound) { associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity(); @@ -105,7 +115,7 @@ public static async void CutItem(IShellPage associatedInstance) var onlyStandard = items.All(x => x is StorageFile || x is StorageFolder || x is SystemStorageFile || x is SystemStorageFolder); if (onlyStandard) { - items = await items.ToStandardStorageItemsAsync(); + items = new ConcurrentBag(await items.ToStandardStorageItemsAsync()); } if (!items.Any()) { @@ -132,7 +142,7 @@ public static async Task CopyItem(IShellPage associatedInstance) { RequestedOperation = DataPackageOperation.Copy }; - List items = new List(); + ConcurrentBag items = new ConcurrentBag(); string copySourcePath = associatedInstance.FilesystemViewModel.WorkingDirectory; FilesystemResult result = (FilesystemResult)false; @@ -140,39 +150,47 @@ public static async Task CopyItem(IShellPage associatedInstance) var canFlush = true; if (associatedInstance.SlimContentPage.IsItemSelected) { - foreach (ListedItem listedItem in associatedInstance.SlimContentPage.SelectedItems.ToList()) + try { - if (listedItem is FtpItem ftpItem) + await Task.WhenAll(associatedInstance.SlimContentPage.SelectedItems.ToList().Select(async listedItem => { - canFlush = false; - if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) + if (listedItem is FtpItem ftpItem) { - items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync()); + canFlush = false; + if (listedItem.PrimaryItemAttribute == StorageItemTypes.File) + { + items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync()); + } + else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder) + { + items.Add(new FtpStorageFolder(ftpItem)); + } } - else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder) - { - items.Add(new FtpStorageFolder(ftpItem)); - } - } - else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem) - { - result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - if (!result) + else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem) { - break; + result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath) + .OnSuccess(t => items.Add(t)); + if (!result) + { + throw new IOException($"Failed to process {listedItem.ItemPath}."); + } } - } - else - { - result = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - if (!result) + else { - break; + result = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath) + .OnSuccess(t => items.Add(t)); + if (!result) + { + throw new IOException($"Failed to process {listedItem.ItemPath}."); + } } - } + })); } + catch + { + return; + } + if (result.ErrorCode == FileSystemStatusCode.Unauthorized) { // Try again with fulltrust process @@ -195,7 +213,7 @@ await connection.SendMessageAsync(new ValueSet() var onlyStandard = items.All(x => x is StorageFile || x is StorageFolder || x is SystemStorageFile || x is SystemStorageFolder); if (onlyStandard) { - items = await items.ToStandardStorageItemsAsync(); + items = new ConcurrentBag(await items.ToStandardStorageItemsAsync()); } if (!items.Any()) { diff --git a/Files/Interacts/BaseLayoutCommandImplementationModel.cs b/Files/Interacts/BaseLayoutCommandImplementationModel.cs index 3c36ffbbea2a..2b1699bd1a3d 100644 --- a/Files/Interacts/BaseLayoutCommandImplementationModel.cs +++ b/Files/Interacts/BaseLayoutCommandImplementationModel.cs @@ -199,11 +199,10 @@ await FilesystemHelpers.RestoreFromTrashAsync(StorageItemHelpers.FromPathAndType public virtual async void DeleteItem(RoutedEventArgs e) { - await FilesystemHelpers.DeleteItemsAsync( - SlimContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType( + var items = await Task.WhenAll(SlimContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType( item.ItemPath, - item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(), - true, false, true); + item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)))); + await FilesystemHelpers.DeleteItemsAsync(items, true, false, true); } public virtual void ShowFolderProperties(RoutedEventArgs e) diff --git a/Files/Views/ColumnShellPage.xaml.cs b/Files/Views/ColumnShellPage.xaml.cs index 1a3a21452ca3..ebf9ee14431e 100644 --- a/Files/Views/ColumnShellPage.xaml.cs +++ b/Files/Views/ColumnShellPage.xaml.cs @@ -616,11 +616,10 @@ private async void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, Keybo case (false, true, false, true, VirtualKey.Delete): // shift + delete, PermanentDelete if (ContentPage.IsItemSelected && !NavToolbarViewModel.IsEditModeEnabled && !InstanceViewModel.IsPageTypeSearchResults) { - await FilesystemHelpers.DeleteItemsAsync( - ContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType( + var items = await Task.WhenAll(ContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType( item.ItemPath, - item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(), - true, true, true); + item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)))); + await FilesystemHelpers.DeleteItemsAsync(items, true, true, true); } break; @@ -661,11 +660,10 @@ await FilesystemHelpers.DeleteItemsAsync( case (false, false, false, true, VirtualKey.Delete): // delete, delete item if (ContentPage.IsItemSelected && !ContentPage.IsRenamingItem && !InstanceViewModel.IsPageTypeSearchResults) { - await FilesystemHelpers.DeleteItemsAsync( - ContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType( + var items = await Task.WhenAll(ContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType( item.ItemPath, - item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(), - true, false, true); + item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)))); + await FilesystemHelpers.DeleteItemsAsync(items, true, false, true); } break; diff --git a/Files/Views/ModernShellPage.xaml.cs b/Files/Views/ModernShellPage.xaml.cs index 6ff33e258674..8e72123eefd3 100644 --- a/Files/Views/ModernShellPage.xaml.cs +++ b/Files/Views/ModernShellPage.xaml.cs @@ -661,11 +661,10 @@ private async void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, Keybo case (false, true, false, true, VirtualKey.Delete): // shift + delete, PermanentDelete if (ContentPage.IsItemSelected && !NavToolbarViewModel.IsEditModeEnabled && !InstanceViewModel.IsPageTypeSearchResults) { - await FilesystemHelpers.DeleteItemsAsync( - ContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType( + var items = await Task.WhenAll(ContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType( item.ItemPath, - item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(), - true, true, true); + item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)))); + await FilesystemHelpers.DeleteItemsAsync(items, true, true, true); } break; @@ -706,11 +705,10 @@ await FilesystemHelpers.DeleteItemsAsync( case (false, false, false, true, VirtualKey.Delete): // delete, delete item if (ContentPage.IsItemSelected && !ContentPage.IsRenamingItem && !InstanceViewModel.IsPageTypeSearchResults) { - await FilesystemHelpers.DeleteItemsAsync( - ContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType( + var items = await Task.WhenAll(ContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType( item.ItemPath, - item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(), - true, false, true); + item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)))); + await FilesystemHelpers.DeleteItemsAsync(items, true, false, true); } break;