Skip to content

Commit 026a5f2

Browse files
committed
Improve performance of file operations
1 parent ea39852 commit 026a5f2

5 files changed

+101
-85
lines changed

Files/DataModels/FilesystemItemsOperationDataModel.cs

+13-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Files.Helpers;
33
using Files.ViewModels.Dialogs;
44
using System;
5+
using System.Collections.Concurrent;
56
using System.Collections.Generic;
67
using System.Linq;
78
using System.Threading.Tasks;
@@ -59,16 +60,16 @@ public FilesystemItemsOperationDataModel(FilesystemOperationType operationType,
5960

6061
public async Task<List<FilesystemOperationItemViewModel>> ToItems(Action updatePrimaryButtonEnabled, Action optionGenerateNewName, Action optionReplaceExisting, Action optionSkip)
6162
{
62-
List<FilesystemOperationItemViewModel> items = new List<FilesystemOperationItemViewModel>();
63+
ConcurrentBag<(int Index, FilesystemOperationItemViewModel Model)> items = new ConcurrentBag<(int Index, FilesystemOperationItemViewModel Model)>();
6364

6465
List<FilesystemItemsOperationItemModel> nonConflictingItems = IncomingItems.Except(ConflictingItems).ToList();
6566

6667
// Add conflicting items first
67-
foreach (var item in ConflictingItems)
68+
await Task.WhenAll(ConflictingItems.Select(async (item, index) =>
6869
{
6970
var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(item.SourcePath, 64u, Windows.Storage.FileProperties.ThumbnailMode.ListView);
7071

71-
items.Add(new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip)
72+
items.Add((index, new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip)
7273
{
7374
IsConflict = true,
7475
ItemIcon = iconData != null ? await iconData.ToBitmapAsync() : null,
@@ -78,15 +79,17 @@ public async Task<List<FilesystemOperationItemViewModel>> ToItems(Action updateP
7879
ConflictResolveOption = FileNameConflictResolveOptionType.GenerateNewName,
7980
ItemOperation = item.OperationType,
8081
ActionTaken = false
81-
});
82-
}
82+
}));
83+
}));
84+
85+
var baseIndex = ConflictingItems.Count;
8386

8487
// Then add non-conflicting items
85-
foreach (var item in nonConflictingItems)
88+
await Task.WhenAll(nonConflictingItems.Select(async (item, index) =>
8689
{
8790
var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(item.SourcePath, 64u, Windows.Storage.FileProperties.ThumbnailMode.ListView);
8891

89-
items.Add(new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip)
92+
items.Add((baseIndex + index, new FilesystemOperationItemViewModel(updatePrimaryButtonEnabled, optionGenerateNewName, optionReplaceExisting, optionSkip)
9093
{
9194
IsConflict = false,
9295
ItemIcon = iconData != null ? await iconData.ToBitmapAsync() : null,
@@ -96,10 +99,10 @@ public async Task<List<FilesystemOperationItemViewModel>> ToItems(Action updateP
9699
ConflictResolveOption = FileNameConflictResolveOptionType.NotAConflict,
97100
ItemOperation = item.OperationType,
98101
ActionTaken = true
99-
});
100-
}
102+
}));
103+
}));
101104

102-
return items;
105+
return items.OrderBy(i => i.Index).Select(i => i.Model).ToList();
103106
}
104107

105108
private string GetOperationIconGlyph(FilesystemOperationType operationType)

Files/Helpers/UIFilesystemHelpers.cs

+73-55
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
using Files.Interacts;
77
using Microsoft.Toolkit.Uwp;
88
using System;
9+
using System.Collections.Concurrent;
910
using System.Collections.Generic;
11+
using System.IO;
1012
using System.Linq;
1113
using System.Threading.Tasks;
1214
using Windows.ApplicationModel.AppService;
@@ -24,7 +26,7 @@ public static async void CutItem(IShellPage associatedInstance)
2426
{
2527
RequestedOperation = DataPackageOperation.Move
2628
};
27-
List<IStorageItem> items = new List<IStorageItem>();
29+
ConcurrentBag<IStorageItem> items = new ConcurrentBag<IStorageItem>();
2830
FilesystemResult result = (FilesystemResult)false;
2931

3032
var canFlush = true;
@@ -33,46 +35,54 @@ public static async void CutItem(IShellPage associatedInstance)
3335
// First, reset DataGrid Rows that may be in "cut" command mode
3436
associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
3537

