Skip to content

Improve UI responsiveness for file operations with many items #8046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2e75034
Stop loading images when conflict dialog is closed
gave92 Jan 8, 2022
a082310
Add support for batch restore items from bin
gave92 Jan 8, 2022
284618c
Fix UI hang / move ToList() call
gave92 Jan 8, 2022
b54a5fa
Add ParallelForEach extension, limit parallel icon loading to 10
gave92 Jan 9, 2022
d0e5932
Run Linq statements in background, force use IList
gave92 Jan 9, 2022
05eebc6
Fix lock when copying many files
gave92 Jan 9, 2022
d321ed1
Simplify path check, fix wrong thread
gave92 Jan 9, 2022
96bfdc4
Do not chek conflicts when pasting same folder
gave92 Jan 9, 2022
3842a76
Optimize linq calls
gave92 Jan 9, 2022
1bc4406
Small change
gave92 Jan 10, 2022
8fd59f1
Merge remote-tracking branch 'origin/main' into delete_stop
gave92 Jan 23, 2022
84a270f
Revert CreateList => CreateEnumerable
gave92 Jan 23, 2022
fc1b171
Run file ops on background thread
gave92 Jan 23, 2022
0818a22
Revert "Run file ops on background thread"
gave92 Jan 23, 2022
0d48f24
ToListAsync in FilesystemHelpers
gave92 Jan 24, 2022
80015ce
Revert "Revert CreateList => CreateEnumerable"
gave92 Jan 24, 2022
4364867
Force use IList, avoid repetitive source-result match
gave92 Jan 24, 2022
0956e48
Removed duplicate method
gave92 Jan 24, 2022
0b3b1e0
Fix move method for SystemStorageFile
gave92 Jan 25, 2022
946d869
Use parallel for each in DragItemsStarting
gave92 Jan 30, 2022
2c33830
Optimize test for recycle bin
gave92 Jan 30, 2022
c8cdb7c
Execute on background thread
gave92 Jan 30, 2022
76ccdb2
Show progress when preparing items for copy / move / drag
gave92 Jan 30, 2022
2b3944e
Merge branch 'main' of https://github.com/files-community/Files into …
gave92 Jan 30, 2022
52a968f
Revert splitted changes
gave92 Jan 30, 2022
78c667d
No need to use Zip for ILists
gave92 Jan 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Files/DataModels/FilesystemItemsOperationDataModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public FilesystemItemsOperationDataModel(FilesystemOperationType operationType,
public List<FilesystemOperationItemViewModel> ToItems(Action updatePrimaryButtonEnabled, Action optionGenerateNewName, Action optionReplaceExisting, Action optionSkip)
{
List<FilesystemOperationItemViewModel> items = new List<FilesystemOperationItemViewModel>();
List<FilesystemItemsOperationItemModel> nonConflictingItems = IncomingItems.Except(ConflictingItems).ToList();
IEnumerable<FilesystemItemsOperationItemModel> nonConflictingItems = IncomingItems.Except(ConflictingItems);

// Add conflicting items first
items.AddRange(ConflictingItems.Select((item, index) =>
Expand Down
53 changes: 40 additions & 13 deletions src/Files/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

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

public static IList<T> AddIfNotPresent<T>(this IList<T> list, T element)
{
if (!list.Contains(element))
{
list.Add(element);
}
return list;
}

public static IDictionary<TKey, TValue> AddIfNotPresent<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
if (!dictionary.ContainsKey(key))
{
dictionary.Add(key, value);
}
return dictionary;
}

/// <summary>
/// Executes given lambda parallelly on given data set with max degree of parallelism set up
/// </summary>
Expand All @@ -27,38 +47,45 @@ internal static List<T> CreateList<T>(this T item) =>
/// <param name="body">Lambda to execute on all items</param>
/// <param name="maxDegreeOfParallelism">Max degree of parallelism (-1 for unbounded execution)</param>
/// <returns></returns>
internal static Task AsyncParallelForEach<T>(this IEnumerable<T> source, Func<T, Task> body, int maxDegreeOfParallelism)
/// <param name="cts">Cancellation token, stops all remaining operations</param>
/// <param name="scheduler">Task scheduler on which to execute `body`</param>
/// <returns></returns>
public static async Task ParallelForEach<T>(this IEnumerable<T> source, Func<T, Task> body, int maxDegreeOfParallelism = DataflowBlockOptions.Unbounded, CancellationToken cts = default, TaskScheduler scheduler = null)
{
var options = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = maxDegreeOfParallelism
MaxDegreeOfParallelism = maxDegreeOfParallelism,
CancellationToken = cts
};
if (scheduler != null)
options.TaskScheduler = scheduler;

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

foreach (var item in source)
block.Post(item);

block.Complete();
return block.Completion;
await block.Completion;
}

public static IList<T> AddIfNotPresent<T>(this IList<T> list, T element)
public static async Task<IList<T>> ToListAsync<T>(this IEnumerable<T> source)
{
if (!list.Contains(element))
{
list.Add(element);
}
return list;
return await Task.Run(() => source.ToList());
}

public static IDictionary<TKey, TValue> AddIfNotPresent<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value)
public static IEnumerable<TResult> Zip<T1, T2, TResult>(
this IEnumerable<T1> source,
IEnumerable<T2> second,
Func<T1, T2, int, TResult> func)
{
if (!dictionary.ContainsKey(key))
using (var e1 = source.GetEnumerator())
using (var e2 = second.GetEnumerator())
{
dictionary.Add(key, value);
var index = 0;
while (e1.MoveNext() && e2.MoveNext())
yield return func(e1.Current, e2.Current, index++);
}
return dictionary;
}
}
}
166 changes: 92 additions & 74 deletions src/Files/Filesystem/FilesystemOperations/FilesystemOperations.cs

