From 5662991a1c15ed2f166769bca4ae122ecdc06889 Mon Sep 17 00:00:00 2001 From: 0x5bfa <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 29 Jun 2024 03:47:22 +0900 Subject: [PATCH 1/4] Init --- .../Actions/FileSystem/CopyItemAction.cs | 46 ---- .../Actions/FileSystem/CutItemAction.cs | 45 ---- .../Transfer/BaseTransferItemAction.cs | 130 +++++++++++ .../FileSystem/Transfer/CopyItemAction.cs | 31 +++ .../FileSystem/Transfer/CutItemAction.cs | 31 +++ .../Helpers/UI/UIFilesystemHelpers.cs | 211 +----------------- 6 files changed, 193 insertions(+), 301 deletions(-) delete mode 100644 src/Files.App/Actions/FileSystem/CopyItemAction.cs delete mode 100644 src/Files.App/Actions/FileSystem/CutItemAction.cs create mode 100644 src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs create mode 100644 src/Files.App/Actions/FileSystem/Transfer/CopyItemAction.cs create mode 100644 src/Files.App/Actions/FileSystem/Transfer/CutItemAction.cs diff --git a/src/Files.App/Actions/FileSystem/CopyItemAction.cs b/src/Files.App/Actions/FileSystem/CopyItemAction.cs deleted file mode 100644 index 1deae0258333..000000000000 --- a/src/Files.App/Actions/FileSystem/CopyItemAction.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -namespace Files.App.Actions -{ - internal sealed class CopyItemAction : ObservableObject, IAction - { - private readonly IContentPageContext context; - - public string Label - => "Copy".GetLocalizedResource(); - - public string Description - => "CopyItemDescription".GetLocalizedResource(); - - public RichGlyph Glyph - => new(opacityStyle: "ColorIconCopy"); - - public HotKey HotKey - => new(Keys.C, KeyModifiers.Ctrl); - - public bool IsExecutable - => context.HasSelection; - - public CopyItemAction() - { - context = Ioc.Default.GetRequiredService(); - - context.PropertyChanged += Context_PropertyChanged; - } - - public Task ExecuteAsync(object? parameter = null) - { - if (context.ShellPage is not null) - return UIFilesystemHelpers.CopyItemAsync(context.ShellPage); - - return Task.CompletedTask; - } - - private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName is nameof(IContentPageContext.HasSelection)) - OnPropertyChanged(nameof(IsExecutable)); - } - } -} diff --git a/src/Files.App/Actions/FileSystem/CutItemAction.cs b/src/Files.App/Actions/FileSystem/CutItemAction.cs deleted file mode 100644 index fe105c78126e..000000000000 --- a/src/Files.App/Actions/FileSystem/CutItemAction.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -namespace Files.App.Actions -{ - internal sealed class CutItemAction : ObservableObject, IAction - { - private readonly IContentPageContext context; - - public string Label - => "Cut".GetLocalizedResource(); - - public string Description - => "CutItemDescription".GetLocalizedResource(); - - public RichGlyph Glyph - => new(opacityStyle: "ColorIconCut"); - - public HotKey HotKey - => new(Keys.X, KeyModifiers.Ctrl); - - public bool IsExecutable - => context.HasSelection; - - public CutItemAction() - { - context = Ioc.Default.GetRequiredService(); - - context.PropertyChanged += Context_PropertyChanged; - } - - public Task ExecuteAsync(object? parameter = null) - { - return context.ShellPage is not null - ? UIFilesystemHelpers.CutItemAsync(context.ShellPage) - : Task.CompletedTask; - } - - private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName is nameof(IContentPageContext.HasSelection)) - OnPropertyChanged(nameof(IsExecutable)); - } - } -} diff --git a/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs b/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs new file mode 100644 index 000000000000..486ffce8b5b0 --- /dev/null +++ b/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs @@ -0,0 +1,130 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using System.Collections.Concurrent; +using System.IO; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; +using Windows.System; + +namespace Files.App.Actions +{ + internal abstract class BaseTransferItemAction : ObservableObject + { + protected readonly IContentPageContext ContentPageContext = Ioc.Default.GetRequiredService(); + protected readonly StatusCenterViewModel StatusCenterViewModel = Ioc.Default.GetRequiredService(); + + public bool IsExecutable + => ContentPageContext.HasSelection; + + public BaseTransferItemAction() + { + ContentPageContext.PropertyChanged += ContentPageContext_PropertyChanged; + } + + public async Task ExecuteTransferAsync(DataPackageOperation type = DataPackageOperation.Copy) + { + if (ContentPageContext.ShellPage is null || + ContentPageContext.ShellPage.SlimContentPage.IsItemSelected is false) + return; + + // Reset cut mode + ContentPageContext.ShellPage.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity(); + + ConcurrentBag items = []; + var itemsCount = ContentPageContext.SelectedItems.Count; + var statusCenterItem = itemsCount > 50 ? StatusCenterHelper.AddCard_Prepare() : null; + var dataPackage = new DataPackage() { RequestedOperation = type }; + + try + { + // Update the status to in-progress + if (statusCenterItem is not null) + { + statusCenterItem.Progress.EnumerationCompleted = true; + statusCenterItem.Progress.ItemsCount = items.Count; + statusCenterItem.Progress.ReportStatus(FileSystemStatusCode.InProgress); + } + + await ContentPageContext.SelectedItems.ToList().ParallelForEachAsync(async listedItem => + { + // Update the status to increase processed count by one + if (statusCenterItem is not null) + { + statusCenterItem.Progress.AddProcessedItemsCount(1); + statusCenterItem.Progress.Report(); + } + + if (listedItem is FtpItem ftpItem) + { + // Don't dim selected items here since FTP doesn't support cut + if (ftpItem.PrimaryItemAttribute is StorageItemTypes.File or StorageItemTypes.Folder) + items.Add(await ftpItem.ToStorageItem()); + } + else + { + if (type is DataPackageOperation.Move) + { + var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + _ = dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => + { + // Dim opacities accordingly + listedItem.Opacity = Constants.UI.DimItemOpacity; + }); + } + + FilesystemResult? result = + listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem + ? await ContentPageContext.ShellPage.ShellViewModel.GetFileFromPathAsync(listedItem.ItemPath).OnSuccess(t => items.Add(t)) + : await ContentPageContext.ShellPage.ShellViewModel.GetFolderFromPathAsync(listedItem.ItemPath).OnSuccess(t => items.Add(t)); + + if (!result) + throw new IOException($"Failed to process {listedItem.ItemPath} in cutting/copying to the clipboard.", (int)result.ErrorCode); + } + }, + 10, + statusCenterItem?.CancellationToken ?? default); + + var standardObjectsOnly = items.All(x => x is StorageFile or StorageFolder or SystemStorageFile or SystemStorageFolder); + if (standardObjectsOnly) + items = new ConcurrentBag(await items.ToStandardStorageItemsAsync()); + + if (items.IsEmpty is false) + return; + + dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName; + dataPackage.SetStorageItems(items, false); + + Clipboard.SetContent(dataPackage); + } + catch (Exception ex) + { + dataPackage = default; + + if ((FileSystemStatusCode)ex.HResult is FileSystemStatusCode.Unauthorized) + { + string[] filePaths = ContentPageContext.SelectedItems.Select(x => x.ItemPath).ToArray(); + await FileOperationsHelpers.SetClipboard(filePaths, type); + + return; + } + + // Reset cut mode + ContentPageContext.ShellPage.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity(); + + return; + } + finally + { + if (statusCenterItem is not null) + StatusCenterViewModel.RemoveItem(statusCenterItem); + } + } + + private void ContentPageContext_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(IContentPageContext.HasSelection)) + OnPropertyChanged(nameof(IsExecutable)); + } + } +} diff --git a/src/Files.App/Actions/FileSystem/Transfer/CopyItemAction.cs b/src/Files.App/Actions/FileSystem/Transfer/CopyItemAction.cs new file mode 100644 index 000000000000..2ce0391936ec --- /dev/null +++ b/src/Files.App/Actions/FileSystem/Transfer/CopyItemAction.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Windows.ApplicationModel.DataTransfer; + +namespace Files.App.Actions +{ + internal sealed class CopyItemAction : BaseTransferItemAction, IAction + { + public string Label + => "Copy".GetLocalizedResource(); + + public string Description + => "CopyItemDescription".GetLocalizedResource(); + + public RichGlyph Glyph + => new(opacityStyle: "ColorIconCopy"); + + public HotKey HotKey + => new(Keys.C, KeyModifiers.Ctrl); + + public CopyItemAction() : base() + { + } + + public Task ExecuteAsync(object? parameter = null) + { + return ExecuteTransferAsync(DataPackageOperation.Copy); + } + } +} diff --git a/src/Files.App/Actions/FileSystem/Transfer/CutItemAction.cs b/src/Files.App/Actions/FileSystem/Transfer/CutItemAction.cs new file mode 100644 index 000000000000..1651d6f405ad --- /dev/null +++ b/src/Files.App/Actions/FileSystem/Transfer/CutItemAction.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Windows.ApplicationModel.DataTransfer; + +namespace Files.App.Actions +{ + internal sealed class CutItemAction : BaseTransferItemAction, IAction + { + public string Label + => "Cut".GetLocalizedResource(); + + public string Description + => "CutItemDescription".GetLocalizedResource(); + + public RichGlyph Glyph + => new(opacityStyle: "ColorIconCut"); + + public HotKey HotKey + => new(Keys.X, KeyModifiers.Ctrl); + + public CutItemAction() : base() + { + } + + public Task ExecuteAsync(object? parameter = null) + { + return ExecuteTransferAsync(DataPackageOperation.Move); + } + } +} diff --git a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs index e1439745d53f..875390362052 100644 --- a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs +++ b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs @@ -3,227 +3,18 @@ using Files.App.Dialogs; using Files.App.Storage.Storables; -using Files.App.ViewModels.Dialogs; using Microsoft.Extensions.Logging; -using System.Collections.Concurrent; using System.IO; using System.Net; using System.Text; using Windows.ApplicationModel.DataTransfer; using Windows.Storage; -using Windows.System; namespace Files.App.Helpers { + // TODO: Remove this class public static class UIFilesystemHelpers { - private static readonly StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); - - public static async Task CutItemAsync(IShellPage associatedInstance) - { - var dataPackage = new DataPackage() - { - RequestedOperation = DataPackageOperation.Move - }; - ConcurrentBag items = []; - - if (associatedInstance.SlimContentPage.IsItemSelected) - { - // First, reset DataGrid Rows that may be in "cut" command mode - associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity(); - - var itemsCount = associatedInstance.SlimContentPage.SelectedItems!.Count; - - var banner = itemsCount > 50 ? StatusCenterHelper.AddCard_Prepare() : null; - - try - { - var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - if (banner is not null) - { - banner.Progress.EnumerationCompleted = true; - banner.Progress.ItemsCount = items.Count; - banner.Progress.ReportStatus(FileSystemStatusCode.InProgress); - } - - await associatedInstance.SlimContentPage.SelectedItems.ToList().ParallelForEachAsync(async listedItem => - { - if (banner is not null) - { - banner.Progress.AddProcessedItemsCount(1); - banner.Progress.Report(); - } - - // FTP don't support cut, fallback to copy - if (listedItem is not FtpItem) - { - _ = dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => - { - // Dim opacities accordingly - listedItem.Opacity = Constants.UI.DimItemOpacity; - }); - } - if (listedItem is FtpItem ftpItem) - { - if (ftpItem.PrimaryItemAttribute is StorageItemTypes.File or StorageItemTypes.Folder) - items.Add(await ftpItem.ToStorageItem()); - } - else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem) - { - var result = await associatedInstance.ShellViewModel.GetFileFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - - if (!result) - throw new IOException($"Failed to process {listedItem.ItemPath}.", (int)result.ErrorCode); - } - else - { - var result = await associatedInstance.ShellViewModel.GetFolderFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - - if (!result) - throw new IOException($"Failed to process {listedItem.ItemPath}.", (int)result.ErrorCode); - } - }, 10, banner?.CancellationToken ?? default); - } - catch (Exception ex) - { - if (ex.HResult == (int)FileSystemStatusCode.Unauthorized) - { - string[] filePaths = associatedInstance.SlimContentPage.SelectedItems.Select(x => x.ItemPath).ToArray(); - - await FileOperationsHelpers.SetClipboard(filePaths, DataPackageOperation.Move); - - _statusCenterViewModel.RemoveItem(banner); - - return; - } - - associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity(); - - _statusCenterViewModel.RemoveItem(banner); - - return; - } - - _statusCenterViewModel.RemoveItem(banner); - } - - var onlyStandard = items.All(x => x is StorageFile || x is StorageFolder || x is SystemStorageFile || x is SystemStorageFolder); - if (onlyStandard) - items = new ConcurrentBag(await items.ToStandardStorageItemsAsync()); - - if (!items.Any()) - return; - - dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName; - dataPackage.SetStorageItems(items, false); - try - { - Clipboard.SetContent(dataPackage); - } - catch - { - dataPackage = null; - } - } - - public static async Task CopyItemAsync(IShellPage associatedInstance) - { - var dataPackage = new DataPackage() - { - RequestedOperation = DataPackageOperation.Copy - }; - ConcurrentBag items = []; - - if (associatedInstance.SlimContentPage.IsItemSelected) - { - associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity(); - - var itemsCount = associatedInstance.SlimContentPage.SelectedItems!.Count; - - var banner = itemsCount > 50 ? StatusCenterHelper.AddCard_Prepare() : null; - - try - { - if (banner is not null) - { - banner.Progress.EnumerationCompleted = true; - banner.Progress.ItemsCount = items.Count; - banner.Progress.ReportStatus(FileSystemStatusCode.InProgress); - } - await associatedInstance.SlimContentPage.SelectedItems.ToList().ParallelForEachAsync(async listedItem => - { - if (banner is not null) - { - banner.Progress.AddProcessedItemsCount(1); - banner.Progress.Report(); - } - - if (listedItem is FtpItem ftpItem) - { - if (ftpItem.PrimaryItemAttribute is StorageItemTypes.File or StorageItemTypes.Folder) - items.Add(await ftpItem.ToStorageItem()); - } - else if (listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem) - { - var result = await associatedInstance.ShellViewModel.GetFileFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - - if (!result) - throw new IOException($"Failed to process {listedItem.ItemPath}.", (int)result.ErrorCode); - } - else - { - var result = await associatedInstance.ShellViewModel.GetFolderFromPathAsync(listedItem.ItemPath) - .OnSuccess(t => items.Add(t)); - - if (!result) - throw new IOException($"Failed to process {listedItem.ItemPath}.", (int)result.ErrorCode); - } - }, 10, banner?.CancellationToken ?? default); - } - catch (Exception ex) - { - if (ex.HResult == (int)FileSystemStatusCode.Unauthorized) - { - string[] filePaths = associatedInstance.SlimContentPage.SelectedItems.Select(x => x.ItemPath).ToArray(); - - await FileOperationsHelpers.SetClipboard(filePaths, DataPackageOperation.Copy); - - _statusCenterViewModel.RemoveItem(banner); - - return; - } - - _statusCenterViewModel.RemoveItem(banner); - - return; - } - - _statusCenterViewModel.RemoveItem(banner); - } - - var onlyStandard = items.All(x => x is StorageFile || x is StorageFolder || x is SystemStorageFile || x is SystemStorageFolder); - if (onlyStandard) - items = new ConcurrentBag(await items.ToStandardStorageItemsAsync()); - - if (!items.Any()) - return; - - dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName; - dataPackage.SetStorageItems(items, false); - - try - { - Clipboard.SetContent(dataPackage); - } - catch - { - dataPackage = null; - } - } - public static async Task PasteItemAsync(string destinationPath, IShellPage associatedInstance) { FilesystemResult packageView = await FilesystemTasks.Wrap(() => Task.FromResult(Clipboard.GetContent())); From 3c796471bd2cb5bfdb20b7671407fd0e49078e53 Mon Sep 17 00:00:00 2001 From: 0x5bfa <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 29 Jun 2024 04:35:42 +0900 Subject: [PATCH 2/4] Brush up --- .../Actions/FileSystem/Transfer/BaseTransferItemAction.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs b/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs index 486ffce8b5b0..e23d00c6cbbb 100644 --- a/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs +++ b/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs @@ -65,10 +65,9 @@ await ContentPageContext.SelectedItems.ToList().ParallelForEachAsync(async liste { if (type is DataPackageOperation.Move) { - var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - _ = dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => + // Dim opacities accordingly + await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => { - // Dim opacities accordingly listedItem.Opacity = Constants.UI.DimItemOpacity; }); } @@ -89,7 +88,7 @@ await ContentPageContext.SelectedItems.ToList().ParallelForEachAsync(async liste if (standardObjectsOnly) items = new ConcurrentBag(await items.ToStandardStorageItemsAsync()); - if (items.IsEmpty is false) + if (items.IsEmpty) return; dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName; From d485cebf6c949d208ef2e372792bedf177d97294 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Thu, 4 Jul 2024 02:53:29 +0900 Subject: [PATCH 3/4] Added logging --- .../Actions/FileSystem/Transfer/BaseTransferItemAction.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs b/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs index e23d00c6cbbb..7dea9c64ad65 100644 --- a/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs +++ b/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs @@ -1,6 +1,7 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using Microsoft.Extensions.Logging; using System.Collections.Concurrent; using System.IO; using Windows.ApplicationModel.DataTransfer; @@ -100,6 +101,11 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => { dataPackage = default; + if (ex is not IOException) + { + App.Logger.LogWarning(ex, "Failed to process cutting/copying due to an unknown error."); + } + if ((FileSystemStatusCode)ex.HResult is FileSystemStatusCode.Unauthorized) { string[] filePaths = ContentPageContext.SelectedItems.Select(x => x.ItemPath).ToArray(); From 879ac1d753550cea9552004c76ec2871c318faed Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:46:00 +0900 Subject: [PATCH 4/4] =?UTF-8?q?BaseTransferItemAction.cs=20=E3=82=92?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hishitetsu <66369541+hishitetsu@users.noreply.github.com> --- .../Actions/FileSystem/Transfer/BaseTransferItemAction.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs b/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs index 7dea9c64ad65..9965a73f07b4 100644 --- a/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs +++ b/src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs @@ -102,9 +102,7 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => dataPackage = default; if (ex is not IOException) - { App.Logger.LogWarning(ex, "Failed to process cutting/copying due to an unknown error."); - } if ((FileSystemStatusCode)ex.HResult is FileSystemStatusCode.Unauthorized) {