From 8c2d0b1b47a79e4322300e8ba4471f3c60c54f96 Mon Sep 17 00:00:00 2001 From: Steve Date: Mon, 6 Sep 2021 22:22:41 +0800 Subject: [PATCH] Improve performance of watcher, operations and props loading (#6028) --- .../FilesystemItemsOperationDataModel.cs | 23 +- Files/Files.csproj | 2 +- Files/Filesystem/ListedItem.cs | 12 +- Files/Helpers/AsyncManualResetEvent.cs | 61 +++ .../FileListCache/FileListCacheController.cs | 30 +- .../PersistentSQLiteCacheAdapter.cs | 128 ------ Files/Helpers/UIFilesystemHelpers.cs | 128 +++--- .../BaseLayoutCommandImplementationModel.cs | 7 +- Files/ViewModels/ItemViewModel.cs | 401 ++++++++++-------- Files/Views/ColumnShellPage.xaml.cs | 14 +- Files/Views/ModernShellPage.xaml.cs | 14 +- 11 files changed, 408 insertions(+), 412 deletions(-) create mode 100644 Files/Helpers/AsyncManualResetEvent.cs delete mode 100644 Files/Helpers/FileListCache/PersistentSQLiteCacheAdapter.cs 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/Files.csproj b/Files/Files.csproj index 16dcae0cc0d5..c1be3fef9c27 100644 --- a/Files/Files.csproj +++ b/Files/Files.csproj @@ -266,7 +266,6 @@ - @@ -275,6 +274,7 @@ + diff --git a/Files/Filesystem/ListedItem.cs b/Files/Filesystem/ListedItem.cs index 66b00378ecc6..9f8d92b120cd 100644 --- a/Files/Filesystem/ListedItem.cs +++ b/Files/Filesystem/ListedItem.cs @@ -13,6 +13,7 @@ using System.Collections.ObjectModel; using System.IO; using System.Linq; +using System.Threading; using Windows.Storage; using Windows.UI.Xaml.Media.Imaging; @@ -22,7 +23,16 @@ public class ListedItem : ObservableObject, IGroupableItem { public bool IsHiddenItem { get; set; } = false; public StorageItemTypes PrimaryItemAttribute { get; set; } - public bool ItemPropertiesInitialized { get; set; } = false; + + private volatile int itemPropertiesInitialized = 0; + public bool ItemPropertiesInitialized + { + get => itemPropertiesInitialized == 1; + set + { + Interlocked.Exchange(ref itemPropertiesInitialized, value ? 1 : 0); + } + } public string ItemTooltipText { diff --git a/Files/Helpers/AsyncManualResetEvent.cs b/Files/Helpers/AsyncManualResetEvent.cs new file mode 100644 index 000000000000..79665e7698a7 --- /dev/null +++ b/Files/Helpers/AsyncManualResetEvent.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Files.Helpers +{ + public class AsyncManualResetEvent + { + private volatile TaskCompletionSource m_tcs = new TaskCompletionSource(); + + public async Task WaitAsync(CancellationToken cancellationToken = default) + { + var tcs = m_tcs; + var cancelTcs = new TaskCompletionSource(); + + cancellationToken.Register( + s => ((TaskCompletionSource)s).TrySetCanceled(), cancelTcs); + + await await Task.WhenAny(tcs.Task, cancelTcs.Task); + } + + private async Task Delay(int milliseconds) + { + await Task.Delay(milliseconds); + return false; + } + + public async Task WaitAsync(int milliseconds, CancellationToken cancellationToken = default) + { + var tcs = m_tcs; + var cancelTcs = new TaskCompletionSource(); + + cancellationToken.Register( + s => ((TaskCompletionSource)s).TrySetCanceled(), cancelTcs); + + return await await Task.WhenAny(tcs.Task, cancelTcs.Task, Delay(milliseconds)); + } + + public void Set() + { + var tcs = m_tcs; + Task.Factory.StartNew(s => ((TaskCompletionSource)s).TrySetResult(true), + tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default); + tcs.Task.Wait(); + } + + public void Reset() + { + while (true) + { + var tcs = m_tcs; + if (!tcs.Task.IsCompleted || + Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource(), tcs) == tcs) + return; + } + } + } +} diff --git a/Files/Helpers/FileListCache/FileListCacheController.cs b/Files/Helpers/FileListCache/FileListCacheController.cs index ca623bcd55ed..45d8491ee1a1 100644 --- a/Files/Helpers/FileListCache/FileListCacheController.cs +++ b/Files/Helpers/FileListCache/FileListCacheController.cs @@ -1,5 +1,4 @@ -using Files.Common; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; @@ -14,40 +13,31 @@ public static FileListCacheController GetInstance() return instance ??= new FileListCacheController(); } - private readonly IFileListCache persistentAdapter; - private FileListCacheController() { - persistentAdapter = new PersistentSQLiteCacheAdapter(); } - private readonly Dictionary fileNamesCache = new Dictionary(); + private readonly ConcurrentDictionary fileNamesCache = new ConcurrentDictionary(); - public async Task ReadFileDisplayNameFromCache(string path, CancellationToken cancellationToken) + public Task ReadFileDisplayNameFromCache(string path, CancellationToken cancellationToken) { - var displayName = fileNamesCache.Get(path, (string)null); - if (displayName == null) + if (fileNamesCache.TryGetValue(path, out var displayName)) { - displayName = await persistentAdapter.ReadFileDisplayNameFromCache(path, cancellationToken); - if (displayName != null) - { - fileNamesCache[path] = displayName; - } + return Task.FromResult(displayName); } - return displayName; + + return Task.FromResult(null); } public Task SaveFileDisplayNameToCache(string path, string displayName) { if (displayName == null) { - fileNamesCache.Remove(path); - return persistentAdapter.SaveFileDisplayNameToCache(path, displayName); + fileNamesCache.TryRemove(path, out _); } - fileNamesCache[path] = displayName; - // save entry to persistent cache in background - return persistentAdapter.SaveFileDisplayNameToCache(path, displayName); + fileNamesCache[path] = displayName; + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/Files/Helpers/FileListCache/PersistentSQLiteCacheAdapter.cs b/Files/Helpers/FileListCache/PersistentSQLiteCacheAdapter.cs deleted file mode 100644 index 27779a40d4c5..000000000000 --- a/Files/Helpers/FileListCache/PersistentSQLiteCacheAdapter.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Microsoft.Data.Sqlite; -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Windows.Storage; - -namespace Files.Helpers.FileListCache -{ - internal class PersistentSQLiteCacheAdapter : IFileListCache, IDisposable - { - private SqliteConnection connection; - private bool disposedValue; - - public async Task SaveFileDisplayNameToCache(string path, string displayName) - { - if (!await InitializeIfNeeded()) - { - return; - } - try - { - if (displayName == null) - { - using var deleteCommand = new SqliteCommand("DELETE FROM FileDisplayNameCache WHERE Id = @Id", connection); - deleteCommand.Parameters.Add("@Id", SqliteType.Text).Value = path; - await deleteCommand.ExecuteNonQueryAsync(); - return; - } - - using var cmd = new SqliteCommand("SELECT Id FROM FileDisplayNameCache WHERE Id = @Id", connection); - cmd.Parameters.Add("@Id", SqliteType.Text).Value = path; - using var reader = await cmd.ExecuteReaderAsync(); - if (reader.HasRows) - { - // need to update entry - using var updateCommand = new SqliteCommand("UPDATE FileDisplayNameCache SET DisplayName = @DisplayName WHERE Id = @Id", connection); - updateCommand.Parameters.Add("@Id", SqliteType.Text).Value = path; - updateCommand.Parameters.Add("@DisplayName", SqliteType.Text).Value = displayName; - await updateCommand.ExecuteNonQueryAsync(); - } - else - { - // need to insert entry - using var insertCommand = new SqliteCommand("INSERT INTO FileDisplayNameCache (Id, DisplayName) VALUES (@Id, @DisplayName)", connection); - insertCommand.Parameters.Add("@Id", SqliteType.Text).Value = path; - insertCommand.Parameters.Add("@DisplayName", SqliteType.Text).Value = displayName; - await insertCommand.ExecuteNonQueryAsync(); - } - } - catch (Exception ex) - { - Debug.WriteLine(ex.ToString()); - } - } - - public async Task ReadFileDisplayNameFromCache(string path, CancellationToken cancellationToken) - { - if (!await InitializeIfNeeded()) - { - return null; - } - try - { - using var cmd = new SqliteCommand("SELECT DisplayName FROM FileDisplayNameCache WHERE Id = @Id", connection); - cmd.Parameters.Add("@Id", SqliteType.Text).Value = path; - - using var reader = await cmd.ExecuteReaderAsync(cancellationToken); - if (!await reader.ReadAsync()) - { - return null; - } - return reader.GetString(0); - } - catch (Exception ex) - { - Debug.WriteLine(ex.ToString()); - return null; - } - } - - public void Dispose() - { - if (!disposedValue) - { - connection?.Dispose(); - disposedValue = true; - } - } - - private void RunCleanupRoutine() - { - } - - private async Task InitializeIfNeeded() - { - if (disposedValue) return false; - if (connection != null) return true; - - string dbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "cache.db"); - try - { - SQLitePCL.Batteries_V2.Init(); - - connection = new SqliteConnection($"Data Source='{dbPath}'"); - connection.Open(); - - // create db schema - var createFileDisplayNameCacheTable = @"CREATE TABLE IF NOT EXISTS ""FileDisplayNameCache"" ( - ""Id"" VARCHAR(5000) NOT NULL, - ""DisplayName"" TEXT NOT NULL, - PRIMARY KEY(""Id"") - )"; - using var cmdFileDisplayNameCacheTable = new SqliteCommand(createFileDisplayNameCacheTable, connection); - await cmdFileDisplayNameCacheTable.ExecuteNonQueryAsync(); - - RunCleanupRoutine(); - return true; - } - catch (Exception ex) - { - App.Logger.Warn(ex, $"Failed initializing database with path: {dbPath}"); - return false; - } - } - } -} \ No newline at end of file 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/ViewModels/ItemViewModel.cs b/Files/ViewModels/ItemViewModel.cs index 4bae49dda194..544925b7b3f6 100644 --- a/Files/ViewModels/ItemViewModel.cs +++ b/Files/ViewModels/ItemViewModel.cs @@ -44,10 +44,10 @@ namespace Files.ViewModels { public class ItemViewModel : ObservableObject, IDisposable { - private readonly SemaphoreSlim enumFolderSemaphore, loadExtendedPropsSemaphore; + private readonly SemaphoreSlim enumFolderSemaphore; private readonly ConcurrentQueue<(uint Action, string FileName)> operationQueue; private readonly ConcurrentDictionary itemLoadQueue; - private readonly ManualResetEventSlim operationEvent, itemLoadEvent; + private readonly AsyncManualResetEvent operationEvent, itemLoadEvent; private IntPtr hWatchDir; private IAsyncAction aWatcherAction; @@ -349,10 +349,9 @@ public ItemViewModel(FolderSettingsViewModel folderSettingsViewModel) addFilesCTS = new CancellationTokenSource(); semaphoreCTS = new CancellationTokenSource(); loadPropsCTS = new CancellationTokenSource(); - operationEvent = new ManualResetEventSlim(); - itemLoadEvent = new ManualResetEventSlim(); + operationEvent = new AsyncManualResetEvent(); enumFolderSemaphore = new SemaphoreSlim(1, 1); - loadExtendedPropsSemaphore = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount); + itemLoadEvent = new AsyncManualResetEvent(); shouldDisplayFileExtensions = App.AppSettings.ShowFileExtensions; AppServiceConnectionHelper.ConnectionChanged += AppServiceConnectionHelper_ConnectionChanged; @@ -907,161 +906,164 @@ public async Task LoadExtendedItemProperties(ListedItem item, uint thumbnailSize return; } - try - { - itemLoadQueue[item.ItemPath] = false; - await loadExtendedPropsSemaphore.WaitAsync(loadPropsCTS.Token); - if (itemLoadQueue.TryGetValue(item.ItemPath, out var canceled) && canceled) - { - loadExtendedPropsSemaphore.Release(); - return; - } - } - catch (OperationCanceledException) - { - return; - } - finally - { - itemLoadQueue.TryRemove(item.ItemPath, out _); - } + itemLoadQueue[item.ItemPath] = false; - item.ItemPropertiesInitialized = true; + var cts = loadPropsCTS; - await Task.Run(async () => + try { - try - { - itemLoadEvent.Wait(loadPropsCTS.Token); - } - catch (OperationCanceledException) + await Task.Run(async () => { - loadExtendedPropsSemaphore.Release(); - return; - } + await itemLoadEvent.WaitAsync(cts.Token); - var wasSyncStatusLoaded = false; - ImageSource groupImage = null; - bool loadGroupHeaderInfo = false; - GroupedCollection gp = null; - try - { - bool isFileTypeGroupMode = folderSettings.DirectoryGroupOption == GroupOption.FileType; - BaseStorageFile matchingStorageFile = null; - if (item.Key != null && FilesAndFolders.IsGrouped && FilesAndFolders.GetExtendedGroupHeaderInfo != null) + if (itemLoadQueue.TryGetValue(item.ItemPath, out var canceled) && canceled) { - gp = FilesAndFolders.GroupedCollection.Where(x => x.Model.Key == item.Key).FirstOrDefault(); - loadGroupHeaderInfo = !(gp is null) && !gp.Model.Initialized && !(gp.GetExtendedGroupHeaderInfo is null); + return; } - if (item.IsLibraryItem || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsZipItem) + item.ItemPropertiesInitialized = true; + var wasSyncStatusLoaded = false; + ImageSource groupImage = null; + bool loadGroupHeaderInfo = false; + GroupedCollection gp = null; + try { - if (!item.IsShortcutItem && !item.IsHiddenItem && !FtpHelpers.IsFtpPath(item.ItemPath)) + bool isFileTypeGroupMode = folderSettings.DirectoryGroupOption == GroupOption.FileType; + BaseStorageFile matchingStorageFile = null; + if (item.Key != null && FilesAndFolders.IsGrouped && FilesAndFolders.GetExtendedGroupHeaderInfo != null) { - matchingStorageFile = await GetFileFromPathAsync(item.ItemPath); - if (matchingStorageFile != null) - { - await LoadItemThumbnail(item, thumbnailSize, matchingStorageFile, true); + gp = FilesAndFolders.GroupedCollection.Where(x => x.Model.Key == item.Key).FirstOrDefault(); + loadGroupHeaderInfo = !(gp is null) && !gp.Model.Initialized && !(gp.GetExtendedGroupHeaderInfo is null); + } - var syncStatus = await CheckCloudDriveSyncStatusAsync(matchingStorageFile); - var fileFRN = await FileTagsHelper.GetFileFRN(matchingStorageFile); - var fileTag = FileTagsHelper.ReadFileTag(item.ItemPath); - await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => + if (item.IsLibraryItem || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsZipItem) + { + if (!item.IsShortcutItem && !item.IsHiddenItem && !FtpHelpers.IsFtpPath(item.ItemPath)) + { + cts.Token.ThrowIfCancellationRequested(); + matchingStorageFile = await GetFileFromPathAsync(item.ItemPath); + if (matchingStorageFile != null) { - item.FolderRelativeId = matchingStorageFile.FolderRelativeId; - item.ItemType = matchingStorageFile.DisplayType; - item.SyncStatusUI = CloudDriveSyncStatusUI.FromCloudDriveSyncStatus(syncStatus); - item.FileFRN = fileFRN; - item.FileTag = fileTag; - }, Windows.System.DispatcherQueuePriority.Low); - FileTagsHelper.DbInstance.SetTag(item.ItemPath, item.FileFRN, item.FileTag); - wasSyncStatusLoaded = true; + cts.Token.ThrowIfCancellationRequested(); + await LoadItemThumbnail(item, thumbnailSize, matchingStorageFile, true); + + var syncStatus = await CheckCloudDriveSyncStatusAsync(matchingStorageFile); + var fileFRN = await FileTagsHelper.GetFileFRN(matchingStorageFile); + var fileTag = FileTagsHelper.ReadFileTag(item.ItemPath); + + cts.Token.ThrowIfCancellationRequested(); + await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => + { + item.FolderRelativeId = matchingStorageFile.FolderRelativeId; + item.ItemType = matchingStorageFile.DisplayType; + item.SyncStatusUI = CloudDriveSyncStatusUI.FromCloudDriveSyncStatus(syncStatus); + item.FileFRN = fileFRN; + item.FileTag = fileTag; + }, Windows.System.DispatcherQueuePriority.Low); + FileTagsHelper.DbInstance.SetTag(item.ItemPath, item.FileFRN, item.FileTag); + wasSyncStatusLoaded = true; + } + } + if (!wasSyncStatusLoaded) + { + await LoadItemThumbnail(item, thumbnailSize, null, true); } } - if (!wasSyncStatusLoaded) - { - await LoadItemThumbnail(item, thumbnailSize, null, true); - } - } - else - { - if (!item.IsShortcutItem && !item.IsHiddenItem && !FtpHelpers.IsFtpPath(item.ItemPath)) + else { - BaseStorageFolder matchingStorageFolder = await GetFolderFromPathAsync(item.ItemPath); - if (matchingStorageFolder != null) + if (!item.IsShortcutItem && !item.IsHiddenItem && !FtpHelpers.IsFtpPath(item.ItemPath)) { - await LoadItemThumbnail(item, thumbnailSize, matchingStorageFolder, true); - if (matchingStorageFolder.DisplayName != item.ItemName && !matchingStorageFolder.DisplayName.StartsWith("$R")) + cts.Token.ThrowIfCancellationRequested(); + BaseStorageFolder matchingStorageFolder = await GetFolderFromPathAsync(item.ItemPath); + if (matchingStorageFolder != null) { - await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => - { - item.ItemName = matchingStorageFolder.DisplayName; - }); - await fileListCache.SaveFileDisplayNameToCache(item.ItemPath, matchingStorageFolder.DisplayName); - if (folderSettings.DirectorySortOption == SortOption.Name && !isLoadingItems) + cts.Token.ThrowIfCancellationRequested(); + await LoadItemThumbnail(item, thumbnailSize, matchingStorageFolder, true); + if (matchingStorageFolder.DisplayName != item.ItemName && !matchingStorageFolder.DisplayName.StartsWith("$R")) { - await OrderFilesAndFoldersAsync(); - await ApplySingleFileChangeAsync(item); + cts.Token.ThrowIfCancellationRequested(); + await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => + { + item.ItemName = matchingStorageFolder.DisplayName; + }); + await fileListCache.SaveFileDisplayNameToCache(item.ItemPath, matchingStorageFolder.DisplayName); + if (folderSettings.DirectorySortOption == SortOption.Name && !isLoadingItems) + { + await OrderFilesAndFoldersAsync(); + await ApplySingleFileChangeAsync(item); + } } - } - var syncStatus = await CheckCloudDriveSyncStatusAsync(matchingStorageFolder); - var fileFRN = await FileTagsHelper.GetFileFRN(matchingStorageFolder); - var fileTag = FileTagsHelper.ReadFileTag(item.ItemPath); - await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => - { - item.FolderRelativeId = matchingStorageFolder.FolderRelativeId; - item.ItemType = matchingStorageFolder.DisplayType; - item.SyncStatusUI = CloudDriveSyncStatusUI.FromCloudDriveSyncStatus(syncStatus); - item.FileFRN = fileFRN; - item.FileTag = fileTag; - }, Windows.System.DispatcherQueuePriority.Low); - FileTagsHelper.DbInstance.SetTag(item.ItemPath, item.FileFRN, item.FileTag); - wasSyncStatusLoaded = true; + cts.Token.ThrowIfCancellationRequested(); + var syncStatus = await CheckCloudDriveSyncStatusAsync(matchingStorageFolder); + var fileFRN = await FileTagsHelper.GetFileFRN(matchingStorageFolder); + var fileTag = FileTagsHelper.ReadFileTag(item.ItemPath); + cts.Token.ThrowIfCancellationRequested(); + await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => + { + item.FolderRelativeId = matchingStorageFolder.FolderRelativeId; + item.ItemType = matchingStorageFolder.DisplayType; + item.SyncStatusUI = CloudDriveSyncStatusUI.FromCloudDriveSyncStatus(syncStatus); + item.FileFRN = fileFRN; + item.FileTag = fileTag; + }, Windows.System.DispatcherQueuePriority.Low); + FileTagsHelper.DbInstance.SetTag(item.ItemPath, item.FileFRN, item.FileTag); + wasSyncStatusLoaded = true; + } + } + if (!wasSyncStatusLoaded) + { + cts.Token.ThrowIfCancellationRequested(); + await LoadItemThumbnail(item, thumbnailSize, null, true); } } - if (!wasSyncStatusLoaded) + + if (loadGroupHeaderInfo && isFileTypeGroupMode) { - await LoadItemThumbnail(item, thumbnailSize, null, true); + cts.Token.ThrowIfCancellationRequested(); + groupImage = await GetItemTypeGroupIcon(item, matchingStorageFile); } } - - if (loadGroupHeaderInfo && isFileTypeGroupMode) + catch (Exception ex) { - groupImage = await GetItemTypeGroupIcon(item, matchingStorageFile); } - } - catch (Exception ex) - { - } - finally - { - if (!wasSyncStatusLoaded) + finally { - await FilesystemTasks.Wrap(async () => + if (!wasSyncStatusLoaded) { - var fileTag = FileTagsHelper.ReadFileTag(item.ItemPath); - await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => + cts.Token.ThrowIfCancellationRequested(); + await FilesystemTasks.Wrap(async () => { - item.SyncStatusUI = new CloudDriveSyncStatusUI() { LoadSyncStatus = false }; // Reset cloud sync status icon - item.FileTag = fileTag; - }, Windows.System.DispatcherQueuePriority.Low); - FileTagsHelper.DbInstance.SetTag(item.ItemPath, item.FileFRN, item.FileTag); - }); - } + var fileTag = FileTagsHelper.ReadFileTag(item.ItemPath); + await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => + { + item.SyncStatusUI = new CloudDriveSyncStatusUI() { LoadSyncStatus = false }; // Reset cloud sync status icon + item.FileTag = fileTag; + }, Windows.System.DispatcherQueuePriority.Low); + FileTagsHelper.DbInstance.SetTag(item.ItemPath, item.FileFRN, item.FileTag); + }); + } - if (loadGroupHeaderInfo) - { - await FilesystemTasks.Wrap(() => CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => + if (loadGroupHeaderInfo) { - gp.Model.ImageSource = groupImage; - gp.InitializeExtendedGroupHeaderInfoAsync(); - })); + cts.Token.ThrowIfCancellationRequested(); + await FilesystemTasks.Wrap(() => CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => + { + gp.Model.ImageSource = groupImage; + gp.InitializeExtendedGroupHeaderInfoAsync(); + })); + } } - - loadExtendedPropsSemaphore.Release(); - } - }); + }, cts.Token); + } + catch (OperationCanceledException) + { + // ignored + } + finally + { + itemLoadQueue.TryRemove(item.ItemPath, out _); + } } private async Task GetItemTypeGroupIcon(ListedItem item, BaseStorageFile matchingStorageItem = null) @@ -1141,6 +1143,8 @@ private async void RapidAddItemsToCollectionAsync(string path, string previousDi return; } + itemLoadEvent.Reset(); + try { // Drop all the other waiting instances @@ -1148,7 +1152,6 @@ private async void RapidAddItemsToCollectionAsync(string path, string previousDi semaphoreCTS = new CancellationTokenSource(); IsLoadingItems = true; - itemLoadEvent.Reset(); filesAndFolders.Clear(); FilesAndFolders.Clear(); @@ -1714,8 +1717,10 @@ private void WatchForDirectoryChanges(string path, CloudDriveSyncStatus syncStat return; } + var hasSyncStatus = syncStatus != CloudDriveSyncStatus.NotSynced && syncStatus != CloudDriveSyncStatus.Unknown; + var cts = new CancellationTokenSource(); - _ = Windows.System.Threading.ThreadPool.RunAsync((x) => ProcessOperationQueue(cts.Token)); + _ = Windows.System.Threading.ThreadPool.RunAsync((x) => ProcessOperationQueue(cts.Token, hasSyncStatus)); aWatcherAction = Windows.System.Threading.ThreadPool.RunAsync((x) => { @@ -1724,7 +1729,7 @@ private void WatchForDirectoryChanges(string path, CloudDriveSyncStatus syncStat buff = new byte[4096]; int notifyFilters = FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SIZE; - if (syncStatus != CloudDriveSyncStatus.NotSynced && syncStatus != CloudDriveSyncStatus.Unknown) + if (hasSyncStatus) { notifyFilters |= FILE_NOTIFY_CHANGE_ATTRIBUTES; } @@ -1785,11 +1790,11 @@ private void WatchForDirectoryChanges(string path, CloudDriveSyncStatus syncStat operationQueue.Enqueue((action, FileName)); - operationEvent.Set(); - offset += notifyInfo.NextEntryOffset; } while (notifyInfo.NextEntryOffset != 0 && x.Status != AsyncStatus.Canceled); + operationEvent.Set(); + //ResetEvent(overlapped.hEvent); Debug.WriteLine("Task running..."); } @@ -1804,7 +1809,7 @@ private void WatchForDirectoryChanges(string path, CloudDriveSyncStatus syncStat Debug.WriteLine("Task exiting..."); } - private async void ProcessOperationQueue(CancellationToken cancellationToken) + private async void ProcessOperationQueue(CancellationToken cancellationToken, bool hasSyncStatus) { ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; string returnformat = Enum.Parse(localSettings.Values[Constants.LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g"; @@ -1815,16 +1820,20 @@ private async void ProcessOperationQueue(CancellationToken cancellationToken) const uint FILE_ACTION_RENAMED_OLD_NAME = 0x00000004; const uint FILE_ACTION_RENAMED_NEW_NAME = 0x00000005; + const int UPDATE_BATCH_SIZE = 32; var sampler = new IntervalSampler(200); + var updateQueue = new Queue(); bool anyEdits = false; try { while (!cancellationToken.IsCancellationRequested) { - if (operationEvent.Wait(200, cancellationToken)) + if (await operationEvent.WaitAsync(200, cancellationToken)) { operationEvent.Reset(); + itemLoadEvent.Reset(); + while (operationQueue.TryDequeue(out var operation)) { if (cancellationToken.IsCancellationRequested) break; @@ -1839,7 +1848,10 @@ private async void ProcessOperationQueue(CancellationToken cancellationToken) break; case FILE_ACTION_MODIFIED: - await UpdateFileOrFolderAsync(operation.FileName); + if (!updateQueue.Contains(operation.FileName)) + { + updateQueue.Enqueue(operation.FileName); + } break; case FILE_ACTION_REMOVED: @@ -1861,6 +1873,29 @@ private async void ProcessOperationQueue(CancellationToken cancellationToken) anyEdits = false; } } + + var itemsToUpdate = new List(); + for (var i = 0; i < UPDATE_BATCH_SIZE && updateQueue.Count > 0; i++) + { + itemsToUpdate.Add(updateQueue.Dequeue()); + } + + await UpdateFilesOrFoldersAsync(itemsToUpdate, hasSyncStatus); + itemLoadEvent.Set(); + } + + + if (updateQueue.Count > 0) + { + itemLoadEvent.Reset(); + var itemsToUpdate = new List(); + for (var i = 0; i < UPDATE_BATCH_SIZE && updateQueue.Count > 0; i++) + { + itemsToUpdate.Add(updateQueue.Dequeue()); + } + + await UpdateFilesOrFoldersAsync(itemsToUpdate, hasSyncStatus); + itemLoadEvent.Set(); } if (anyEdits && sampler.CheckNow()) @@ -1959,26 +1994,22 @@ public ListedItem AddFileOrFolderFromShellFile(ShellFileItem item, string dateRe private async Task AddFileOrFolderAsync(ListedItem item) { - try - { - await enumFolderSemaphore.WaitAsync(semaphoreCTS.Token); - } - catch (OperationCanceledException) + if (item == null) { return; } try { - if (item != null) - { - filesAndFolders.Add(item); - } + await enumFolderSemaphore.WaitAsync(semaphoreCTS.Token); } - finally + catch (OperationCanceledException) { - enumFolderSemaphore.Release(); + return; } + + filesAndFolders.Add(item); + enumFolderSemaphore.Release(); } private async Task AddFileOrFolderAsync(string fileOrFolderPath, string dateReturnFormat) @@ -2015,13 +2046,10 @@ private async Task AddFileOrFolderAsync(string fileOrFolderPath, string dateRetu listedItem = await Win32StorageEnumerator.GetFile(findData, Directory.GetParent(fileOrFolderPath).FullName, dateReturnFormat, Connection, addFilesCTS.Token); } - if (listedItem != null) - { - filesAndFolders.Add(listedItem); - } + await AddFileOrFolderAsync(listedItem); } - private async Task UpdateFileOrFolderAsync(ListedItem item) + private async Task<(ListedItem Item, CloudDriveSyncStatusUI SyncUI, long? Size, DateTimeOffset Created, DateTimeOffset Modified)?> GetFileOrFolderUpdateInfoAsync(ListedItem item, bool hasSyncStatus) { IStorageItem storageItem = null; if (item.PrimaryItemAttribute == StorageItemTypes.File) @@ -2034,30 +2062,31 @@ private async Task UpdateFileOrFolderAsync(ListedItem item) } if (storageItem != null) { - var syncStatus = await CheckCloudDriveSyncStatusAsync(storageItem); - await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(async () => + CloudDriveSyncStatusUI syncUI = hasSyncStatus ? CloudDriveSyncStatusUI.FromCloudDriveSyncStatus(await CheckCloudDriveSyncStatusAsync(storageItem)) : null; + long? size = null; + DateTimeOffset created = default, modified = default; + + if (storageItem.IsOfType(StorageItemTypes.File)) + { + var properties = await storageItem.AsBaseStorageFile().GetBasicPropertiesAsync(); + size = (long)properties.Size; + modified = properties.DateModified; + created = properties.ItemDate; + } + else if (storageItem.IsOfType(StorageItemTypes.Folder)) { - item.SyncStatusUI = CloudDriveSyncStatusUI.FromCloudDriveSyncStatus(syncStatus); + var properties = await storageItem.AsBaseStorageFolder().GetBasicPropertiesAsync(); + modified = properties.DateModified; + created = properties.ItemDate; + } - if (storageItem.IsOfType(StorageItemTypes.File)) - { - var properties = await storageItem.AsBaseStorageFile().GetBasicPropertiesAsync(); - item.FileSizeBytes = (long)properties.Size; - item.FileSize = ByteSizeLib.ByteSize.FromBytes(item.FileSizeBytes).ToBinaryString().ConvertSizeAbbreviation(); - item.ItemDateModifiedReal = properties.DateModified; - item.ItemDateCreatedReal = properties.ItemDate; - } - else if (storageItem.IsOfType(StorageItemTypes.Folder)) - { - var properties = await storageItem.AsBaseStorageFolder().GetBasicPropertiesAsync(); - item.ItemDateModifiedReal = properties.DateModified; - item.ItemDateCreatedReal = properties.ItemDate; - } - }); + return (item, syncUI, size, created, modified); } + + return null; } - private async Task UpdateFileOrFolderAsync(string path) + private async Task UpdateFilesOrFoldersAsync(IEnumerable paths, bool hasSyncStatus) { try { @@ -2070,12 +2099,32 @@ private async Task UpdateFileOrFolderAsync(string path) try { - var matchingItem = filesAndFolders.FirstOrDefault(x => x.ItemPath.Equals(path, StringComparison.OrdinalIgnoreCase)); + var matchingItems = filesAndFolders.Where(x => paths.Any(p => p.Equals(x.ItemPath, StringComparison.OrdinalIgnoreCase))); + var results = await Task.WhenAll(matchingItems.Select(x => GetFileOrFolderUpdateInfoAsync(x, hasSyncStatus))); - if (matchingItem != null) + await CoreApplication.MainView.DispatcherQueue.EnqueueAsync(() => { - await UpdateFileOrFolderAsync(matchingItem); - } + foreach (var result in results) + { + if (result.HasValue) + { + var item = result.Value.Item; + item.ItemDateModifiedReal = result.Value.Modified; + item.ItemDateCreatedReal = result.Value.Created; + + if (result.Value.SyncUI != null) + { + item.SyncStatusUI = result.Value.SyncUI; + } + + if (result.Value.Size.HasValue) + { + item.FileSizeBytes = result.Value.Size.Value; + item.FileSize = ByteSizeLib.ByteSize.FromBytes(item.FileSizeBytes).ToBinaryString().ConvertSizeAbbreviation(); + } + } + } + }, Windows.System.DispatcherQueuePriority.Low); } finally { @@ -2133,7 +2182,6 @@ public async Task SearchAsync(FolderSearch search) filesAndFolders.Clear(); IsLoadingItems = true; IsSearchResults = true; - itemLoadEvent.Reset(); await ApplyFilesAndFoldersChangesAsync(); EmptyTextType = EmptyTextType.None; @@ -2154,7 +2202,6 @@ public async Task SearchAsync(FolderSearch search) ItemLoadStatusChanged?.Invoke(this, new ItemLoadStatusChangedEventArgs() { Status = ItemLoadStatusChangedEventArgs.ItemLoadStatus.Complete }); IsLoadingItems = false; - itemLoadEvent.Set(); } public void CancelSearch() @@ -2210,4 +2257,4 @@ public enum ItemLoadStatus /// public string Path { get; set; } } -} \ No newline at end of file +} 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;