Large diffs are not rendered by default.

117 changes: 84 additions & 33 deletions src/Files/Filesystem/FilesystemOperations/Helpers/FilesystemHelpers.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,45 @@ public interface IFilesystemHelpers : IDisposable

#endregion Delete

#region Restore

/// <summary>
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
/// </summary>
/// <param name="source">The source Recycle Bin item path</param>
/// <param name="destination">The destination fullPath to restore to</param>
/// <param name="registerHistory">Determines whether <see cref="IStorageHistory"/> is saved</param>
/// <returns><see cref="ReturnResult"/> of performed operation</returns>
Task<ReturnResult> RestoreItemFromTrashAsync(IStorageItem source, string destination, bool registerHistory);

/// <summary>
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
/// </summary>
/// <param name="source">The source Recycle Bin item path</param>
/// <param name="destination">The destination fullPath to restore to</param>
/// <param name="registerHistory">Determines whether <see cref="IStorageHistory"/> is saved</param>
/// <returns><see cref="ReturnResult"/> of performed operation</returns>
Task<ReturnResult> RestoreItemsFromTrashAsync(IEnumerable<IStorageItem> source, IEnumerable<string> destination, bool registerHistory);

/// <summary>
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
/// </summary>
/// <param name="source">The source Recycle Bin item path</param>
/// <param name="destination">The destination fullPath to restore to</param>
/// <param name="registerHistory">Determines whether <see cref="IStorageHistory"/> is saved</param>
/// <returns><see cref="ReturnResult"/> of performed operation</returns>
Task<ReturnResult> RestoreFromTrashAsync(IStorageItemWithPath source, string destination, bool registerHistory);
Task<ReturnResult> RestoreItemFromTrashAsync(IStorageItemWithPath source, string destination, bool registerHistory);

/// <summary>
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
/// </summary>
/// <param name="source">The source Recycle Bin item path</param>
/// <param name="destination">The destination fullPath to restore to</param>
/// <param name="registerHistory">Determines whether <see cref="IStorageHistory"/> is saved</param>
/// <returns><see cref="ReturnResult"/> of performed operation</returns>
Task<ReturnResult> RestoreItemsFromTrashAsync(IEnumerable<IStorageItemWithPath> source, IEnumerable<string> destination, bool registerHistory);

#endregion Restore

/// <summary>
/// Performs relevant operation based on <paramref name="operation"/>
Expand Down
90 changes: 75 additions & 15 deletions src/Files/Filesystem/FilesystemOperations/IFilesystemOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public interface IFilesystemOperations : IDisposable
/// </returns>
Task<(IStorageHistory, IStorageItem)> CreateAsync(IStorageItemWithPath source, IProgress<FileSystemStatusCode> errorCode, CancellationToken cancellationToken);

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

