Skip to content

Commit 700c4cd

Browse files
authored
Improve UI responsiveness for file operations with many items (#8046)
1 parent 9044e9f commit 700c4cd

14 files changed

+476
-269
lines changed

src/Files/DataModels/FilesystemItemsOperationDataModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public FilesystemItemsOperationDataModel(FilesystemOperationType operationType,
5858
public List<FilesystemOperationItemViewModel> ToItems(Action updatePrimaryButtonEnabled, Action optionGenerateNewName, Action optionReplaceExisting, Action optionSkip)
5959
{
6060
List<FilesystemOperationItemViewModel> items = new List<FilesystemOperationItemViewModel>();
61-
List<FilesystemItemsOperationItemModel> nonConflictingItems = IncomingItems.Except(ConflictingItems).ToList();
61+
IEnumerable<FilesystemItemsOperationItemModel> nonConflictingItems = IncomingItems.Except(ConflictingItems);
6262

6363
// Add conflicting items first
6464
items.AddRange(ConflictingItems.Select((item, index) =>
Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
35
using System.Threading.Tasks;
46
using System.Threading.Tasks.Dataflow;
57

@@ -19,6 +21,24 @@ internal static IEnumerable<T> CreateEnumerable<T>(this T item) =>
1921
internal static List<T> CreateList<T>(this T item) =>
2022
new List<T>() { item };
2123

24+
public static IList<T> AddIfNotPresent<T>(this IList<T> list, T element)
25+
{
26+
if (!list.Contains(element))
27+
{
28+
list.Add(element);
29+
}
30+
return list;
31+
}
32+
33+
public static IDictionary<TKey, TValue> AddIfNotPresent<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
34+
{
35+
if (!dictionary.ContainsKey(key))
36+
{
37+
dictionary.Add(key, value);
38+
}
39+
return dictionary;
40+
}
41+
2242
/// <summary>
2343
/// Executes given lambda parallelly on given data set with max degree of parallelism set up
2444
/// </summary>
@@ -27,38 +47,45 @@ internal static List<T> CreateList<T>(this T item) =>
2747
/// <param name="body">Lambda to execute on all items</param>
2848
/// <param name="maxDegreeOfParallelism">Max degree of parallelism (-1 for unbounded execution)</param>
2949
/// <returns></returns>
30-
internal static Task AsyncParallelForEach<T>(this IEnumerable<T> source, Func<T, Task> body, int maxDegreeOfParallelism)
50+
/// <param name="cts">Cancellation token, stops all remaining operations</param>
51+
/// <param name="scheduler">Task scheduler on which to execute `body`</param>
52+
/// <returns></returns>
53+
public static async Task ParallelForEach<T>(this IEnumerable<T> source, Func<T, Task> body, int maxDegreeOfParallelism = DataflowBlockOptions.Unbounded, CancellationToken cts = default, TaskScheduler scheduler = null)
3154
{
3255
var options = new ExecutionDataflowBlockOptions
3356
{
34-
MaxDegreeOfParallelism = maxDegreeOfParallelism
57+
MaxDegreeOfParallelism = maxDegreeOfParallelism,
58+
CancellationToken = cts
3559
};
60+
if (scheduler != null)
61+
options.TaskScheduler = scheduler;
3662

3763
var block = new ActionBlock<T>(body, options);
3864

3965
foreach (var item in source)
4066
block.Post(item);
4167

4268
block.Complete();
43-
return block.Completion;
69+
await block.Completion;
4470
}
4571

46-
public static IList<T> AddIfNotPresent<T>(this IList<T> list, T element)
72+
public static async Task<IList<T>> ToListAsync<T>(this IEnumerable<T> source)
4773
{
48-
if (!list.Contains(element))
49-
{
50-
list.Add(element);
51-
}
52-
return list;
74+
return await Task.Run(() => source.ToList());
5375
}
5476

55-
public static IDictionary<TKey, TValue> AddIfNotPresent<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
77+
public static IEnumerable<TResult> Zip<T1, T2, TResult>(
78+
this IEnumerable<T1> source,
79+
IEnumerable<T2> second,
80+
Func<T1, T2, int, TResult> func)
5681
{
57-
if (!dictionary.ContainsKey(key))
82+
using (var e1 = source.GetEnumerator())
83+
using (var e2 = second.GetEnumerator())
5884
{
59-
dictionary.Add(key, value);
85+
var index = 0;
86+
while (e1.MoveNext() && e2.MoveNext())
87+
yield return func(e1.Current, e2.Current, index++);
6088
}
61-
return dictionary;
6289
}
6390
}
6491
}

src/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs

Lines changed: 92 additions & 74 deletions
Large diffs are not rendered by default.

src/Files/Filesystem/FilesystemOperations/Helpers/FilesystemHelpers.cs

Lines changed: 84 additions & 33 deletions
Large diffs are not rendered by default.

src/Files/Filesystem/FilesystemOperations/Helpers/IFilesystemHelpers.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,45 @@ public interface IFilesystemHelpers : IDisposable
6262

6363
#endregion Delete
6464

65+
#region Restore
66+
67+
/// <summary>
68+
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
69+
/// </summary>
70+
/// <param name="source">The source Recycle Bin item path</param>
71+
/// <param name="destination">The destination fullPath to restore to</param>
72+
/// <param name="registerHistory">Determines whether <see cref="IStorageHistory"/> is saved</param>
73+
/// <returns><see cref="ReturnResult"/> of performed operation</returns>
74+
Task<ReturnResult> RestoreItemFromTrashAsync(IStorageItem source, string destination, bool registerHistory);
75+
76+
/// <summary>
77+
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
78+
/// </summary>
79+
/// <param name="source">The source Recycle Bin item path</param>
80+
/// <param name="destination">The destination fullPath to restore to</param>
81+
/// <param name="registerHistory">Determines whether <see cref="IStorageHistory"/> is saved</param>
82+
/// <returns><see cref="ReturnResult"/> of performed operation</returns>
83+
Task<ReturnResult> RestoreItemsFromTrashAsync(IEnumerable<IStorageItem> source, IEnumerable<string> destination, bool registerHistory);
84+
6585
/// <summary>
6686
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
6787
/// </summary>
6888
/// <param name="source">The source Recycle Bin item path</param>
6989
/// <param name="destination">The destination fullPath to restore to</param>
7090
/// <param name="registerHistory">Determines whether <see cref="IStorageHistory"/> is saved</param>
7191
/// <returns><see cref="ReturnResult"/> of performed operation</returns>
72-
Task<ReturnResult> RestoreFromTrashAsync(IStorageItemWithPath source, string destination, bool registerHistory);
92+
Task<ReturnResult> RestoreItemFromTrashAsync(IStorageItemWithPath source, string destination, bool registerHistory);
93+
94+
/// <summary>
95+
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
96+
/// </summary>
97+
/// <param name="source">The source Recycle Bin item path</param>
98+
/// <param name="destination">The destination fullPath to restore to</param>
99+
/// <param name="registerHistory">Determines whether <see cref="IStorageHistory"/> is saved</param>
100+
/// <returns><see cref="ReturnResult"/> of performed operation</returns>
101+
Task<ReturnResult> RestoreItemsFromTrashAsync(IEnumerable<IStorageItemWithPath> source, IEnumerable<string> destination, bool registerHistory);
102+
103+
#endregion Restore
73104

74105
/// <summary>
75106
/// Performs relevant operation based on <paramref name="operation"/>

src/Files/Filesystem/FilesystemOperations/IFilesystemOperations.cs

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public interface IFilesystemOperations : IDisposable
3232
/// </returns>
3333
Task<(IStorageHistory, IStorageItem)> CreateAsync(IStorageItemWithPath source, IProgress<FileSystemStatusCode> errorCode, CancellationToken cancellationToken);
3434

35-
Task<IStorageHistory> CreateShortcutItemsAsync(IEnumerable<IStorageItemWithPath> source, IEnumerable<string> destination, IProgress<float> progress, IProgress<FileSystemStatusCode> errorCode, CancellationToken cancellationToken);
35+
Task<IStorageHistory> CreateShortcutItemsAsync(IList<IStorageItemWithPath> source, IList<string> destination, IProgress<float> progress, IProgress<FileSystemStatusCode> errorCode, CancellationToken cancellationToken);
3636

3737
/// <summary>
3838
/// Copies <paramref name="source"/> to <paramref name="destination"/> fullPath
@@ -81,19 +81,19 @@ Task<IStorageHistory> CopyAsync(IStorageItemWithPath source,
8181
/// <summary>
8282
/// Copies <paramref name="source"/> to <paramref name="destination"/> fullPath
8383
/// </summary>
84-
Task<IStorageHistory> CopyItemsAsync(IEnumerable<IStorageItem> source,
85-
IEnumerable<string> destination,
86-
IEnumerable<FileNameConflictResolveOptionType> collisions,
84+
Task<IStorageHistory> CopyItemsAsync(IList<IStorageItem> source,
85+
IList<string> destination,
86+
IList<FileNameConflictResolveOptionType> collisions,
8787
IProgress<float> progress,
8888
IProgress<FileSystemStatusCode> errorCode,
8989
CancellationToken cancellationToken);
9090

9191
/// <summary>
9292
/// Copies <paramref name="source"/> to <paramref name="destination"/> fullPath
9393
/// </summary>
94-
Task<IStorageHistory> CopyItemsAsync(IEnumerable<IStorageItemWithPath> source,
95-
IEnumerable<string> destination,
96-
IEnumerable<FileNameConflictResolveOptionType> collisions,
94+
Task<IStorageHistory> CopyItemsAsync(IList<IStorageItemWithPath> source,
95+
IList<string> destination,
96+
IList<FileNameConflictResolveOptionType> collisions,
9797
IProgress<float> progress,
9898
IProgress<FileSystemStatusCode> errorCode,
9999
CancellationToken cancellationToken);
@@ -145,19 +145,19 @@ Task<IStorageHistory> MoveAsync(IStorageItemWithPath source,
145145
/// <summary>
146146
/// Moves <paramref name="source"/> to <paramref name="destination"/> fullPath
147147
/// </summary>
148-
Task<IStorageHistory> MoveItemsAsync(IEnumerable<IStorageItem> source,
149-
IEnumerable<string> destination,
150-
IEnumerable<FileNameConflictResolveOptionType> collisions,
148+
Task<IStorageHistory> MoveItemsAsync(IList<IStorageItem> source,
149+
IList<string> destination,
150+
IList<FileNameConflictResolveOptionType> collisions,
151151
IProgress<float> progress,
152152
IProgress<FileSystemStatusCode> errorCode,
153153
CancellationToken cancellationToken);
154154

155155
/// <summary>
156156
/// Moves <paramref name="source"/> to <paramref name="destination"/> fullPath
157157
/// </summary>
158-
Task<IStorageHistory> MoveItemsAsync(IEnumerable<IStorageItemWithPath> source,
159-
IEnumerable<string> destination,
160-
IEnumerable<FileNameConflictResolveOptionType> collisions,
158+
Task<IStorageHistory> MoveItemsAsync(IList<IStorageItemWithPath> source,
159+
IList<string> destination,
160+
IList<FileNameConflictResolveOptionType> collisions,
161161
IProgress<float> progress,
162162
IProgress<FileSystemStatusCode> errorCode,
163163
CancellationToken cancellationToken);
@@ -214,7 +214,7 @@ Task<IStorageHistory> DeleteAsync(IStorageItemWithPath source,
214214
/// <summary>
215215
/// Deletes provided <paramref name="source"/>
216216
/// </summary>
217-
Task<IStorageHistory> DeleteItemsAsync(IEnumerable<IStorageItem> source,
217+
Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItem> source,
218218
IProgress<float> progress,
219219
IProgress<FileSystemStatusCode> errorCode,
220220
bool permanently,
@@ -223,7 +223,7 @@ Task<IStorageHistory> DeleteItemsAsync(IEnumerable<IStorageItem> source,
223223
/// <summary>
224224
/// Deletes provided <paramref name="source"/>
225225
/// </summary>
226-
Task<IStorageHistory> DeleteItemsAsync(IEnumerable<IStorageItemWithPath> source,
226+
Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath> source,
227227
IProgress<float> progress,
228228
IProgress<FileSystemStatusCode> errorCode,
229229
bool permanently,
@@ -269,6 +269,66 @@ Task<IStorageHistory> RenameAsync(IStorageItemWithPath source,
269269
IProgress<FileSystemStatusCode> errorCode,
270270
CancellationToken cancellationToken);
271271

272+
/// <summary>
273+
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
274+
/// </summary>
275+
/// <param name="source">The source Recycle Bin item path</param>
276+
/// <param name="destination">The destination fullPath to restore to</param>
277+
/// <param name="progress">Progress of the operation</param>
278+
/// <param name="errorCode">Status of the operation</param>
279+
/// <param name="cancellationToken">Can be cancelled with <see cref="CancellationToken"/></param>
280+
/// <returns><see cref="IStorageHistory"/> where:
281+
/// <br/>
282+
/// Source: The trash item fullPath
283+
/// <br/>
284+
/// Destination: The <paramref name="destination"/> item fullPath (as <see cref="PathWithType"/>) the <paramref name="source"/> has been restored
285+
/// </returns>
286+
Task<IStorageHistory> RestoreItemsFromTrashAsync(IList<IStorageItem> source,
287+
IList<string> destination,
288+
IProgress<float> progress,
289+
IProgress<FileSystemStatusCode> errorCode,
290+
CancellationToken cancellationToken);
291+
292+
/// <summary>
293+
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
294+
/// </summary>
295+
/// <param name="source">The source Recycle Bin item path</param>
296+
/// <param name="destination">The destination fullPath to restore to</param>
297+
/// <param name="progress">Progress of the operation</param>
298+
/// <param name="errorCode">Status of the operation</param>
299+
/// <param name="cancellationToken">Can be cancelled with <see cref="CancellationToken"/></param>
300+
/// <returns><see cref="IStorageHistory"/> where:
301+
/// <br/>
302+
/// Source: The trash item fullPath
303+
/// <br/>
304+
/// Destination: The <paramref name="destination"/> item fullPath (as <see cref="PathWithType"/>) the <paramref name="source"/> has been restored
305+
/// </returns>
306+
Task<IStorageHistory> RestoreFromTrashAsync(IStorageItem source,
307+
string destination,
308+
IProgress<float> progress,
309+
IProgress<FileSystemStatusCode> errorCode,
310+
CancellationToken cancellationToken);
311+
312+
/// <summary>
313+
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
314+
/// </summary>
315+
/// <param name="source">The source Recycle Bin item path</param>
316+
/// <param name="destination">The destination fullPath to restore to</param>
317+
/// <param name="progress">Progress of the operation</param>
318+
/// <param name="errorCode">Status of the operation</param>
319+
/// <param name="cancellationToken">Can be cancelled with <see cref="CancellationToken"/></param>
320+
/// <returns><see cref="IStorageHistory"/> where:
321+
/// <br/>
322+
/// Source: The trash item fullPath
323+
/// <br/>
324+
/// Destination: The <paramref name="destination"/> item fullPath (as <see cref="PathWithType"/>) the <paramref name="source"/> has been restored
325+
/// </returns>
326+
Task<IStorageHistory> RestoreItemsFromTrashAsync(IList<IStorageItemWithPath> source,
327+
IList<string> destination,
328+
IProgress<float> progress,
329+
IProgress<FileSystemStatusCode> errorCode,
330+
CancellationToken cancellationToken);
331+
272332
/// <summary>
273333
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
274334
/// </summary>

0 commit comments

Comments
 (0)