36-
foreach (ListedItem listedItem in associatedInstance.SlimContentPage.SelectedItems.ToList())
38+
try
3739
{
40+
await Task.WhenAll(associatedInstance.SlimContentPage.SelectedItems.ToList().Select(async listedItem =>
41+
{
3842
// FTP don't support cut, fallback to copy
3943
if (listedItem is not FtpItem)
40-
{
44+
{
4145
// Dim opacities accordingly
4246
listedItem.Opacity = Constants.UI.DimItemOpacity;
43-
}
44-
45-
if (listedItem is FtpItem ftpItem)
46-
{
47-
canFlush = false;
48-
if (listedItem.PrimaryItemAttribute == StorageItemTypes.File)
49-
{
50-
items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync());
5147
}
52-
else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder)
48+
49+
if (listedItem is FtpItem ftpItem)
5350
{
54-
items.Add(new FtpStorageFolder(ftpItem));
51+
canFlush = false;
52+
if (listedItem.PrimaryItemAttribute == StorageItemTypes.File)
53+
{
54+
items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync());
55+
}
56+
else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder)
57+
{
58+
items.Add(new FtpStorageFolder(ftpItem));
59+
}
5560
}
56-
}
57-
else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem)
58-
{
59-
result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath)
60-
.OnSuccess(t => items.Add(t));
61-
if (!result)
61+
else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem)
6262
{
63-
break;
63+
result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath)
64+
.OnSuccess(t => items.Add(t));
65+
if (!result)
66+
{
67+
throw new IOException($"Failed to process {listedItem.ItemPath}.");
68+
}
6469
}
65-
}
66-
else
67-
{
68-
result = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath)
69-
.OnSuccess(t => items.Add(t));
70-
if (!result)
70+
else
7171
{
72-
break;
72+
result = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath)
73+
.OnSuccess(t => items.Add(t));
74+
if (!result)
75+
{
76+
throw new IOException($"Failed to process {listedItem.ItemPath}.");
77+
}
7378
}
74-
}
79+
}));
80+
}
81+
catch
82+
{
83+
return;
7584
}
85+
7686
if (result.ErrorCode == FileSystemStatusCode.NotFound)
7787
{
7888
associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
@@ -105,7 +115,7 @@ public static async void CutItem(IShellPage associatedInstance)
105115
var onlyStandard = items.All(x => x is StorageFile || x is StorageFolder || x is SystemStorageFile || x is SystemStorageFolder);
106116
if (onlyStandard)
107117
{
108-
items = await items.ToStandardStorageItemsAsync();
118+
items = new ConcurrentBag<IStorageItem>(await items.ToStandardStorageItemsAsync());
109119
}
110120
if (!items.Any())
111121
{
@@ -132,47 +142,55 @@ public static async Task CopyItem(IShellPage associatedInstance)
132142
{
133143
RequestedOperation = DataPackageOperation.Copy
134144
};
135-
List<IStorageItem> items = new List<IStorageItem>();
145+
ConcurrentBag<IStorageItem> items = new ConcurrentBag<IStorageItem>();
136146

137147
string copySourcePath = associatedInstance.FilesystemViewModel.WorkingDirectory;
138148
FilesystemResult result = (FilesystemResult)false;
139149

140150
var canFlush = true;
141151
if (associatedInstance.SlimContentPage.IsItemSelected)
142152
{
143-
foreach (ListedItem listedItem in associatedInstance.SlimContentPage.SelectedItems.ToList())
153+
try
144154
{
145-
if (listedItem is FtpItem ftpItem)
155+
await Task.WhenAll(associatedInstance.SlimContentPage.SelectedItems.ToList().Select(async listedItem =>
146156
{
147-
canFlush = false;
148-
if (listedItem.PrimaryItemAttribute == StorageItemTypes.File)
157+
if (listedItem is FtpItem ftpItem)
149158
{
150-
items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync());
159+
canFlush = false;
160+
if (listedItem.PrimaryItemAttribute == StorageItemTypes.File)
161+
{
162+
items.Add(await new FtpStorageFile(ftpItem).ToStorageFileAsync());
163+
}
164+
else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder)
165+
{
166+
items.Add(new FtpStorageFolder(ftpItem));
167+
}
151168
}
152-
else if (listedItem.PrimaryItemAttribute == StorageItemTypes.Folder)
153-
{
154-
items.Add(new FtpStorageFolder(ftpItem));
155-
}
156-
}
157-
else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem)
158-
{
159-
result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath)
160-
.OnSuccess(t => items.Add(t));
161-
if (!result)
169+
else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem)
162170
{
163-
break;
171+
result = await associatedInstance.FilesystemViewModel.GetFileFromPathAsync(listedItem.ItemPath)
172+
.OnSuccess(t => items.Add(t));
173+
if (!result)
174+
{
175+
throw new IOException($"Failed to process {listedItem.ItemPath}.");
176+
}
164177
}
165-
}
166-
else
167-
{
168-
result = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath)
169-
.OnSuccess(t => items.Add(t));
170-
if (!result)
178+
else
171179
{
172-
break;
180+
result = await associatedInstance.FilesystemViewModel.GetFolderFromPathAsync(listedItem.ItemPath)
181+
.OnSuccess(t => items.Add(t));
182+
if (!result)
183+
{
184+
throw new IOException($"Failed to process {listedItem.ItemPath}.");
185+
}
173186
}
174-
}
187+
}));
175188
}
189+
catch
190+
{
191+
return;
192+
}
193+
176194
if (result.ErrorCode == FileSystemStatusCode.Unauthorized)
177195
{
178196
// Try again with fulltrust process
@@ -195,7 +213,7 @@ await connection.SendMessageAsync(new ValueSet()
195213
var onlyStandard = items.All(x => x is StorageFile || x is StorageFolder || x is SystemStorageFile || x is SystemStorageFolder);
196214
if (onlyStandard)
197215
{
198-
items = await items.ToStandardStorageItemsAsync();
216+
items = new ConcurrentBag<IStorageItem>(await items.ToStandardStorageItemsAsync());
199217
}
200218
if (!items.Any())
201219
{

Files/Interacts/BaseLayoutCommandImplementationModel.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,10 @@ await FilesystemHelpers.RestoreFromTrashAsync(StorageItemHelpers.FromPathAndType
199199

200200
public virtual async void DeleteItem(RoutedEventArgs e)
201201
{
202-
await FilesystemHelpers.DeleteItemsAsync(
203-
SlimContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType(
202+
var items = await Task.WhenAll(SlimContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType(
204203
item.ItemPath,
205-
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(),
206-
true, false, true);
204+
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory))));
205+
await FilesystemHelpers.DeleteItemsAsync(items, true, false, true);
207206
}
208207

209208
public virtual void ShowFolderProperties(RoutedEventArgs e)

Files/Views/ColumnShellPage.xaml.cs

+6-8
Original file line numberDiff line numberDiff line change
@@ -616,11 +616,10 @@ private async void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, Keybo
616616
case (false, true, false, true, VirtualKey.Delete): // shift + delete, PermanentDelete
617617
if (ContentPage.IsItemSelected && !NavToolbarViewModel.IsEditModeEnabled && !InstanceViewModel.IsPageTypeSearchResults)
618618
{
619-
await FilesystemHelpers.DeleteItemsAsync(
620-
ContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType(
619+
var items = await Task.WhenAll(ContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType(
621620
item.ItemPath,
622-
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(),
623-
true, true, true);
621+
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory))));
622+
await FilesystemHelpers.DeleteItemsAsync(items, true, true, true);
624623
}
625624

626625
break;
@@ -661,11 +660,10 @@ await FilesystemHelpers.DeleteItemsAsync(
661660
case (false, false, false, true, VirtualKey.Delete): // delete, delete item
662661
if (ContentPage.IsItemSelected && !ContentPage.IsRenamingItem && !InstanceViewModel.IsPageTypeSearchResults)
663662
{
664-
await FilesystemHelpers.DeleteItemsAsync(
665-
ContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType(
663+
var items = await Task.WhenAll(ContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType(
666664
item.ItemPath,
667-
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(),
668-
true, false, true);
665+
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory))));
666+
await FilesystemHelpers.DeleteItemsAsync(items, true, false, true);
669667
}
670668

671669
break;

Files/Views/ModernShellPage.xaml.cs

+6-8
Original file line numberDiff line numberDiff line change
@@ -661,11 +661,10 @@ private async void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, Keybo
661661
case (false, true, false, true, VirtualKey.Delete): // shift + delete, PermanentDelete
662662
if (ContentPage.IsItemSelected && !NavToolbarViewModel.IsEditModeEnabled && !InstanceViewModel.IsPageTypeSearchResults)
663663
{
664-
await FilesystemHelpers.DeleteItemsAsync(
665-
ContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType(
664+
var items = await Task.WhenAll(ContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType(
666665
item.ItemPath,
667-
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(),
668-
true, true, true);
666+
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory))));
667+
await FilesystemHelpers.DeleteItemsAsync(items, true, true, true);
669668
}
670669

671670
break;
@@ -706,11 +705,10 @@ await FilesystemHelpers.DeleteItemsAsync(
706705
case (false, false, false, true, VirtualKey.Delete): // delete, delete item
707706
if (ContentPage.IsItemSelected && !ContentPage.IsRenamingItem && !InstanceViewModel.IsPageTypeSearchResults)
708707
{
709-
await FilesystemHelpers.DeleteItemsAsync(
710-
ContentPage.SelectedItems.Select((item) => StorageItemHelpers.FromPathAndType(
708+
var items = await Task.WhenAll(ContentPage.SelectedItems.Select((item) => Task.Run(() => StorageItemHelpers.FromPathAndType(
711709
item.ItemPath,
712-
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory)).ToList(),
713-
true, false, true);
710+
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory))));
711+
await FilesystemHelpers.DeleteItemsAsync(items, true, false, true);
714712
}
715713

716714
break;

0 commit comments

Comments
 (0)