/// <summary>
/// Copies <paramref name="source"/> to <paramref name="destination"/> fullPath
Expand Down Expand Up @@ -81,19 +81,19 @@ Task<IStorageHistory> CopyAsync(IStorageItemWithPath source,
/// <summary>
/// Copies <paramref name="source"/> to <paramref name="destination"/> fullPath
/// </summary>
Task<IStorageHistory> CopyItemsAsync(IEnumerable<IStorageItem> source,
IEnumerable<string> destination,
IEnumerable<FileNameConflictResolveOptionType> collisions,
Task<IStorageHistory> CopyItemsAsync(IList<IStorageItem> source,
IList<string> destination,
IList<FileNameConflictResolveOptionType> collisions,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
CancellationToken cancellationToken);

/// <summary>
/// Copies <paramref name="source"/> to <paramref name="destination"/> fullPath
/// </summary>
Task<IStorageHistory> CopyItemsAsync(IEnumerable<IStorageItemWithPath> source,
IEnumerable<string> destination,
IEnumerable<FileNameConflictResolveOptionType> collisions,
Task<IStorageHistory> CopyItemsAsync(IList<IStorageItemWithPath> source,
IList<string> destination,
IList<FileNameConflictResolveOptionType> collisions,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
CancellationToken cancellationToken);
Expand Down Expand Up @@ -145,19 +145,19 @@ Task<IStorageHistory> MoveAsync(IStorageItemWithPath source,
/// <summary>
/// Moves <paramref name="source"/> to <paramref name="destination"/> fullPath
/// </summary>
Task<IStorageHistory> MoveItemsAsync(IEnumerable<IStorageItem> source,
IEnumerable<string> destination,
IEnumerable<FileNameConflictResolveOptionType> collisions,
Task<IStorageHistory> MoveItemsAsync(IList<IStorageItem> source,
IList<string> destination,
IList<FileNameConflictResolveOptionType> collisions,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
CancellationToken cancellationToken);

/// <summary>
/// Moves <paramref name="source"/> to <paramref name="destination"/> fullPath
/// </summary>
Task<IStorageHistory> MoveItemsAsync(IEnumerable<IStorageItemWithPath> source,
IEnumerable<string> destination,
IEnumerable<FileNameConflictResolveOptionType> collisions,
Task<IStorageHistory> MoveItemsAsync(IList<IStorageItemWithPath> source,
IList<string> destination,
IList<FileNameConflictResolveOptionType> collisions,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
CancellationToken cancellationToken);
Expand Down Expand Up @@ -214,7 +214,7 @@ Task<IStorageHistory> DeleteAsync(IStorageItemWithPath source,
/// <summary>
/// Deletes provided <paramref name="source"/>
/// </summary>
Task<IStorageHistory> DeleteItemsAsync(IEnumerable<IStorageItem> source,
Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItem> source,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
bool permanently,
Expand All @@ -223,7 +223,7 @@ Task<IStorageHistory> DeleteItemsAsync(IEnumerable<IStorageItem> source,
/// <summary>
/// Deletes provided <paramref name="source"/>
/// </summary>
Task<IStorageHistory> DeleteItemsAsync(IEnumerable<IStorageItemWithPath> source,
Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath> source,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
bool permanently,
Expand Down Expand Up @@ -269,6 +269,66 @@ Task<IStorageHistory> RenameAsync(IStorageItemWithPath source,
IProgress<FileSystemStatusCode> errorCode,
CancellationToken cancellationToken);

/// <summary>
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
/// </summary>
/// <param name="source">The source Recycle Bin item path</param>
/// <param name="destination">The destination fullPath to restore to</param>
/// <param name="progress">Progress of the operation</param>
/// <param name="errorCode">Status of the operation</param>
/// <param name="cancellationToken">Can be cancelled with <see cref="CancellationToken"/></param>
/// <returns><see cref="IStorageHistory"/> where:
/// <br/>
/// Source: The trash item fullPath
/// <br/>
/// Destination: The <paramref name="destination"/> item fullPath (as <see cref="PathWithType"/>) the <paramref name="source"/> has been restored
/// </returns>
Task<IStorageHistory> RestoreItemsFromTrashAsync(IList<IStorageItem> source,
IList<string> destination,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
CancellationToken cancellationToken);

/// <summary>
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
/// </summary>
/// <param name="source">The source Recycle Bin item path</param>
/// <param name="destination">The destination fullPath to restore to</param>
/// <param name="progress">Progress of the operation</param>
/// <param name="errorCode">Status of the operation</param>
/// <param name="cancellationToken">Can be cancelled with <see cref="CancellationToken"/></param>
/// <returns><see cref="IStorageHistory"/> where:
/// <br/>
/// Source: The trash item fullPath
/// <br/>
/// Destination: The <paramref name="destination"/> item fullPath (as <see cref="PathWithType"/>) the <paramref name="source"/> has been restored
/// </returns>
Task<IStorageHistory> RestoreFromTrashAsync(IStorageItem source,
string destination,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
CancellationToken cancellationToken);

/// <summary>
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
/// </summary>
/// <param name="source">The source Recycle Bin item path</param>
/// <param name="destination">The destination fullPath to restore to</param>
/// <param name="progress">Progress of the operation</param>
/// <param name="errorCode">Status of the operation</param>
/// <param name="cancellationToken">Can be cancelled with <see cref="CancellationToken"/></param>
/// <returns><see cref="IStorageHistory"/> where:
/// <br/>
/// Source: The trash item fullPath
/// <br/>
/// Destination: The <paramref name="destination"/> item fullPath (as <see cref="PathWithType"/>) the <paramref name="source"/> has been restored
/// </returns>
Task<IStorageHistory> RestoreItemsFromTrashAsync(IList<IStorageItemWithPath> source,
IList<string> destination,
IProgress<float> progress,
IProgress<FileSystemStatusCode> errorCode,
CancellationToken cancellationToken);

/// <summary>
/// Restores <paramref name="source"/> from the RecycleBin to <paramref name="destination"/> fullPath
/// </summary>
Expand Down
Loading