From 91ee304c2fa3ae8918b2e06b6b21bc49987c5a48 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Tue, 12 Sep 2023 23:08:24 +0900 Subject: [PATCH 01/97] Initial commit --- src/Files.App/Files.App.csproj | 1 + src/Files.App/UserControls/StatusCenter.xaml | 6 +- .../Utils/StatusCenter/StatusCenterItem.cs | 75 ++++++++++++++++++- .../StatusCenterItemProgressModel.cs | 8 ++ 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/Files.App/Files.App.csproj b/src/Files.App/Files.App.csproj index 7321dbe9b3e8..87f5226f38be 100644 --- a/src/Files.App/Files.App.csproj +++ b/src/Files.App/Files.App.csproj @@ -82,6 +82,7 @@ + diff --git a/src/Files.App/UserControls/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter.xaml index 7f75ebddb7a6..34e79c2197ba 100644 --- a/src/Files.App/UserControls/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter.xaml @@ -7,6 +7,7 @@ xmlns:converters="using:Files.App.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:helpers="using:Files.App.Helpers" + xmlns:livecharts2="using:LiveChartsCore.SkiaSharpView.WinUI" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:statuscenter="using:Files.App.Utils.StatusCenter" xmlns:uc="using:Files.App.UserControls" @@ -543,7 +544,7 @@ Value="{x:Bind ProgressPercentage, Mode=OneWay}" /> - + diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index f3f7be546f90..08ebea38d535 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -2,6 +2,12 @@ // Licensed under the MIT License. See the LICENSE. using System.Windows.Input; +using SkiaSharp; +using LiveChartsCore; +using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel.Sketches; +using LiveChartsCore.SkiaSharpView; +using LiveChartsCore.SkiaSharpView.Painting; namespace Files.App.Utils.StatusCenter { @@ -70,6 +76,58 @@ public bool IsCancelled set => SetProperty(ref _IsCancelled, value); } + private StatusCenterItemProgressModel _Progress = null!; + public StatusCenterItemProgressModel Progress + { + get => _Progress; + set => SetProperty(ref _Progress, value); + } + + // (_currentWriteAmount[Mib,kiB,B] - _previousWriteAmount[Mib,kiB,B]) / (pasted seconds[s]) + private string? _SpeedText; + public string? SpeedText + { + get => _SpeedText; + set => SetProperty(ref _SpeedText, value); + } + + public ObservableCollection Values { get; set; } + + public ObservableCollection Series { get; set; } + + public IList XAxes { get; set; } = new ICartesianAxis[] + { + new Axis + { + Padding = new Padding(0, 0), + Labels = new List(), + MaxLimit = 100, + + ShowSeparatorLines = false, + //SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) + //{ + // StrokeThickness = 0.5F, + // PathEffect = new DashEffect(new float[] { 3, 3 }) + //} + } + }; + + public IList YAxes { get; set; } = new ICartesianAxis[] + { + new Axis + { + Padding = new Padding(0, 0), + Labels = new List(), + + ShowSeparatorLines = false, + //SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) + //{ + // StrokeThickness = 0.5F, + // PathEffect = new DashEffect(new float[] { 3, 3 }) + //} + } + }; + public CancellationToken CancellationToken => _operationCancellationToken?.Token ?? default; @@ -86,15 +144,13 @@ public bool IsCancelable public StatusCenterItemIconKind ItemIconKind { get; private set; } - public readonly StatusCenterItemProgressModel Progress; - public readonly Progress ProgressEventSource; private readonly CancellationTokenSource? _operationCancellationToken; public ICommand CancelCommand { get; } - public StatusCenterItem(string message, string title, float progress, ReturnResult status, FileOperationType operation, CancellationTokenSource operationCancellationToken = null) + public StatusCenterItem(string message, string title, float progress, ReturnResult status, FileOperationType operation, CancellationTokenSource? operationCancellationToken = null) { _operationCancellationToken = operationCancellationToken; SubHeader = message; @@ -107,6 +163,19 @@ public StatusCenterItem(string message, string title, float progress, ReturnResu CancelCommand = new RelayCommand(ExecuteCancelCommand); + Values = new(); + + Series = new() + { + new LineSeries + { + Values = Values, + GeometrySize = 0, + Stroke = new SolidColorPaint(new(25, 118, 210), 1), + DataPadding = new(0, 0), + } + }; + switch (FileSystemOperationReturnResult) { case ReturnResult.InProgress: diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index 082324384e68..ae610305817a 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -14,6 +14,14 @@ public class StatusCenterItemProgressModel : ObservableObject private bool _criticalReport; + private int _previousWriteAmount; + + private int _currentWriteAmount; + + private DateTimeOffset _previousProgressUpdate; + + private DateTimeOffset _currentProgressUpdate; + private FileSystemStatusCode? _Status; public FileSystemStatusCode? Status { From 96bdacc1d960e3c671ce7257e22f1dc0c32d7660 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Tue, 12 Sep 2023 23:35:02 +0900 Subject: [PATCH 02/97] Update --- src/Files.App/UserControls/StatusCenter.xaml | 4 +-- .../Utils/StatusCenter/StatusCenterItem.cs | 6 +++++ .../StatusCenterItemProgressModel.cs | 27 ++++++++++++++----- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Files.App/UserControls/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter.xaml index 34e79c2197ba..eb38f9f70b19 100644 --- a/src/Files.App/UserControls/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter.xaml @@ -490,7 +490,7 @@ - + diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 08ebea38d535..c571b28cfed5 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -253,6 +253,7 @@ private void ReportProgress(StatusCenterItemProgressModel value) { Header = $"{HeaderBody} ({ProgressPercentage}%)"; ProgressPercentage = p; + SpeedText = $"{Progress.ProcessingSpeed}"; } } else if (value.EnumerationCompleted) @@ -263,19 +264,23 @@ private void ReportProgress(StatusCenterItemProgressModel value) case (not 0, not 0): ProgressPercentage = (int)(value.ProcessedSize * 100f / value.TotalSize); Header = $"{HeaderBody} ({value.ProcessedItemsCount} ({value.ProcessedSize.ToSizeString()}) / {value.ItemsCount} ({value.TotalSize.ToSizeString()}): {ProgressPercentage}%)"; + SpeedText = $"{Progress.ProcessingSpeed}"; break; // In progress, displaying processed size case (not 0, _): ProgressPercentage = (int)(value.ProcessedSize * 100 / value.TotalSize); Header = $"{HeaderBody} ({value.ProcessedSize.ToSizeString()} / {value.TotalSize.ToSizeString()}: {ProgressPercentage}%)"; + SpeedText = $"{Progress.ProcessingSpeed}"; break; // In progress, displaying items count case (_, not 0): ProgressPercentage = (int)(value.ProcessedItemsCount * 100 / value.ItemsCount); Header = $"{HeaderBody} ({value.ProcessedItemsCount} / {value.ItemsCount}: {ProgressPercentage}%)"; + SpeedText = $"{Progress.ProcessingSpeed}"; break; default: Header = $"{HeaderBody}"; + SpeedText = $"{Progress.ProcessingSpeed}"; break; } } @@ -288,6 +293,7 @@ private void ReportProgress(StatusCenterItemProgressModel value) (_, not 0) => $"{HeaderBody} ({value.ProcessedItemsCount} / ...)", _ => $"{HeaderBody}", }; + SpeedText = $"{Progress.ProcessingSpeed}"; } _viewModel.NotifyChanges(); diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index ae610305817a..ae82a36574a6 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -14,13 +14,9 @@ public class StatusCenterItemProgressModel : ObservableObject private bool _criticalReport; - private int _previousWriteAmount; + private long _previousWriteAmount; - private int _currentWriteAmount; - - private DateTimeOffset _previousProgressUpdate; - - private DateTimeOffset _currentProgressUpdate; + private DateTimeOffset _previousProgressUpdateDate; private FileSystemStatusCode? _Status; public FileSystemStatusCode? Status @@ -83,6 +79,13 @@ public long ProcessedItemsCount set => SetProperty(ref _ProcessedItemsCount, value); } + public long _ProcessingSpeed; + public long ProcessingSpeed + { + get => _ProcessingSpeed; + set => SetProperty(ref _ProcessingSpeed, value); + } + public DateTimeOffset _StartTime; public DateTimeOffset StartTime { @@ -131,7 +134,19 @@ TotalSize is not 0 || if (_progress is not null && (_criticalReport || _sampler.CheckNow())) { + var span = DateTimeOffset.Now - _previousProgressUpdateDate; + var amountDifferent = ProcessedSize - _previousWriteAmount; + + if (span.Seconds == 0 || amountDifferent == 0) + ProcessingSpeed = amountDifferent / span.Seconds; + else + ProcessingSpeed = 0; + + _previousProgressUpdateDate = DateTimeOffset.Now; + _previousWriteAmount = ProcessedSize; + _progress.Report(this); + _criticalReport = false; } } From 15009978d0c31673db31e90e6a2805a31eab194a Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Wed, 13 Sep 2023 23:24:21 +0900 Subject: [PATCH 03/97] Update --- src/Files.App/UserControls/StatusCenter.xaml | 7 +++++++ src/Files.App/Utils/StatusCenter/StatusCenterItem.cs | 1 - .../Utils/StatusCenter/StatusCenterItemProgressModel.cs | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Files.App/UserControls/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter.xaml index eb38f9f70b19..39799c5ac217 100644 --- a/src/Files.App/UserControls/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter.xaml @@ -571,6 +571,13 @@ XAxes="{x:Bind XAxes}" YAxes="{x:Bind YAxes}" /> + + diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index c571b28cfed5..c9e35a026a6f 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -83,7 +83,6 @@ public StatusCenterItemProgressModel Progress set => SetProperty(ref _Progress, value); } - // (_currentWriteAmount[Mib,kiB,B] - _previousWriteAmount[Mib,kiB,B]) / (pasted seconds[s]) private string? _SpeedText; public string? SpeedText { diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index ae82a36574a6..5d67b20eaad3 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -137,11 +137,14 @@ TotalSize is not 0 || var span = DateTimeOffset.Now - _previousProgressUpdateDate; var amountDifferent = ProcessedSize - _previousWriteAmount; - if (span.Seconds == 0 || amountDifferent == 0) + if (span.Seconds != 0 && amountDifferent != 0) ProcessingSpeed = amountDifferent / span.Seconds; else ProcessingSpeed = 0; + ProcessingSpeed = 100; + ProcessingSpeed -= new Random().Next(10); + _previousProgressUpdateDate = DateTimeOffset.Now; _previousWriteAmount = ProcessedSize; From f757935509aa48327b5650c1c1fd54ff30d50be5 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:42:57 +0900 Subject: [PATCH 04/97] Update --- src/Files.App/Files.App.csproj | 20 +- .../StatusCenterStyles.xaml | 363 +++++++++++++++++ src/Files.App/UserControls/StatusCenter.xaml | 373 +----------------- .../Utils/Archives/ArchiveHelpers.cs | 2 +- src/Files.App/Utils/Archives/ZipHelpers.cs | 12 +- .../Utils/StatusCenter/StatusCenterItem.cs | 16 +- .../Operations/FilesystemOperations.cs | 55 ++- 7 files changed, 431 insertions(+), 410 deletions(-) create mode 100644 src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml diff --git a/src/Files.App/Files.App.csproj b/src/Files.App/Files.App.csproj index e3e414e9b890..5797706867f9 100644 --- a/src/Files.App/Files.App.csproj +++ b/src/Files.App/Files.App.csproj @@ -1,4 +1,4 @@ - + @@ -68,12 +68,6 @@ - - - - - - @@ -139,16 +133,4 @@ - - - $(DefaultXamlRuntime) - - - $(DefaultXamlRuntime) - - - MSBuild:Compile - - - diff --git a/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml b/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml new file mode 100644 index 000000000000..4dd35bac125a --- /dev/null +++ b/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Files.App/UserControls/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter.xaml index 39799c5ac217..819f98b9e7dc 100644 --- a/src/Files.App/UserControls/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter.xaml @@ -4,383 +4,20 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals" - xmlns:converters="using:Files.App.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:helpers="using:Files.App.Helpers" xmlns:livecharts2="using:LiveChartsCore.SkiaSharpView.WinUI" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:statuscenter="using:Files.App.Utils.StatusCenter" xmlns:uc="using:Files.App.UserControls" - xmlns:wctconverters="using:CommunityToolkit.WinUI.UI.Converters" xmlns:wcttriggers="using:CommunityToolkit.WinUI.UI.Triggers" mc:Ignorable="d"> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -673,6 +310,14 @@ + + + + + + + + diff --git a/src/Files.App/Utils/Archives/ArchiveHelpers.cs b/src/Files.App/Utils/Archives/ArchiveHelpers.cs index 43a8b0a6cc3b..d9e462d0411b 100644 --- a/src/Files.App/Utils/Archives/ArchiveHelpers.cs +++ b/src/Files.App/Utils/Archives/ArchiveHelpers.cs @@ -73,7 +73,7 @@ public static async Task CompressArchiveAsync(IArchiveCreator creator) ( "CompressionInProgress".GetLocalizedResource(), archivePath, - 0, + initialProgress: 0, ReturnResult.InProgress, FileOperationType.Compressed, compressionToken diff --git a/src/Files.App/Utils/Archives/ZipHelpers.cs b/src/Files.App/Utils/Archives/ZipHelpers.cs index ce9199568f44..313b4387761e 100644 --- a/src/Files.App/Utils/Archives/ZipHelpers.cs +++ b/src/Files.App/Utils/Archives/ZipHelpers.cs @@ -86,7 +86,17 @@ public static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFold int entriesFinished = 0; var minimumTime = new DateTime(1); - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress, entriesAmount); + ulong totalSize = 0; + foreach (var item in zipFile.ArchiveFileData) + totalSize += item.Size; + + StatusCenterItemProgressModel fsProgress = new( + progress, + enumerationCompleted: true, + FileSystemStatusCode.InProgress, + entriesAmount, + (long)totalSize); + fsProgress.Report(); foreach (var entry in fileEntries) diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 6696e6d11f4b..706c6e7f4a1e 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -8,6 +8,7 @@ using LiveChartsCore.Kernel.Sketches; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Painting; +using LiveChartsCore.Defaults; namespace Files.App.Utils.StatusCenter { @@ -90,7 +91,7 @@ public string? SpeedText set => SetProperty(ref _SpeedText, value); } - public ObservableCollection Values { get; set; } + public ObservableCollection Values { get; set; } public ObservableCollection Series { get; set; } @@ -166,7 +167,7 @@ public StatusCenterItem(string message, string title, float progress, ReturnResu Series = new() { - new LineSeries + new LineSeries { Values = Values, GeometrySize = 0, @@ -262,23 +263,25 @@ private void ReportProgress(StatusCenterItemProgressModel value) case (not 0, not 0): ProgressPercentage = (int)(value.ProcessedSize * 100.0 / value.TotalSize); Header = $"{HeaderBody} ({value.ProcessedItemsCount} ({value.ProcessedSize.ToSizeString()}) / {value.ItemsCount} ({value.TotalSize.ToSizeString()}): {ProgressPercentage}%)"; - SpeedText = $"{Progress.ProcessingSpeed}"; + SpeedText = $"{value.ProcessedItemsCount:0} items ({value.ProcessingSizeSpeed:0.00} bytes) / second"; + Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); break; // In progress, displaying processed size case (not 0, _): ProgressPercentage = (int)(value.ProcessedSize * 100.0 / value.TotalSize); Header = $"{HeaderBody} ({value.ProcessedSize.ToSizeString()} / {value.TotalSize.ToSizeString()}: {ProgressPercentage}%)"; - SpeedText = $"{Progress.ProcessingSpeed}"; + SpeedText = $"{value.ProcessingSizeSpeed:0.00} bytes / second"; + Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); break; // In progress, displaying items count case (_, not 0): ProgressPercentage = (int)(value.ProcessedItemsCount * 100.0 / value.ItemsCount); Header = $"{HeaderBody} ({value.ProcessedItemsCount} / {value.ItemsCount}: {ProgressPercentage}%)"; - SpeedText = $"{Progress.ProcessingSpeed}"; + SpeedText = $"{value.ProcessedItemsCount:0} items / second"; + Values.Add(new(value.ProcessedItemsCount * 100.0 / value.ItemsCount, value.ProcessingItemsCountSpeed)); break; default: Header = $"{HeaderBody}"; - SpeedText = $"{Progress.ProcessingSpeed}"; break; } } @@ -291,7 +294,6 @@ private void ReportProgress(StatusCenterItemProgressModel value) (_, not 0) => $"{HeaderBody} ({value.ProcessedItemsCount} / ...)", _ => $"{HeaderBody}", }; - SpeedText = $"{Progress.ProcessingSpeed}"; } _viewModel.NotifyChanges(); diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs index 0d75c8833431..6d07963f08d6 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs @@ -18,7 +18,7 @@ public class FilesystemOperations : IFilesystemOperations public FilesystemOperations(IShellPage associatedInstance) { - this._associatedInstance = associatedInstance; + _associatedInstance = associatedInstance; } public async Task<(IStorageHistory, IStorageItem)> CreateAsync(IStorageItemWithPath source, IProgress progress, CancellationToken cancellationToken, bool asAdmin = false) @@ -109,7 +109,13 @@ public Task CopyAsync(IStorageItem source, string destination, public async Task CopyAsync(IStorageItemWithPath source, string destination, NameCollisionOption collision, IProgress progress, CancellationToken cancellationToken) { - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress); + + // TODO: Get total size + fsProgress.Report(); if (destination.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal)) @@ -125,7 +131,6 @@ await DialogDisplayHelper.ShowDialogAsync( } IStorageItem copiedItem = null; - //long itemSize = await FilesystemHelpers.GetItemSize(await source.ToStorageItem(associatedInstance)); if (source.ItemType == FilesystemItemType.Directory) { @@ -289,7 +294,13 @@ public Task MoveAsync(IStorageItem source, string destination, public async Task MoveAsync(IStorageItemWithPath source, string destination, NameCollisionOption collision, IProgress progress, CancellationToken cancellationToken) { - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress); + + // TODO: Get total size + fsProgress.Report(); if (source.Path == destination) @@ -321,8 +332,6 @@ await DialogDisplayHelper.ShowDialogAsync( IStorageItem movedItem = null; - //long itemSize = await FilesystemHelpers.GetItemSize(await source.ToStorageItem(associatedInstance)); - if (source.ItemType == FilesystemItemType.Directory) { // Also check if user tried to move anything above the source.ItemPath @@ -465,7 +474,13 @@ public Task DeleteAsync(IStorageItem source, IProgress DeleteAsync(IStorageItemWithPath source, IProgress progress, bool permanently, CancellationToken cancellationToken) { - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress); + + // TODO: Get total size + fsProgress.Report(); bool deleteFromRecycleBin = RecycleBinHelpers.IsPathUnderRecycleBin(source.Path); @@ -547,15 +562,18 @@ public Task RenameAsync(IStorageItem source, string newName, Na return RenameAsync(StorageHelpers.FromStorageItem(source), newName, collision, progress, cancellationToken); } - public async Task RenameAsync(IStorageItemWithPath source, - string newName, - NameCollisionOption collision, - IProgress progress, - CancellationToken cancellationToken, - bool asAdmin = false) + public async Task RenameAsync( + IStorageItemWithPath source, + string newName, + NameCollisionOption collision, + IProgress progress, + CancellationToken cancellationToken, + bool asAdmin = false) { StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + // TODO: Get total size + fsProgress.Report(); if (Path.GetFileName(source.Path) == newName && collision == NameCollisionOption.FailIfExists) @@ -652,11 +670,12 @@ public async Task RestoreItemsFromTrashAsync(IList item.FromStorageItem()).ToListAsync(), destination, progress, cancellationToken); } - public async Task RestoreItemsFromTrashAsync(IList source, - IList destination, - IProgress progress, - CancellationToken token, - bool asAdmin = false) + public async Task RestoreItemsFromTrashAsync( + IList source, + IList destination, + IProgress progress, + CancellationToken token, + bool asAdmin = false) { StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress, source.Count); fsProgress.Report(); From fcd3824938301b97bb5aa6f001a920273bf8402a Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Fri, 15 Sep 2023 23:45:10 +0900 Subject: [PATCH 05/97] Update --- .../Utils/StatusCenter/StatusCenterItem.cs | 3 + .../StatusCenterItemProgressModel.cs | 10 ++++ .../Storage/Helpers/FileOperationsHelpers.cs | 58 +++++++++++++++---- .../Operations/ShellFilesystemOperations.cs | 34 ++++++++++- 4 files changed, 92 insertions(+), 13 deletions(-) diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 706c6e7f4a1e..f2a453768bdb 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -253,6 +253,9 @@ private void ReportProgress(StatusCenterItemProgressModel value) { Header = $"{HeaderBody} ({ProgressPercentage:0}%)"; ProgressPercentage = (int)p; + + SpeedText = $"{value.ProcessedItemsCount:0} items ({value.ProcessingSizeSpeed:0.00} bytes) / second"; + Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); } } else if (value.EnumerationCompleted) diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index aba3135b8398..a5ae7253b0ae 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -141,6 +141,7 @@ public void Report(double? percentage = null) { Percentage = percentage; + // Set the progress state as success if ((EnumerationCompleted && ProcessedItemsCount == ItemsCount && ProcessedSize == TotalSize && @@ -151,6 +152,7 @@ TotalSize is not 0 || _Status = FileSystemStatusCode.Success; } + // Set time at completed when succeed if (_Status is FileSystemStatusCode.Success) CompletedTime = DateTimeOffset.Now; @@ -165,10 +167,18 @@ TotalSize is not 0 || PropertyChanged?.Invoke(this, new(propertyName)); } } + + if (ProcessedSize == 0 && Percentage is not null) + ProcessedSize = TotalSize * (long)(Percentage / 100); + ProcessingSizeSpeed = (ProcessedSize - _previousProcessedSize) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; + + // NOTE: This won't work yet ProcessingItemsCountSpeed = (ProcessedItemsCount - _previousProcessedItemsCount) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; + PropertyChanged?.Invoke(this, new(nameof(ProcessingSizeSpeed))); PropertyChanged?.Invoke(this, new(nameof(ProcessingItemsCountSpeed))); + _progress?.Report(this); _previousReportTime = DateTimeOffset.Now; _previousProcessedSize = ProcessedSize; diff --git a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs b/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs index cb67b489a45d..582cf11a34b9 100644 --- a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs @@ -444,7 +444,19 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera { operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + long totalSize = 0; + foreach (var item in fileToCopyPath) + { + totalSize += FileOperationsHelpers.GetFileSize(item); + } + + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress, + fileToCopyPath.Count(), + totalSize); + fsProgress.Report(); progressHandler ??= new(); @@ -454,24 +466,32 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera var shellOperationResult = new ShellOperationResult(); - op.Options = ShellFileOperations.OperationFlags.NoConfirmMkDir - | ShellFileOperations.OperationFlags.Silent - | ShellFileOperations.OperationFlags.NoErrorUI; + op.Options = + ShellFileOperations.OperationFlags.NoConfirmMkDir | + ShellFileOperations.OperationFlags.Silent | + ShellFileOperations.OperationFlags.NoErrorUI; + if (asAdmin) { - op.Options |= ShellFileOperations.OperationFlags.ShowElevationPrompt - | ShellFileOperations.OperationFlags.RequireElevation; + op.Options |= + ShellFileOperations.OperationFlags.ShowElevationPrompt | + ShellFileOperations.OperationFlags.RequireElevation; } + op.OwnerWindow = (IntPtr)ownerHwnd; - op.Options |= !overwriteOnCopy ? ShellFileOperations.OperationFlags.PreserveFileExtensions | ShellFileOperations.OperationFlags.RenameOnCollision - : ShellFileOperations.OperationFlags.NoConfirmation; + op.Options |= + !overwriteOnCopy + ? ShellFileOperations.OperationFlags.PreserveFileExtensions | ShellFileOperations.OperationFlags.RenameOnCollision + : ShellFileOperations.OperationFlags.NoConfirmation; for (var i = 0; i < fileToCopyPath.Length; i++) { if (!SafetyExtensions.IgnoreExceptions(() => { - using ShellItem shi = new ShellItem(fileToCopyPath[i]); - using ShellFolder shd = new ShellFolder(Path.GetDirectoryName(copyDestination[i])); + using ShellItem shi = new(fileToCopyPath[i]); + using ShellFolder shd = new(Path.GetDirectoryName(copyDestination[i])); + + // Performa copy operation op.QueueCopyOperation(shi, shd, Path.GetFileName(copyDestination[i])); })) { @@ -807,6 +827,24 @@ private static void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e, public static void WaitForCompletion() => progressHandler?.WaitForCompletion(); + public static long GetFileSize(string path) + { + var hFile = Kernel32.CreateFile( + path, + Kernel32.FileAccess.FILE_READ_ATTRIBUTES, + FileShare.Read, + null, + FileMode.Open, + 0, + null); + + Kernel32.GetFileSizeEx(hFile, out var size); + + hFile.Dispose(); + + return size; + } + private class ProgressHandler : Disposable { private readonly ManualResetEvent operationsCompletedEvent; diff --git a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs index 2d88fd8f0b0c..3e81b9b2e52d 100644 --- a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs +++ b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs @@ -43,8 +43,12 @@ public async Task CopyItemsAsync(IList so // Fallback to built-in file operations return await _filesystemOperations.CopyItemsAsync(source, destination, collisions, progress, cancellationToken); } + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress, + source.Count); - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); fsProgress.Report(); var sourceNoSkip = source.Zip(collisions, (src, coll) => new { src, coll }).Where(item => item.coll != FileNameConflictResolveOptionType.Skip).Select(item => item.src); @@ -343,7 +347,19 @@ public async Task DeleteItemsAsync(IList return await _filesystemOperations.DeleteItemsAsync(source, progress, permanently, cancellationToken); } - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + long totalSize = 0; + foreach (var item in source) + { + totalSize += FileOperationsHelpers.GetFileSize(item.Path); + } + + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress, + source.Count, + totalSize); + fsProgress.Report(); var deleteFilePaths = source.Select(s => s.Path).Distinct(); @@ -457,7 +473,19 @@ public async Task MoveItemsAsync(IList so return await _filesystemOperations.MoveItemsAsync(source, destination, collisions, progress, cancellationToken); } - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + long totalSize = 0; + foreach (var item in source) + { + totalSize += FileOperationsHelpers.GetFileSize(item.Path); + } + + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress, + source.Count, + totalSize); + fsProgress.Report(); var sourceNoSkip = source.Zip(collisions, (src, coll) => new { src, coll }).Where(item => item.coll != FileNameConflictResolveOptionType.Skip).Select(item => item.src); From 67c03fa926c253b87e98dfe57bd1f116bde9eb75 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 16 Sep 2023 00:04:43 +0900 Subject: [PATCH 06/97] Update --- .../Utils/StatusCenter/StatusCenterItemProgressModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index a5ae7253b0ae..d2e23d3a8c56 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -169,7 +169,7 @@ TotalSize is not 0 || } if (ProcessedSize == 0 && Percentage is not null) - ProcessedSize = TotalSize * (long)(Percentage / 100); + ProcessedSize = (long)((double)TotalSize * Percentage / 100); ProcessingSizeSpeed = (ProcessedSize - _previousProcessedSize) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; From d20e14a35b9ae03692354ba524df4ac57fc87c37 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sat, 16 Sep 2023 13:05:25 +0200 Subject: [PATCH 07/97] Speed calc --- .../StatusCenterItemProgressModel.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index d2e23d3a8c56..ba2455183442 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -168,21 +168,25 @@ TotalSize is not 0 || } } - if (ProcessedSize == 0 && Percentage is not null) + if (Percentage is not null) ProcessedSize = (long)((double)TotalSize * Percentage / 100); - ProcessingSizeSpeed = (ProcessedSize - _previousProcessedSize) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; + if (ProcessedSize != _previousProcessedSize) + { + ProcessingSizeSpeed = (ProcessedSize - _previousProcessedSize) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; + + // NOTE: This won't work yet + ProcessingItemsCountSpeed = (ProcessedItemsCount - _previousProcessedItemsCount) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; - // NOTE: This won't work yet - ProcessingItemsCountSpeed = (ProcessedItemsCount - _previousProcessedItemsCount) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; + PropertyChanged?.Invoke(this, new(nameof(ProcessingSizeSpeed))); + PropertyChanged?.Invoke(this, new(nameof(ProcessingItemsCountSpeed))); - PropertyChanged?.Invoke(this, new(nameof(ProcessingSizeSpeed))); - PropertyChanged?.Invoke(this, new(nameof(ProcessingItemsCountSpeed))); + _previousReportTime = DateTimeOffset.Now; + _previousProcessedSize = ProcessedSize; + _previousProcessedItemsCount = ProcessedItemsCount; + } _progress?.Report(this); - _previousReportTime = DateTimeOffset.Now; - _previousProcessedSize = ProcessedSize; - _previousProcessedItemsCount = ProcessedItemsCount; } } From feaeea721ae1166e2853bd951fac49d38bb2dcfe Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 16 Sep 2023 23:56:27 +0900 Subject: [PATCH 08/97] Update --- src/Files.App/Extensions/StringExtensions.cs | 1 + src/Files.App/UserControls/StatusCenter.xaml | 33 ++++++++++++++----- .../Utils/StatusCenter/StatusCenterItem.cs | 31 +++++++++++++---- .../StatusCenterItemProgressModel.cs | 17 +++++----- .../Storage/Helpers/FileOperationsHelpers.cs | 30 +++++++++++++++-- .../Operations/ShellFilesystemOperations.cs | 10 +++++- 6 files changed, 95 insertions(+), 27 deletions(-) diff --git a/src/Files.App/Extensions/StringExtensions.cs b/src/Files.App/Extensions/StringExtensions.cs index 06d9166ad1d5..d3be80df286f 100644 --- a/src/Files.App/Extensions/StringExtensions.cs +++ b/src/Files.App/Extensions/StringExtensions.cs @@ -79,6 +79,7 @@ public static string ConvertSizeAbbreviation(this string value) return value; } + public static string ToSizeString(this double size) => ByteSize.FromBytes(size).ToSizeString(); public static string ToSizeString(this long size) => ByteSize.FromBytes(size).ToSizeString(); public static string ToSizeString(this ulong size) => ByteSize.FromBytes(size).ToSizeString(); public static string ToSizeString(this ByteSize size) => size.ToBinaryString().ConvertSizeAbbreviation(); diff --git a/src/Files.App/UserControls/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter.xaml index 819f98b9e7dc..3e534e66efc5 100644 --- a/src/Files.App/UserControls/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter.xaml @@ -185,7 +185,7 @@ x:Name="MainGraphCartesianChartClipGrid" Height="64" Margin="4,8,0,8" - BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}" + BorderBrush="{ThemeResource ControlStrongStrokeColorDefaultBrush}" BorderThickness="1" CornerRadius="4" Visibility="{x:Bind IsExpanded, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}"> @@ -202,18 +202,33 @@ - + + + + + + + @@ -352,7 +367,7 @@ - + { Values = Values, - GeometrySize = 0, - Stroke = new SolidColorPaint(new(25, 118, 210), 1), + GeometrySize = 0d, DataPadding = new(0, 0), + IsHoverable = false, + + // Stroke + Stroke = new SolidColorPaint( + new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B), + 1), + + // Fill under the stroke + Fill = new LinearGradientPaint( + new SKColor[] { + new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B, 50), + new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B, 10) + }, + new(0.5f, 0f), + new(0.5f, 1.0f), + new[] { 0.2f, 1.3f }), } }; @@ -254,7 +273,7 @@ private void ReportProgress(StatusCenterItemProgressModel value) Header = $"{HeaderBody} ({ProgressPercentage:0}%)"; ProgressPercentage = (int)p; - SpeedText = $"{value.ProcessedItemsCount:0} items ({value.ProcessingSizeSpeed:0.00} bytes) / second"; + SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s"; Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); } } @@ -266,21 +285,21 @@ private void ReportProgress(StatusCenterItemProgressModel value) case (not 0, not 0): ProgressPercentage = (int)(value.ProcessedSize * 100.0 / value.TotalSize); Header = $"{HeaderBody} ({value.ProcessedItemsCount} ({value.ProcessedSize.ToSizeString()}) / {value.ItemsCount} ({value.TotalSize.ToSizeString()}): {ProgressPercentage}%)"; - SpeedText = $"{value.ProcessedItemsCount:0} items ({value.ProcessingSizeSpeed:0.00} bytes) / second"; + SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s"; Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); break; // In progress, displaying processed size case (not 0, _): ProgressPercentage = (int)(value.ProcessedSize * 100.0 / value.TotalSize); Header = $"{HeaderBody} ({value.ProcessedSize.ToSizeString()} / {value.TotalSize.ToSizeString()}: {ProgressPercentage}%)"; - SpeedText = $"{value.ProcessingSizeSpeed:0.00} bytes / second"; + SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s"; Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); break; // In progress, displaying items count case (_, not 0): ProgressPercentage = (int)(value.ProcessedItemsCount * 100.0 / value.ItemsCount); Header = $"{HeaderBody} ({value.ProcessedItemsCount} / {value.ItemsCount}: {ProgressPercentage}%)"; - SpeedText = $"{value.ProcessedItemsCount:0} items / second"; + SpeedText = $"{value.ProcessedItemsCount:0} items/s"; Values.Add(new(value.ProcessedItemsCount * 100.0 / value.ItemsCount, value.ProcessingItemsCountSpeed)); break; default: diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index d2e23d3a8c56..15e031d83dbf 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -139,8 +139,6 @@ private void SetProperty(ref T field, T value, [CallerMemberName] string? pro public void Report(double? percentage = null) { - Percentage = percentage; - // Set the progress state as success if ((EnumerationCompleted && ProcessedItemsCount == ItemsCount && @@ -168,17 +166,20 @@ TotalSize is not 0 || } } - if (ProcessedSize == 0 && Percentage is not null) - ProcessedSize = (long)((double)TotalSize * Percentage / 100); - - ProcessingSizeSpeed = (ProcessedSize - _previousProcessedSize) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; + if (percentage is not null && Percentage != percentage) + { + ProcessedSize = (long)(TotalSize * percentage / 100); + ProcessingSizeSpeed = (ProcessedSize - _previousProcessedSize) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; - // NOTE: This won't work yet - ProcessingItemsCountSpeed = (ProcessedItemsCount - _previousProcessedItemsCount) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; + // NOTE: This won't work yet + ProcessingItemsCountSpeed = (ProcessedItemsCount - _previousProcessedItemsCount) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; + } PropertyChanged?.Invoke(this, new(nameof(ProcessingSizeSpeed))); PropertyChanged?.Invoke(this, new(nameof(ProcessingItemsCountSpeed))); + Percentage = percentage; + _progress?.Report(this); _previousReportTime = DateTimeOffset.Now; _previousProcessedSize = ProcessedSize; diff --git a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs b/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs index 582cf11a34b9..c039043fb0d0 100644 --- a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs @@ -199,7 +199,19 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera { operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + long totalSize = 0; + foreach (var item in fileToDeletePath) + { + totalSize += GetFileSize(item); + } + + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress, + fileToDeletePath.Count(), + totalSize); + fsProgress.Report(); progressHandler ??= new(); @@ -359,7 +371,19 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera { operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; - StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); + long totalSize = 0; + foreach (var item in fileToMovePath) + { + totalSize += GetFileSize(item); + } + + StatusCenterItemProgressModel fsProgress = new( + progress, + true, + FileSystemStatusCode.InProgress, + fileToMovePath.Count(), + totalSize); + fsProgress.Report(); progressHandler ??= new(); @@ -447,7 +471,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera long totalSize = 0; foreach (var item in fileToCopyPath) { - totalSize += FileOperationsHelpers.GetFileSize(item); + totalSize += GetFileSize(item); } StatusCenterItemProgressModel fsProgress = new( diff --git a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs index 3e81b9b2e52d..f60ad064e446 100644 --- a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs +++ b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs @@ -43,11 +43,19 @@ public async Task CopyItemsAsync(IList so // Fallback to built-in file operations return await _filesystemOperations.CopyItemsAsync(source, destination, collisions, progress, cancellationToken); } + + long totalSize = 0; + foreach (var item in source) + { + totalSize += FileOperationsHelpers.GetFileSize(item.Path); + } + StatusCenterItemProgressModel fsProgress = new( progress, true, FileSystemStatusCode.InProgress, - source.Count); + source.Count(), + totalSize); fsProgress.Report(); From 181552f9062c99ad7239cff651103db1d7bafe08 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sun, 17 Sep 2023 00:04:15 +0900 Subject: [PATCH 09/97] Update --- .../Storage/Helpers/FileOperationsHelpers.cs | 8 +-- .../Storage/Operations/FilesystemHelpers.cs | 69 ++----------------- .../Operations/ShellFilesystemOperations.cs | 4 +- 3 files changed, 13 insertions(+), 68 deletions(-) diff --git a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs b/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs index c039043fb0d0..c2c74ad0e26f 100644 --- a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs @@ -200,7 +200,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; long totalSize = 0; - foreach (var item in fileToDeletePath) + foreach (var item in fileToCopyPath) { totalSize += GetFileSize(item); } @@ -209,7 +209,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera progress, true, FileSystemStatusCode.InProgress, - fileToDeletePath.Count(), + fileToCopyPath.Count(), totalSize); fsProgress.Report(); @@ -372,7 +372,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; long totalSize = 0; - foreach (var item in fileToMovePath) + foreach (var item in fileToCopyPath) { totalSize += GetFileSize(item); } @@ -381,7 +381,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera progress, true, FileSystemStatusCode.InProgress, - fileToMovePath.Count(), + fileToCopyPath.Count(), totalSize); fsProgress.Report(); diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs index 9e574df7e537..35b551211e79 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs @@ -19,8 +19,6 @@ namespace Files.App.Utils.Storage { public sealed class FilesystemHelpers : IFilesystemHelpers { - #region Private Members - private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); private IShellPage associatedInstance; @@ -30,9 +28,6 @@ public sealed class FilesystemHelpers : IFilesystemHelpers private ItemManipulationModel itemManipulationModel => associatedInstance.SlimContentPage?.ItemManipulationModel; private readonly CancellationToken cancellationToken; - - #region Helpers Members - private static char[] RestrictedCharacters { get @@ -55,18 +50,7 @@ private static char[] RestrictedCharacters "LPT6", "LPT7", "LPT8", "LPT9" }; - #endregion Helpers Members - - #endregion Private Members - - #region Properties - private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); - - #endregion - - #region Constructor - public FilesystemHelpers(IShellPage associatedInstance, CancellationToken cancellationToken) { this.associatedInstance = associatedInstance; @@ -74,13 +58,6 @@ public FilesystemHelpers(IShellPage associatedInstance, CancellationToken cancel jumpListService = Ioc.Default.GetRequiredService(); filesystemOperations = new ShellFilesystemOperations(this.associatedInstance); } - - #endregion Constructor - - #region IFilesystemHelpers - - #region Create - public async Task<(ReturnResult, IStorageItem)> CreateAsync(IStorageItemWithPath source, bool registerHistory) { var returnStatus = ReturnResult.InProgress; @@ -106,10 +83,6 @@ await DialogDisplayHelper.ShowDialogAsync( return (returnStatus, result.Item2); } - #endregion Create - - #region Delete - public async Task DeleteItemsAsync(IEnumerable source, DeleteConfirmationPolicies showDialog, bool permanently, bool registerHistory) { source = await source.ToListAsync(); @@ -198,10 +171,6 @@ public Task DeleteItemsAsync(IEnumerable source, Del public Task DeleteItemAsync(IStorageItem source, DeleteConfirmationPolicies showDialog, bool permanently, bool registerHistory) => DeleteItemAsync(source.FromStorageItem(), showDialog, permanently, registerHistory); - #endregion Delete - - #region Restore - public Task RestoreItemFromTrashAsync(IStorageItem source, string destination, bool registerHistory) => RestoreItemFromTrashAsync(source.FromStorageItem(), destination, registerHistory); @@ -237,14 +206,13 @@ public async Task RestoreItemsFromTrashAsync(IEnumerable PerformOperationTypeAsync(DataPackageOperation operation, - DataPackageView packageView, - string destination, - bool showDialog, - bool registerHistory, - bool isTargetExecutable = false) + public async Task PerformOperationTypeAsync( + DataPackageOperation operation, + DataPackageView packageView, + string destination, + bool showDialog, + bool registerHistory, + bool isTargetExecutable = false) { try { @@ -293,8 +261,6 @@ public async Task PerformOperationTypeAsync(DataPackageOperation o } } - #region Copy - public Task CopyItemsAsync(IEnumerable source, IEnumerable destination, bool showDialog, bool registerHistory) => CopyItemsAsync(source.Select((item) => item.FromStorageItem()), destination, showDialog, registerHistory); @@ -418,10 +384,6 @@ public async Task CopyItemsFromClipboard(DataPackageView packageVi return ReturnResult.BadArgumentException; } - #endregion Copy - - #region Move - public Task MoveItemsAsync(IEnumerable source, IEnumerable destination, bool showDialog, bool registerHistory) => MoveItemsAsync(source.Select((item) => item.FromStorageItem()), destination, showDialog, registerHistory); @@ -528,10 +490,6 @@ public async Task MoveItemsFromClipboard(DataPackageView packageVi return returnStatus; } - #endregion Move - - #region Rename - public Task RenameAsync(IStorageItem source, string newName, NameCollisionOption collision, bool registerHistory, bool showExtensionDialog = true) => RenameAsync(source.FromStorageItem(), newName, collision, registerHistory, showExtensionDialog); @@ -595,8 +553,6 @@ await DialogDisplayHelper.ShowDialogAsync( return returnStatus; } - #endregion Rename - public async Task CreateShortcutFromClipboard(DataPackageView packageView, string destination, bool showDialog, bool registerHistory) { if (!HasDraggedStorageItems(packageView)) @@ -645,9 +601,6 @@ public async Task RecycleItemsFromClipboard(DataPackageView packag return returnStatus; } - - #endregion IFilesystemHelpers - public static bool IsValidForFilename(string name) => !string.IsNullOrWhiteSpace(name) && !ContainsRestrictedCharacters(name) && !ContainsRestrictedFileName(name); @@ -728,8 +681,6 @@ await Ioc.Default.GetRequiredService().TryGetFileAsync(item. return (newCollisions, false, itemsResult ?? new List()); } - #region Public Helpers - public static bool HasDraggedStorageItems(DataPackageView packageView) { return packageView is not null && (packageView.Contains(StandardDataFormats.StorageItems) || packageView.Contains("FileDrop")); @@ -865,10 +816,6 @@ public static bool ContainsRestrictedFileName(string input) return false; } - #endregion Public Helpers - - #region IDisposable - public void Dispose() { filesystemOperations?.Dispose(); @@ -876,7 +823,5 @@ public void Dispose() associatedInstance = null; filesystemOperations = null; } - - #endregion IDisposable } } diff --git a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs index f60ad064e446..999e2119034f 100644 --- a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs +++ b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs @@ -365,7 +365,7 @@ public async Task DeleteItemsAsync(IList progress, true, FileSystemStatusCode.InProgress, - source.Count, + source.Count(), totalSize); fsProgress.Report(); @@ -491,7 +491,7 @@ public async Task MoveItemsAsync(IList so progress, true, FileSystemStatusCode.InProgress, - source.Count, + source.Count(), totalSize); fsProgress.Report(); From 5405899d9683d5f3fea4463dc9a04250b47c2071 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sun, 17 Sep 2023 00:25:07 +0900 Subject: [PATCH 10/97] Update --- .../Compress/BaseCompressArchiveAction.cs | 2 +- .../Compress/CompressIntoArchiveAction.cs | 22 +++++----- .../Compress/CompressIntoSevenZipAction.cs | 18 ++++---- .../Compress/CompressIntoZipAction.cs | 18 ++++---- .../Decompress/BaseDecompressArchiveAction.cs | 2 +- .../Archives/Decompress/DecompressArchive.cs | 2 +- .../Decompress/DecompressArchiveHere.cs | 2 +- .../DecompressArchiveToChildFolderAction.cs | 2 +- .../MenuFlyout/ContextFlyoutItemHelper.cs | 4 +- ...hiveCreator.cs => CompressArchiveModel.cs} | 41 +++++++++++++++---- .../{ArchiveHelpers.cs => CompressHelper.cs} | 17 +++++--- .../{ZipHelpers.cs => DecompressHelper.cs} | 2 +- ...iveCreator.cs => ICompressArchiveModel.cs} | 2 +- 13 files changed, 78 insertions(+), 56 deletions(-) rename src/Files.App/Utils/Archives/{ArchiveCreator.cs => CompressArchiveModel.cs} (86%) rename src/Files.App/Utils/Archives/{ArchiveHelpers.cs => CompressHelper.cs} (92%) rename src/Files.App/Utils/Archives/{ZipHelpers.cs => DecompressHelper.cs} (99%) rename src/Files.App/Utils/Archives/{IArchiveCreator.cs => ICompressArchiveModel.cs} (97%) diff --git a/src/Files.App/Actions/Content/Archives/Compress/BaseCompressArchiveAction.cs b/src/Files.App/Actions/Content/Archives/Compress/BaseCompressArchiveAction.cs index e9e4cb06d6ba..67ad4de8c847 100644 --- a/src/Files.App/Actions/Content/Archives/Compress/BaseCompressArchiveAction.cs +++ b/src/Files.App/Actions/Content/Archives/Compress/BaseCompressArchiveAction.cs @@ -13,7 +13,7 @@ internal abstract class BaseCompressArchiveAction : BaseUIAction, IAction public override bool IsExecutable => IsContextPageTypeAdaptedToCommand() && - ArchiveHelpers.CanCompress(context.SelectedItems) && + CompressHelper.CanCompress(context.SelectedItems) && UIHelpers.CanShowDialog; public BaseCompressArchiveAction() diff --git a/src/Files.App/Actions/Content/Archives/Compress/CompressIntoArchiveAction.cs b/src/Files.App/Actions/Content/Archives/Compress/CompressIntoArchiveAction.cs index 9455c0b42a15..bb7c7a0fd2d7 100644 --- a/src/Files.App/Actions/Content/Archives/Compress/CompressIntoArchiveAction.cs +++ b/src/Files.App/Actions/Content/Archives/Compress/CompressIntoArchiveAction.cs @@ -20,7 +20,7 @@ public CompressIntoArchiveAction() public override async Task ExecuteAsync() { - var (sources, directory, fileName) = ArchiveHelpers.GetCompressDestination(context.ShellPage); + var (sources, directory, fileName) = CompressHelper.GetCompressDestination(context.ShellPage); var dialog = new CreateArchiveDialog { @@ -32,18 +32,16 @@ public override async Task ExecuteAsync() if (!dialog.CanCreate || result != ContentDialogResult.Primary) return; - IArchiveCreator creator = new ArchiveCreator - { - Sources = sources, - Directory = directory, - FileName = dialog.FileName, - Password = dialog.Password, - FileFormat = dialog.FileFormat, - CompressionLevel = dialog.CompressionLevel, - SplittingSize = dialog.SplittingSize, - }; + ICompressArchiveModel creator = new CompressArchiveModel( + sources, + directory, + dialog.FileName, + dialog.Password, + dialog.FileFormat, + dialog.CompressionLevel, + dialog.SplittingSize); - await ArchiveHelpers.CompressArchiveAsync(creator); + await CompressHelper.CompressArchiveAsync(creator); } } } diff --git a/src/Files.App/Actions/Content/Archives/Compress/CompressIntoSevenZipAction.cs b/src/Files.App/Actions/Content/Archives/Compress/CompressIntoSevenZipAction.cs index 7f4a79e79819..b4a24e40b69d 100644 --- a/src/Files.App/Actions/Content/Archives/Compress/CompressIntoSevenZipAction.cs +++ b/src/Files.App/Actions/Content/Archives/Compress/CompressIntoSevenZipAction.cs @@ -6,7 +6,7 @@ namespace Files.App.Actions internal sealed class CompressIntoSevenZipAction : BaseCompressArchiveAction { public override string Label - => string.Format("CreateNamedArchive".GetLocalizedResource(), $"{ArchiveHelpers.DetermineArchiveNameFromSelection(context.SelectedItems)}.7z"); + => string.Format("CreateNamedArchive".GetLocalizedResource(), $"{CompressHelper.DetermineArchiveNameFromSelection(context.SelectedItems)}.7z"); public override string Description => "CompressIntoSevenZipDescription".GetLocalizedResource(); @@ -17,17 +17,15 @@ public CompressIntoSevenZipAction() public override Task ExecuteAsync() { - var (sources, directory, fileName) = ArchiveHelpers.GetCompressDestination(context.ShellPage); + var (sources, directory, fileName) = CompressHelper.GetCompressDestination(context.ShellPage); - IArchiveCreator creator = new ArchiveCreator - { - Sources = sources, - Directory = directory, - FileName = fileName, - FileFormat = ArchiveFormats.SevenZip, - }; + ICompressArchiveModel creator = new CompressArchiveModel( + sources, + directory, + fileName, + fileFormat: ArchiveFormats.SevenZip); - return ArchiveHelpers.CompressArchiveAsync(creator); + return CompressHelper.CompressArchiveAsync(creator); } } } diff --git a/src/Files.App/Actions/Content/Archives/Compress/CompressIntoZipAction.cs b/src/Files.App/Actions/Content/Archives/Compress/CompressIntoZipAction.cs index 6c473f7beb82..c611d47bacd7 100644 --- a/src/Files.App/Actions/Content/Archives/Compress/CompressIntoZipAction.cs +++ b/src/Files.App/Actions/Content/Archives/Compress/CompressIntoZipAction.cs @@ -6,7 +6,7 @@ namespace Files.App.Actions internal sealed class CompressIntoZipAction : BaseCompressArchiveAction { public override string Label - => string.Format("CreateNamedArchive".GetLocalizedResource(), $"{ArchiveHelpers.DetermineArchiveNameFromSelection(context.SelectedItems)}.zip"); + => string.Format("CreateNamedArchive".GetLocalizedResource(), $"{CompressHelper.DetermineArchiveNameFromSelection(context.SelectedItems)}.zip"); public override string Description => "CompressIntoZipDescription".GetLocalizedResource(); @@ -17,17 +17,15 @@ public CompressIntoZipAction() public override Task ExecuteAsync() { - var (sources, directory, fileName) = ArchiveHelpers.GetCompressDestination(context.ShellPage); + var (sources, directory, fileName) = CompressHelper.GetCompressDestination(context.ShellPage); - IArchiveCreator creator = new ArchiveCreator - { - Sources = sources, - Directory = directory, - FileName = fileName, - FileFormat = ArchiveFormats.Zip, - }; + ICompressArchiveModel creator = new CompressArchiveModel( + sources, + directory, + fileName, + fileFormat: ArchiveFormats.SevenZip); - return ArchiveHelpers.CompressArchiveAsync(creator); + return CompressHelper.CompressArchiveAsync(creator); } } } diff --git a/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs b/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs index 52075e7e7978..f5f47979ab74 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs @@ -16,7 +16,7 @@ public virtual HotKey HotKey public override bool IsExecutable => (IsContextPageTypeAdaptedToCommand() && - ArchiveHelpers.CanDecompress(context.SelectedItems) || + CompressHelper.CanDecompress(context.SelectedItems) || CanDecompressInsideArchive()) && UIHelpers.CanShowDialog; diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs index 62754bf93664..c9ed484b265b 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs @@ -23,7 +23,7 @@ public DecompressArchive() public override Task ExecuteAsync() { - return ArchiveHelpers.DecompressArchive(context.ShellPage); + return CompressHelper.DecompressArchive(context.ShellPage); } protected override bool CanDecompressInsideArchive() diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs index 7106d0a18bdd..7e0419bfc613 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs @@ -17,7 +17,7 @@ public DecompressArchiveHere() public override Task ExecuteAsync() { - return ArchiveHelpers.DecompressArchiveHere(context.ShellPage); + return CompressHelper.DecompressArchiveHere(context.ShellPage); } } } diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs index 7d1dbc3c4d35..44836ae649d7 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs @@ -17,7 +17,7 @@ public DecompressArchiveToChildFolderAction() public override Task ExecuteAsync() { - return ArchiveHelpers.DecompressArchiveToChildFolder(context.ShellPage); + return CompressHelper.DecompressArchiveToChildFolder(context.ShellPage); } protected override void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs b/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs index 3327eac8b7c9..643d3cc54726 100644 --- a/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs +++ b/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs @@ -503,7 +503,7 @@ public static List GetBaseItemMenuItems( new ContextMenuFlyoutItemViewModelBuilder(commands.CompressIntoZip).Build(), new ContextMenuFlyoutItemViewModelBuilder(commands.CompressIntoSevenZip).Build(), }, - ShowItem = itemsSelected && ArchiveHelpers.CanCompress(selectedItems) + ShowItem = itemsSelected && CompressHelper.CanCompress(selectedItems) }, new ContextMenuFlyoutItemViewModel { @@ -519,7 +519,7 @@ public static List GetBaseItemMenuItems( new ContextMenuFlyoutItemViewModelBuilder(commands.DecompressArchiveHere).Build(), new ContextMenuFlyoutItemViewModelBuilder(commands.DecompressArchiveToChildFolder).Build(), }, - ShowItem = ArchiveHelpers.CanDecompress(selectedItems) + ShowItem = CompressHelper.CanDecompress(selectedItems) }, new ContextMenuFlyoutItemViewModel() { diff --git a/src/Files.App/Utils/Archives/ArchiveCreator.cs b/src/Files.App/Utils/Archives/CompressArchiveModel.cs similarity index 86% rename from src/Files.App/Utils/Archives/ArchiveCreator.cs rename to src/Files.App/Utils/Archives/CompressArchiveModel.cs index 2da88e9a719e..622837c74781 100644 --- a/src/Files.App/Utils/Archives/ArchiveCreator.cs +++ b/src/Files.App/Utils/Archives/CompressArchiveModel.cs @@ -10,7 +10,7 @@ namespace Files.App.Utils.Archives /// /// Provides an archive creation support. /// - public class ArchiveCreator : IArchiveCreator + public class CompressArchiveModel : ICompressArchiveModel { /// /// Represents the total number of items to be processed. @@ -106,16 +106,38 @@ public IProgress Progress /// public ArchiveSplittingSizes SplittingSize { get; init; } - public ArchiveCreator() + public CompressArchiveModel( + string[] source, + string directory, + string fileName, + string? password = null, + ArchiveFormats fileFormat = ArchiveFormats.Zip, + ArchiveCompressionLevels compressionLevel = ArchiveCompressionLevels.Normal, + ArchiveSplittingSizes splittingSize = ArchiveSplittingSizes.None) { - // Initialize - _fileSystemProgress = new(Progress, true, FileSystemStatusCode.InProgress); + long totalSize = 0; + foreach (var item in Sources) + { + totalSize += FileOperationsHelpers.GetFileSize(item); + } + + _fileSystemProgress = new( + Progress, + true, + FileSystemStatusCode.InProgress, + Sources.Count(), + totalSize); + _Progress = new Progress(); + + Sources = source; + Directory = directory; + FileName = fileName; + Password = password ?? string.Empty; ArchivePath = string.Empty; - Sources = Enumerable.Empty(); - FileFormat = ArchiveFormats.Zip; - CompressionLevel = ArchiveCompressionLevels.Normal; - SplittingSize = ArchiveSplittingSizes.None; + FileFormat = fileFormat; + CompressionLevel = compressionLevel; + SplittingSize = splittingSize; _fileSystemProgress.Report(0); } @@ -131,7 +153,7 @@ public async Task RunCreationAsync() { string[] sources = Sources.ToArray(); - var compressor = new SevenZipCompressor + var compressor = new SevenZipCompressor() { ArchiveFormat = SevenZipArchiveFormat, CompressionLevel = SevenZipCompressionLevel, @@ -142,6 +164,7 @@ public async Task RunCreationAsync() PreserveDirectoryRoot = sources.Length > 1, EventSynchronization = EventSynchronizationStrategy.AlwaysAsynchronous, }; + compressor.Compressing += Compressor_Compressing; compressor.CompressionFinished += Compressor_CompressionFinished; diff --git a/src/Files.App/Utils/Archives/ArchiveHelpers.cs b/src/Files.App/Utils/Archives/CompressHelper.cs similarity index 92% rename from src/Files.App/Utils/Archives/ArchiveHelpers.cs rename to src/Files.App/Utils/Archives/CompressHelper.cs index d9e462d0411b..554d2ee9b712 100644 --- a/src/Files.App/Utils/Archives/ArchiveHelpers.cs +++ b/src/Files.App/Utils/Archives/CompressHelper.cs @@ -11,7 +11,10 @@ namespace Files.App.Utils.Archives { - public static class ArchiveHelpers + /// + /// Provides static helper for compressing archive. + /// + public static class CompressHelper { private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); @@ -59,7 +62,7 @@ public static (string[] Sources, string directory, string fileName) GetCompressD return (sources, directory, fileName); } - public static async Task CompressArchiveAsync(IArchiveCreator creator) + public static async Task CompressArchiveAsync(ICompressArchiveModel creator) { var archivePath = creator.GetArchivePath(); @@ -110,6 +113,8 @@ public static async Task CompressArchiveAsync(IArchiveCreator creator) } } + // TODO: Move all below code to DecompressHelper class + private static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFolder? destinationFolder, string password) { if (archive is null || destinationFolder is null) @@ -125,7 +130,7 @@ private static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFol FileOperationType.Extract, extractCancellation); - await FilesystemTasks.Wrap(() => ZipHelpers.ExtractArchive(archive, destinationFolder, password, banner.ProgressEventSource, extractCancellation.Token)); + await FilesystemTasks.Wrap(() => DecompressHelper.ExtractArchive(archive, destinationFolder, password, banner.ProgressEventSource, extractCancellation.Token)); _statusCenterViewModel.RemoveItem(banner); @@ -149,7 +154,7 @@ public static async Task DecompressArchive(IShellPage associatedInstance) if (archive is null) return; - var isArchiveEncrypted = await FilesystemTasks.Wrap(() => ZipHelpers.IsArchiveEncrypted(archive)); + var isArchiveEncrypted = await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive)); var password = string.Empty; DecompressArchiveDialog decompressArchiveDialog = new(); @@ -197,7 +202,7 @@ public static async Task DecompressArchiveHere(IShellPage associatedInstance) BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); - if (await FilesystemTasks.Wrap(() => ZipHelpers.IsArchiveEncrypted(archive))) + if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) { DecompressArchiveDialog decompressArchiveDialog = new(); DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) @@ -232,7 +237,7 @@ public static async Task DecompressArchiveToChildFolder(IShellPage associatedIns BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); BaseStorageFolder destinationFolder = null; - if (await FilesystemTasks.Wrap(() => ZipHelpers.IsArchiveEncrypted(archive))) + if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) { DecompressArchiveDialog decompressArchiveDialog = new(); DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) diff --git a/src/Files.App/Utils/Archives/ZipHelpers.cs b/src/Files.App/Utils/Archives/DecompressHelper.cs similarity index 99% rename from src/Files.App/Utils/Archives/ZipHelpers.cs rename to src/Files.App/Utils/Archives/DecompressHelper.cs index 313b4387761e..a4acf926643d 100644 --- a/src/Files.App/Utils/Archives/ZipHelpers.cs +++ b/src/Files.App/Utils/Archives/DecompressHelper.cs @@ -7,7 +7,7 @@ namespace Files.App.Utils.Archives { - public static class ZipHelpers + public static class DecompressHelper { private static async Task GetZipFile(BaseStorageFile archive, string password = "") { diff --git a/src/Files.App/Utils/Archives/IArchiveCreator.cs b/src/Files.App/Utils/Archives/ICompressArchiveModel.cs similarity index 97% rename from src/Files.App/Utils/Archives/IArchiveCreator.cs rename to src/Files.App/Utils/Archives/ICompressArchiveModel.cs index b7af5a9134b1..e61b3e0e808f 100644 --- a/src/Files.App/Utils/Archives/IArchiveCreator.cs +++ b/src/Files.App/Utils/Archives/ICompressArchiveModel.cs @@ -6,7 +6,7 @@ namespace Files.App.Utils.Archives /// /// Represents an interface for archive creation support. /// - public interface IArchiveCreator + public interface ICompressArchiveModel { /// /// File path to archive. From 80b2e5f100e75c0bc9ff74ebe26ec898834e13a1 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sun, 17 Sep 2023 00:27:36 +0900 Subject: [PATCH 11/97] Update src/Files.App/Utils/StatusCenter/StatusCenterItem.cs Co-authored-by: Steve --- src/Files.App/Utils/StatusCenter/StatusCenterItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 2719f56c308d..8bd21ee885a2 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -151,7 +151,7 @@ public bool IsCancelable public ICommand CancelCommand { get; } - public StatusCenterItem(string message, string title, float progress, ReturnResult status, FileOperationType operation, CancellationTokenSource? operationCancellationToken = null) + public StatusCenterItem(string message, string title, float progress, ReturnResult status, FileOperationType operation, CancellationTokenSource operationCancellationToken = default) { _operationCancellationToken = operationCancellationToken; SubHeader = message; From 6f9fe761aa14957719024f7deeed811d9cec73b7 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sun, 17 Sep 2023 00:40:39 +0900 Subject: [PATCH 12/97] Remove unncessary comments --- .../Utils/Storage/Operations/FilesystemOperations.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs index 6d07963f08d6..c6e6f6bd17c6 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs @@ -114,8 +114,6 @@ public async Task CopyAsync(IStorageItemWithPath source, string true, FileSystemStatusCode.InProgress); - // TODO: Get total size - fsProgress.Report(); if (destination.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal)) @@ -299,8 +297,6 @@ public async Task MoveAsync(IStorageItemWithPath source, string true, FileSystemStatusCode.InProgress); - // TODO: Get total size - fsProgress.Report(); if (source.Path == destination) @@ -479,8 +475,6 @@ public async Task DeleteAsync(IStorageItemWithPath source, IPro true, FileSystemStatusCode.InProgress); - // TODO: Get total size - fsProgress.Report(); bool deleteFromRecycleBin = RecycleBinHelpers.IsPathUnderRecycleBin(source.Path); @@ -572,8 +566,6 @@ public async Task RenameAsync( { StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress); - // TODO: Get total size - fsProgress.Report(); if (Path.GetFileName(source.Path) == newName && collision == NameCollisionOption.FailIfExists) From 9e65a238f8f8eaf0bd003e6d875cb1593b6ef1a6 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 17 Sep 2023 03:36:44 +0200 Subject: [PATCH 13/97] Get progress percentage as double --- .../Utils/Shell/ShellFileOperations2.cs | 1075 +++++++++++++++++ .../StatusCenterItemProgressModel.cs | 15 +- .../Storage/Helpers/FileOperationsHelpers.cs | 26 +- 3 files changed, 1096 insertions(+), 20 deletions(-) create mode 100644 src/Files.App/Utils/Shell/ShellFileOperations2.cs diff --git a/src/Files.App/Utils/Shell/ShellFileOperations2.cs b/src/Files.App/Utils/Shell/ShellFileOperations2.cs new file mode 100644 index 000000000000..058bf936931d --- /dev/null +++ b/src/Files.App/Utils/Shell/ShellFileOperations2.cs @@ -0,0 +1,1075 @@ +using System.Runtime.InteropServices; +using Vanara.PInvoke; +using static Vanara.PInvoke.Shell32; +using static Vanara.Windows.Shell.ShellFileOperations; + +namespace Vanara.Windows.Shell; + +/// Queued and static file operations using the Shell. +/// +/// https://github.com/dahall/Vanara/blob/master/Windows.Shell.Common/ShellFileOperations/ShellFileOperations.cs +public class ShellFileOperations2 : IDisposable +{ + private const OperationFlags defaultOptions = OperationFlags.AllowUndo | OperationFlags.NoConfirmMkDir; + private ShellFileOperationDialog customProgressDialog; + private bool disposedValue = false; + private IFileOperation op; + private OperationFlags opFlags = defaultOptions; + private HWND owner; + private readonly IFileOperationProgressSink sink; + private readonly uint sinkCookie; + + /// Initializes a new instance of the class. + /// The window that owns the modal dialog. This value can be . + public ShellFileOperations2(HWND owner = default) + { + op = new IFileOperation(); + if (owner != default) + { + op.SetOwnerWindow(owner); + } + + sink = new OpSink(this); + sinkCookie = op.Advise(sink); + } + + /// Initializes a new instance of the class. + /// An existing instance. + public ShellFileOperations2(IFileOperation operation) + { + op = operation; + sink = new OpSink(this); + sinkCookie = op.Advise(sink); + } + + /// Finalizes an instance of the class. + ~ShellFileOperations2() + { + Dispose(false); + } + + /// Performs caller-implemented actions after the last operation performed by the call to IFileOperation is complete. + public event EventHandler FinishOperations; + + /// Performs caller-implemented actions after the copy process for each item is complete. + public event EventHandler PostCopyItem; + + /// Performs caller-implemented actions after the delete process for each item is complete. + public event EventHandler PostDeleteItem; + + /// Performs caller-implemented actions after the move process for each item is complete. + public event EventHandler PostMoveItem; + + /// Performs caller-implemented actions after the new item is created. + public event EventHandler PostNewItem; + + /// Performs caller-implemented actions after the rename process for each item is complete. + public event EventHandler PostRenameItem; + + /// Performs caller-implemented actions before the copy process for each item begins. + public event EventHandler PreCopyItem; + + /// Performs caller-implemented actions before the delete process for each item begins. + public event EventHandler PreDeleteItem; + + /// Performs caller-implemented actions before the move process for each item begins. + public event EventHandler PreMoveItem; + + /// Performs caller-implemented actions before the process to create a new item begins. + public event EventHandler PreNewItem; + + /// Performs caller-implemented actions before the rename process for each item begins. + public event EventHandler PreRenameItem; + + /// Performs caller-implemented actions before any specific file operations are performed. + public event EventHandler StartOperations; + + /// Occurs when a progress update is received. + public event ProgressChangedEventHandler UpdateProgress; + + /// + /// Gets a value that states whether any file operations initiated by a call to were stopped before they + /// were complete. The operations could be stopped either by user action or silently by the system. + /// + /// if any file operations were aborted before they were complete; otherwise, . + public bool AnyOperationsAborted => op.GetAnyOperationsAborted(); + + /// Specifies a dialog box used to display the progress of the operation. + /// A ShellFileOperationDialog object that represents the dialog box. + public ShellFileOperationDialog CustomProgressDialog + { + get => customProgressDialog; + set => op.SetProgressDialog((customProgressDialog = value)?.iProgressDialog); + } + + /// Gets or sets options that control file operations. + public OperationFlags Options + { + get => opFlags; + set { if (value == opFlags) { return; } op.SetOperationFlags((FILEOP_FLAGS)(opFlags = value)); } + } + + /// Gets or sets the parent or owner window for progress and dialog windows. + /// The owner window of the operation. This window will receive error messages. + public HWND OwnerWindow + { + get => owner; + set => op.SetOwnerWindow(owner = value); + } + + /// Gets the number of queued operations. + public int QueuedOperations { get; protected set; } + + /// Copies a single item to a specified destination using the Shell to provide progress and error dialogs. + /// A string that specifies the source item's full file path. + /// A string that specifies the full path of the destination folder to contain the copy of the item. + /// + /// An optional new name for the item after it has been copied. This can be . If , the name + /// of the destination item is the same as the source. + /// + /// Options that control file operations. + public static void Copy(string source, string dest, string newName = null, OperationFlags options = defaultOptions) + { + using ShellItem shfile = new(source); + using ShellFolder shfld = new(dest); + Copy(shfile, shfld, newName, options); + } + + /// Copies a single item to a specified destination using the Shell to provide progress and error dialogs. + /// A that specifies the source item. + /// A that specifies the destination folder to contain the copy of the item. + /// + /// An optional new name for the item after it has been copied. This can be . If , the name + /// of the destination item is the same as the source. + /// + /// Options that control file operations. + public static void Copy(ShellItem source, ShellFolder dest, string newName = null, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new(); + sop.Options = options; + HRESULT hr = HRESULT.S_OK; + sop.PostCopyItem += OnPost; + try + { + sop.QueueCopyOperation(source, dest, newName); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostCopyItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// Copies a set of items to a specified destination using the Shell to provide progress and error dialogs. + /// + /// An of instances that represent the group of items to be copied. + /// + /// A that specifies the destination folder to contain the copy of the items. + /// Options that control file operations. + public static void Copy(IEnumerable sourceItems, ShellFolder dest, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new(); + sop.Options = options; + HRESULT hr = HRESULT.S_OK; + sop.PostCopyItem += OnPost; + try + { + sop.QueueCopyOperation(sourceItems, dest); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostCopyItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// Deletes a single item using the Shell to provide progress and error dialogs. + /// A string that specifies the full path of the item to be deleted. + /// Options that control file operations. + public static void Delete(string source, OperationFlags options = defaultOptions) + { + using ShellItem shfile = new(source); + Delete(shfile, options); + } + + /// Deletes a single item using the Shell to provide progress and error dialogs. + /// A that specifies the item to be deleted. + /// Options that control file operations. + public static void Delete(ShellItem source, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new(); + sop.Options = options; + HRESULT hr = HRESULT.S_OK; + sop.PostDeleteItem += OnPost; + try + { + sop.QueueDeleteOperation(source); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostDeleteItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// Deletes a set of items using the Shell to provide progress and error dialogs. + /// + /// An of instances which represents the group of items to be deleted. + /// + /// Options that control file operations. + public static void Delete(IEnumerable sourceItems, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new(); + sop.Options = options; + HRESULT hr = HRESULT.S_OK; + sop.PostDeleteItem += OnPost; + try + { + sop.QueueDeleteOperation(sourceItems); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostDeleteItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// Moves a single item to a specified destination using the Shell to provide progress and error dialogs. + /// A string that specifies the source item's full file path. + /// A string that specifies the full path of the destination folder to contain the copy of the item. + /// + /// An optional new name for the item in its new location. This can be . If , the name of the + /// destination item is the same as the source. + /// + /// Options that control file operations. + public static void Move(string source, string dest, string newName = null, OperationFlags options = defaultOptions) + { + using ShellItem shfile = new(source); + using ShellFolder shfld = new(dest); + Move(shfile, shfld, newName, options); + } + + /// Moves a single item to a specified destination using the Shell to provide progress and error dialogs. + /// A that specifies the source item. + /// A that specifies the destination folder to contain the moved item. + /// + /// An optional new name for the item in its new location. This can be . If , the name of the + /// destination item is the same as the source. + /// + /// Options that control file operations. + public static void Move(ShellItem source, ShellFolder dest, string newName = null, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new() { Options = options }; + HRESULT hr = HRESULT.S_OK; + sop.PostMoveItem += OnPost; + try + { + sop.QueueMoveOperation(source, dest, newName); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostMoveItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// Moves a set of items to a specified destination using the Shell to provide progress and error dialogs. + /// + /// An of instances which represents the group of items to be moved. + /// + /// A that specifies the destination folder to contain the moved items. + /// Options that control file operations. + public static void Move(IEnumerable sourceItems, ShellFolder dest, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new(); + sop.Options = options; + HRESULT hr = HRESULT.S_OK; + sop.PostMoveItem += OnPost; + try + { + sop.QueueMoveOperation(sourceItems, dest); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostMoveItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// Creates a new item in a specified location using the Shell to provide progress and error dialogs. + /// A that specifies the destination folder that will contain the new item. + /// The file name of the new item, for instance Newfile.txt. + /// A value that specifies the file system attributes for the file or folder. + /// + /// The name of the template file (for example Excel9.xls) that the new item is based on, stored in one of the following locations: + /// + /// + /// CSIDL_COMMON_TEMPLATES. The default path for this folder is %ALLUSERSPROFILE%\Templates. + /// + /// + /// CSIDL_TEMPLATES. The default path for this folder is %USERPROFILE%\Templates. + /// + /// + /// %SystemRoot%\shellnew + /// + /// + /// + /// This is a string used to specify an existing file of the same type as the new file, containing the minimal content that an + /// application wants to include in any new file. + /// + /// This parameter is normally to specify a new, blank file. + /// + /// Options that control file operations. + public static void NewItem(ShellFolder dest, string name, System.IO.FileAttributes attr = System.IO.FileAttributes.Normal, string template = null, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new(); + sop.Options = options; + HRESULT hr = HRESULT.S_OK; + sop.PostNewItem += OnPost; + try + { + sop.QueueNewItemOperation(dest, name, attr, template); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostRenameItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// Renames a single item to a new display name using the Shell to provide progress and error dialogs. + /// A that specifies the source item. + /// The new display name of the item. + /// Options that control file operations. + public static void Rename(ShellItem source, string newName = null, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new(); + sop.Options = options; + HRESULT hr = HRESULT.S_OK; + sop.PostRenameItem += OnPost; + try + { + sop.QueueRenameOperation(source, newName); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostRenameItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// + /// Renames a set of items that are to be given a new display name using the Shell to provide progress and error dialogs. All items are + /// given the same name. + /// + /// + /// An of instances which represents the group of items to be renamed. + /// + /// The new display name of the items. + /// Options that control file operations. + public static void Rename(IEnumerable sourceItems, string newName, OperationFlags options = defaultOptions) + { + using ShellFileOperations2 sop = new(); + sop.Options = options; + HRESULT hr = HRESULT.S_OK; + sop.PostRenameItem += OnPost; + try + { + sop.QueueRenameOperation(sourceItems, newName); + sop.PerformOperations(); + hr.ThrowIfFailed(); + } + finally + { + sop.PostRenameItem -= OnPost; + } + + void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; + } + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// Executes all selected operations. + /// + /// This method is called last to execute those actions that have been specified earlier by calling their individual methods. For + /// instance, does not rename the item, it simply sets the parameters. The actual + /// renaming is done when you call PerformOperations. + /// + public void PerformOperations() + { + op.PerformOperations(); + QueuedOperations = 0; + } + + /// Declares a set of properties and values to be set on an item. + /// The item to receive the new property values. + /// + /// An , which contains a dictionary of objects that specify the properties to be set and their new values. + /// + public void QueueApplyPropertiesOperation(ShellItem item, ShellItemPropertyUpdates props) + { + op.SetProperties(props.IPropertyChangeArray); + op.ApplyPropertiesToItem(item.IShellItem); + QueuedOperations++; + } + + /// Declares a set of properties and values to be set on a set of items. + /// + /// An of instances that represent the group of items to which to apply the properties. + /// + /// + /// An , which contains a dictionary of objects that specify the properties to be set and their new values. + /// + public void QueueApplyPropertiesOperation(IEnumerable items, ShellItemPropertyUpdates props) + { + op.SetProperties(props.IPropertyChangeArray); + op.ApplyPropertiesToItems(GetSHArray(items).IShellItemArray); + QueuedOperations++; + } + + /// Declares a single item that is to be copied to a specified destination. + /// A that specifies the source item. + /// A that specifies the destination folder to contain the copy of the item. + /// + /// An optional new name for the item after it has been copied. This can be . If , the name + /// of the destination item is the same as the source. + /// + public void QueueCopyOperation(ShellItem source, ShellFolder dest, string newName = null) + { + op.CopyItem(source.IShellItem, dest.IShellItem, newName, null); + QueuedOperations++; + } + + /// Declares a set of items that are to be copied to a specified destination. + /// + /// An of instances that represent the group of items to be copied. + /// + /// A that specifies the destination folder to contain the copy of the items. + public void QueueCopyOperation(IEnumerable sourceItems, ShellFolder dest) + { + op.CopyItems(GetSHArray(sourceItems).IShellItemArray, dest.IShellItem); + QueuedOperations++; + } + + /// Declares a single item that is to be deleted. + /// >A that specifies the item to be deleted. + public void QueueDeleteOperation(ShellItem item) + { + op.DeleteItem(item.IShellItem, null); + QueuedOperations++; + } + + /// Declares a set of items that are to be deleted. + /// + /// An of instances which represents the group of items to be deleted. + /// + public void QueueDeleteOperation(IEnumerable items) + { + op.DeleteItems(GetSHArray(items).IShellItemArray); + QueuedOperations++; + } + + /// Declares a single item that is to be moved to a specified destination. + /// A that specifies the source item. + /// A that specifies the destination folder to contain the moved item. + /// + /// An optional new name for the item in its new location. This can be . If , the name of the + /// destination item is the same as the source. + /// + public void QueueMoveOperation(ShellItem source, ShellFolder dest, string newName = null) + { + op.MoveItem(source.IShellItem, dest.IShellItem, newName, null); + QueuedOperations++; + } + + /// Declares a set of items that are to be moved to a specified destination. + /// + /// An of instances which represents the group of items to be moved. + /// + /// A that specifies the destination folder to contain the moved items. + public void QueueMoveOperation(IEnumerable sourceItems, ShellFolder dest) + { + op.MoveItems(GetSHArray(sourceItems).IShellItemArray, dest.IShellItem); + QueuedOperations++; + } + + /// Declares a new item that is to be created in a specified location. + /// A that specifies the destination folder that will contain the new item. + /// The file name of the new item, for instance Newfile.txt. + /// A value that specifies the file system attributes for the file or folder. + /// + /// The name of the template file (for example Excel9.xls) that the new item is based on, stored in one of the following locations: + /// + /// + /// CSIDL_COMMON_TEMPLATES. The default path for this folder is %ALLUSERSPROFILE%\Templates. + /// + /// + /// CSIDL_TEMPLATES. The default path for this folder is %USERPROFILE%\Templates. + /// + /// + /// %SystemRoot%\shellnew + /// + /// + /// + /// This is a string used to specify an existing file of the same type as the new file, containing the minimal content that an + /// application wants to include in any new file. + /// + /// This parameter is normally to specify a new, blank file. + /// + public void QueueNewItemOperation(ShellFolder dest, string name, System.IO.FileAttributes attr = System.IO.FileAttributes.Normal, string template = null) + { + op.NewItem(dest.IShellItem, attr, name, template, null); + QueuedOperations++; + } + + /// Declares a single item that is to be given a new display name. + /// A that specifies the source item. + /// The new display name of the item. + public void QueueRenameOperation(ShellItem source, string newName) + { + op.RenameItem(source.IShellItem, newName, null); + QueuedOperations++; + } + + /// Declares a set of items that are to be given a new display name. All items are given the same name. + /// + /// An of instances which represents the group of items to be renamed. + /// + /// The new display name of the items. + public void QueueRenameOperation(IEnumerable sourceItems, string newName) + { + op.RenameItems(GetSHArray(sourceItems).IShellItemArray, newName); + QueuedOperations++; + } + + /// Releases unmanaged and - optionally - managed resources. + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // Dispose managed state (managed objects). + } + + if (sink != null) + { + op.Unadvise(sinkCookie); + } + + op = null; + disposedValue = true; + } + } + + private ShellItemArray GetSHArray(IEnumerable items) => items is ShellItemArray a ? a : new ShellItemArray(items); + + private class OpSink : IFileOperationProgressSink + { + private readonly ShellFileOperations2 parent; + + public OpSink(ShellFileOperations2 ops) => parent = ops; + + public HRESULT FinishOperations(HRESULT hrResult) => CallChkErr(() => parent.FinishOperations?.Invoke(parent, new ShellFileOpEventArgs(0, null, null, null, null, hrResult))); + + public HRESULT PauseTimer() => HRESULT.E_NOTIMPL; + + public HRESULT PostCopyItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, HRESULT hrCopy, IShellItem psiNewlyCreated) => + CallChkErr(() => parent.PostCopyItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, psiDestinationFolder, psiNewlyCreated, pszNewName, hrCopy))); + + public HRESULT PostDeleteItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, HRESULT hrDelete, IShellItem psiNewlyCreated) => + CallChkErr(() => parent.PostDeleteItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, null, psiNewlyCreated, null, hrDelete))); + + public HRESULT PostMoveItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, HRESULT hrMove, IShellItem psiNewlyCreated) => + CallChkErr(() => parent.PostMoveItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, psiDestinationFolder, psiNewlyCreated, pszNewName, hrMove))); + + public HRESULT PostNewItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName, uint dwFileAttributes, HRESULT hrNew, IShellItem psiNewItem) => + CallChkErr(() => parent.PostNewItem?.Invoke(parent, new ShellFileNewOpEventArgs(dwFlags, null, psiDestinationFolder, psiNewItem, pszNewName, hrNew, pszTemplateName, dwFileAttributes))); + + public HRESULT PostRenameItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, HRESULT hrRename, IShellItem psiNewlyCreated) => + CallChkErr(() => parent.PostRenameItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, null, psiNewlyCreated, pszNewName, hrRename))); + + public HRESULT PreCopyItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName) => + CallChkErr(() => parent.PreCopyItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, psiDestinationFolder, null, pszNewName))); + + public HRESULT PreDeleteItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem) => + CallChkErr(() => parent.PreDeleteItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem))); + + public HRESULT PreMoveItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName) => + CallChkErr(() => parent.PreMoveItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, psiDestinationFolder, null, pszNewName))); + + public HRESULT PreNewItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName) => + CallChkErr(() => parent.PreNewItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, null, psiDestinationFolder, null, pszNewName))); + + public HRESULT PreRenameItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName) => CallChkErr(() => parent.PreRenameItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, null, null, pszNewName))); + + public HRESULT ResetTimer() => HRESULT.E_NOTIMPL; + + public HRESULT ResumeTimer() => HRESULT.E_NOTIMPL; + + public HRESULT StartOperations() => CallChkErr(() => parent.StartOperations?.Invoke(parent, EventArgs.Empty)); + + public HRESULT UpdateProgress(uint iWorkTotal, uint iWorkSoFar) => CallChkErr(() => parent.UpdateProgress?.Invoke(parent, new ProgressChangedEventArgs(iWorkTotal == 0 ? 0 : iWorkSoFar * 100.0 / iWorkTotal, null))); + + private HRESULT CallChkErr(Action action) + { + try { action(); } + catch (COMException comex) { return comex.ErrorCode; } + catch (Win32Exception w32ex) { return new Win32Error(unchecked((uint)w32ex.NativeErrorCode)).ToHRESULT(); } + catch (Exception e) + { + return e.HResult; + } + return HRESULT.S_OK; + } + } + + /// Arguments supplied to the event. + /// + public class ShellFileNewOpEventArgs : ShellFileOpEventArgs + { + internal ShellFileNewOpEventArgs(TRANSFER_SOURCE_FLAGS flags, IShellItem source, IShellItem folder, IShellItem dest, string name, HRESULT hr, string templ, uint attr) : + base(flags, source, folder, dest, name, hr) + { + TemplateName = templ; + FileAttributes = (System.IO.FileAttributes)attr; + } + + /// Gets the name of the template. + /// The name of the template. + public string TemplateName { get; protected set; } + + /// Gets the file attributes. + /// The file attributes. + public System.IO.FileAttributes FileAttributes { get; protected set; } + } + + /// + /// Arguments supplied to events from . Depending on the event, some properties may not be set. + /// + /// + public class ShellFileOpEventArgs : EventArgs + { + internal ShellFileOpEventArgs(TRANSFER_SOURCE_FLAGS flags, IShellItem source, IShellItem folder = null, IShellItem dest = null, string name = null, HRESULT hr = default) + { + Flags = (TransferFlags)flags; + if (source != null) try { SourceItem = ShellItem.Open(source); } catch { } + if (folder != null) try { DestFolder = ShellItem.Open(folder); } catch { } + if (dest != null) try { DestItem = ShellItem.Open(dest); } catch { } + Name = name; + Result = hr; + } + + /// Gets the destination folder. + /// The destination folder. + public ShellItem DestFolder { get; protected set; } + + /// Gets the destination item. + /// The destination item. + public ShellItem DestItem { get; protected set; } + + /// Gets the tranfer flag values. + /// The flags. + public TransferFlags Flags { get; protected set; } + + /// Gets the name of the item. + /// The item name. + public string Name { get; protected set; } + + /// Gets the result of the operation. + /// The result. + public HRESULT Result { get; protected set; } + + /// Gets the source item. + /// The source item. + public ShellItem SourceItem { get; protected set; } + + /// Returns a that represents this instance. + /// A that represents this instance. + public override string ToString() => $"HR:{Result};Src:{SourceItem};DFld:{DestFolder};Dst:{DestItem};Name:{Name}"; + } + + /// The Shell's progress dialog. + /// + public class ShellFileOperationDialog : Component + { + internal IOperationsProgressDialog iProgressDialog; + + private ShellItem currentItem; + private long currentItems; + private long currentProgress; + private long currentSize; + private ShellItem destItem; + private OperationMode mode; + private OperationType operation; + private ShellItem sourceItem; + private long totalItems = 100; + private long totalProgress = 100; + private long totalSize = 100; + + /// Initializes a new instance of the class. + public ShellFileOperationDialog() + { + } + + /// Initializes a new instance of the class. + /// The container. + public ShellFileOperationDialog(IContainer container) : this() + { + container.Add(this); + } + + /// Provides operation status flags for ShellFileOperationDialog. + public enum DialogStatus + { + /// The dialog has not been started. + NotStarted = 0, + + /// Operation is running, no user intervention. + Running = PDOPSTATUS.PDOPS_RUNNING, + + /// Operation has been paused by the user. + Paused = PDOPSTATUS.PDOPS_PAUSED, + + /// Operation has been canceled by the user - now go undo. + Cancelled = PDOPSTATUS.PDOPS_CANCELLED, + + /// Operation has been stopped by the user - terminate completely. + Stopped = PDOPSTATUS.PDOPS_STOPPED, + + /// Operation has gone as far as it can go without throwing error dialogs. + Errors = PDOPSTATUS.PDOPS_ERRORS, + } + + /// Flags used in Mode + [Flags] + public enum OperationMode + { + /// Use the default progress dialog operations mode. + Default = PDMODE.PDM_DEFAULT, + + /// The operation is running. + Running = PDMODE.PDM_RUN, + + /// The operation is gathering data before it begins, such as calculating the predicted operation time. + Starting = PDMODE.PDM_PREFLIGHT, + + /// The operation is rolling back due to an Undo command from the user. + Undoing = PDMODE.PDM_UNDOING, + + /// Error dialogs are blocking progress from continuing. + BlockedByErrors = PDMODE.PDM_ERRORSBLOCKING, + + /// The length of the operation is indeterminate. Do not show a timer and display the progress bar in marquee mode. + Indeterminate = PDMODE.PDM_INDETERMINATE, + } + + /// Describes an action being performed that requires progress to be shown to the user using progress dialog. + public enum OperationType + { + /// No action is being performed. + None = SPACTION.SPACTION_NONE, + + /// Files are being moved. + Moving = SPACTION.SPACTION_MOVING, + + /// Files are being copied. + Copying = SPACTION.SPACTION_COPYING, + + /// Files are being deleted. + Recycling = SPACTION.SPACTION_RECYCLING, + + /// A set of attributes are being applied to files. + ApplyingAttributes = SPACTION.SPACTION_APPLYINGATTRIBS, + + /// A file is being downloaded from a remote source. + Downloading = SPACTION.SPACTION_DOWNLOADING, + + /// An Internet search is being performed. + SearchingInternet = SPACTION.SPACTION_SEARCHING_INTERNET, + + /// A calculation is being performed. + Calculating = SPACTION.SPACTION_CALCULATING, + + /// A file is being uploaded to a remote source. + Uploading = SPACTION.SPACTION_UPLOADING, + + /// A local search is being performed. + SearchingFiles = SPACTION.SPACTION_SEARCHING_FILES, + + /// Windows Vista and later. A deletion is being performed. + Deleting = SPACTION.SPACTION_DELETING, + + /// Windows Vista and later. A renaming action is being performed. + Renaming = SPACTION.SPACTION_RENAMING, + + /// Windows Vista and later. A formatting action is being performed. + Formatting = SPACTION.SPACTION_FORMATTING, + + /// Windows 7 and later. A copy or move action is being performed. + CopyMoving = SPACTION.SPACTION_COPY_MOVING, + } + + /// The operation can be undone in the dialog. (The Stop button becomes Undo) + [DefaultValue(false)] + public bool AllowUndo { get; set; } + + /// + /// A ShellItem that represents the item currently being operated on by the operation engine. This property is only used in Windows + /// 7 and later. In earlier versions, this property should be + /// + public ShellItem CurrentItem { get => currentItem; set { currentItem = value; UpdateLocations(); } } + + /// A ShellItem that represents the target Shell item. + public ShellItem Destination { get => destItem; set { destItem = value ?? throw new ArgumentNullException(nameof(Source)); UpdateLocations(); } } + + /// Gets the elapsed time. + /// The elapsed time, accurate to milliseconds. + public TimeSpan ElapsedTime + { + get + { + ulong t = 0; + if (CanProcess) + try { iProgressDialog.GetMilliseconds(out t, out _); } catch { } + return TimeSpan.FromMilliseconds(t); + } + } + + /// Don't display the path of destination file in progress dialog + [DefaultValue(false)] + public bool HideDestinationPath { get; set; } + + /// Don't display the location line in the progress dialog + [DefaultValue(false)] + public bool HideLocations { get; set; } + + /// Don't display the path of source file in progress dialog + [DefaultValue(false)] + public bool HideSourcePath { get; set; } + + /// Gets or sets progress dialog operations mode. + /// The mode. + public OperationMode Mode + { + get => mode; + set + { + if (mode == value) return; + mode = value; + if (CanProcess) + iProgressDialog.SetMode((PDMODE)mode); + } + } + + /// Sets which progress dialog operation is occurring, and whether we are in pre-flight or undo mode. + /// Specifies operation. See . + public OperationType Operation + { + get => operation; + set + { + if (operation == value) return; + operation = value; + if (CanProcess) + iProgressDialog.SetOperation((SPACTION)operation); + } + } + + /// Total points, used for showing progress in points. + [DefaultValue(100)] + public long ProgressBarMaxValue + { + get => totalProgress; + set { totalProgress = value; UpdateProgress(); } + } + + /// Current points, used for showing progress in points. + [DefaultValue(0)] + public long ProgressBarValue + { + get => currentProgress; + set { currentProgress = value; UpdateProgress(); } + } + + /// Specifies total items, used for showing progress in items. + [DefaultValue(100)] + public long ProgressDialogItemsMaxValue + { + get => totalItems; + set { totalItems = value; UpdateProgress(); } + } + + /// Current items, used for showing progress in items. + [DefaultValue(0)] + public long ProgressDialogItemsValue + { + get => currentItems; + set { currentItems = value; UpdateProgress(); } + } + + /// Total size in bytes, used for showing progress in bytes. + [DefaultValue(100)] + public long ProgressDialogSizeMaxValue + { + get => totalSize; + set { totalSize = value; UpdateProgress(); } + } + + /// Current size in bytes, used for showing progress in bytes. + [DefaultValue(0)] + public long ProgressDialogSizeValue + { + get => currentSize; + set { currentSize = value; UpdateProgress(); } + } + + /// Gets the remaining time. + /// The remaining time, accurate to milliseconds. + public TimeSpan RemainingTime + { + get + { + ulong t = 0; + if (CanProcess) + try { iProgressDialog.GetMilliseconds(out _, out t); } catch { } + return TimeSpan.FromMilliseconds(t); + } + } + + /// Add a pause button (operation can be paused) + [DefaultValue(false)] + public bool ShowPauseButton { get; set; } + + /// A ShellItem that represents the source Shell item. + public ShellItem Source { get => sourceItem; set { sourceItem = value ?? throw new ArgumentNullException(nameof(Source)); UpdateLocations(); } } + + /// Gets operation status for progress dialog. + /// The operation status. See . + public DialogStatus Status => (DialogStatus)(CanProcess ? iProgressDialog.GetOperationStatus() : 0); + + private bool CanProcess => iProgressDialog != null; + + private OPPROGDLGF DialogFlags => (ShowPauseButton ? OPPROGDLGF.OPPROGDLG_ENABLEPAUSE : 0) | + (AllowUndo ? OPPROGDLGF.OPPROGDLG_ALLOWUNDO : 0) | + (HideSourcePath ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYSOURCEPATH : 0) | + (HideDestinationPath ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYDESTPATH : 0) | + (HideLocations ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYLOCATIONS : 0); + + /// Pauses progress dialog timer. + public void PauseTimer() { if (CanProcess) iProgressDialog.PauseTimer(); } + + /// Resets progress dialog timer to 0. + public void ResetTimer() { if (CanProcess) iProgressDialog.ResetTimer(); } + + /// Resumes progress dialog timer. + public void ResumeTimer() { if (CanProcess) iProgressDialog.ResumeTimer(); } + + /// Starts the specified progress dialog. + /// + /// A value that represents the window of the owner window for the common dialog box. This value can be . + /// + public void Start(HWND owner = default) + { + iProgressDialog = new IOperationsProgressDialog(); + iProgressDialog.StartProgressDialog(owner, DialogFlags); + iProgressDialog.SetOperation((SPACTION)operation); + iProgressDialog.SetMode((PDMODE)mode); + UpdateLocations(); + UpdateProgress(); + } + + /// Stops current progress dialog. + public void Stop() + { + if (!CanProcess) + return; + + iProgressDialog.StopProgressDialog(); + Thread.Sleep(500); + iProgressDialog = null; + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + Stop(); + base.Dispose(disposing); + } + + private void UpdateLocations() + { + if (CanProcess) + iProgressDialog.UpdateLocations(sourceItem.IShellItem, destItem.IShellItem, currentItem?.IShellItem); + } + + private void UpdateProgress() + { + if (CanProcess) + iProgressDialog.UpdateProgress((ulong)currentProgress, (ulong)totalProgress, (ulong)currentSize, (ulong)totalSize, (ulong)currentItems, (ulong)totalItems); + } + } + + public delegate void ProgressChangedEventHandler(object? sender, ProgressChangedEventArgs e); + + public class ProgressChangedEventArgs : EventArgs + { + private readonly double _progressPercentage; + private readonly object? _userState; + + public ProgressChangedEventArgs(double progressPercentage, object? userState) + { + _progressPercentage = progressPercentage; + _userState = userState; + } + + public double ProgressPercentage + { + get + { + return _progressPercentage; + } + } + + public object? UserState + { + get + { + return _userState; + } + } + } +} diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index 15e031d83dbf..74e2678a72a7 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -173,17 +173,18 @@ TotalSize is not 0 || // NOTE: This won't work yet ProcessingItemsCountSpeed = (ProcessedItemsCount - _previousProcessedItemsCount) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; - } - PropertyChanged?.Invoke(this, new(nameof(ProcessingSizeSpeed))); - PropertyChanged?.Invoke(this, new(nameof(ProcessingItemsCountSpeed))); + PropertyChanged?.Invoke(this, new(nameof(ProcessingSizeSpeed))); + PropertyChanged?.Invoke(this, new(nameof(ProcessingItemsCountSpeed))); + + Percentage = percentage; - Percentage = percentage; + _previousReportTime = DateTimeOffset.Now; + _previousProcessedSize = ProcessedSize; + _previousProcessedItemsCount = ProcessedItemsCount; + } _progress?.Report(this); - _previousReportTime = DateTimeOffset.Now; - _previousProcessedSize = ProcessedSize; - _previousProcessedItemsCount = ProcessedItemsCount; } } diff --git a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs b/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs index c2c74ad0e26f..69f2f2fb87ec 100644 --- a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs @@ -42,7 +42,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera { return Win32API.StartSTATask(async () => { - using var op = new ShellFileOperations(); + using var op = new ShellFileOperations2(); op.Options = ShellFileOperations.OperationFlags.Silent | ShellFileOperations.OperationFlags.NoConfirmMkDir @@ -111,7 +111,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera { return Win32API.StartSTATask(async () => { - using var op = new ShellFileOperations(); + using var op = new ShellFileOperations2(); op.Options = ShellFileOperations.OperationFlags.Silent | ShellFileOperations.OperationFlags.NoConfirmation @@ -200,7 +200,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; long totalSize = 0; - foreach (var item in fileToCopyPath) + foreach (var item in fileToDeletePath) { totalSize += GetFileSize(item); } @@ -209,7 +209,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera progress, true, FileSystemStatusCode.InProgress, - fileToCopyPath.Count(), + fileToDeletePath.Count(), totalSize); fsProgress.Report(); @@ -217,7 +217,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera return Win32API.StartSTATask(async () => { - using var op = new ShellFileOperations(); + using var op = new ShellFileOperations2(); op.Options = ShellFileOperations.OperationFlags.Silent | ShellFileOperations.OperationFlags.NoConfirmation | ShellFileOperations.OperationFlags.NoErrorUI; @@ -308,7 +308,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera return Win32API.StartSTATask(async () => { - using var op = new ShellFileOperations(); + using var op = new ShellFileOperations2(); var shellOperationResult = new ShellOperationResult(); op.Options = ShellFileOperations.OperationFlags.Silent @@ -372,7 +372,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; long totalSize = 0; - foreach (var item in fileToCopyPath) + foreach (var item in fileToMovePath) { totalSize += GetFileSize(item); } @@ -381,7 +381,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera progress, true, FileSystemStatusCode.InProgress, - fileToCopyPath.Count(), + fileToMovePath.Count(), totalSize); fsProgress.Report(); @@ -389,7 +389,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera return Win32API.StartSTATask(async () => { - using var op = new ShellFileOperations(); + using var op = new ShellFileOperations2(); var shellOperationResult = new ShellOperationResult(); op.Options = ShellFileOperations.OperationFlags.NoConfirmMkDir @@ -486,7 +486,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera return Win32API.StartSTATask(async () => { - using var op = new ShellFileOperations(); + using var op = new ShellFileOperations2(); var shellOperationResult = new ShellOperationResult(); @@ -773,7 +773,7 @@ public static bool SetCompatOptions(string filePath, string options) return null; } - private static void UpdateFileTagsDb(ShellFileOperations.ShellFileOpEventArgs e, string operationType) + private static void UpdateFileTagsDb(ShellFileOperations2.ShellFileOpEventArgs e, string operationType) { var dbInstance = FileTagsHelper.GetDbInstance(); if (e.Result.Succeeded) @@ -875,7 +875,7 @@ private class ProgressHandler : Disposable private class OperationWithProgress { - public int Progress { get; set; } + public double Progress { get; set; } public bool Canceled { get; set; } } @@ -917,7 +917,7 @@ public void RemoveOperation(string uid) } } - public void UpdateOperation(string uid, int progress) + public void UpdateOperation(string uid, double progress) { if (operations.TryGetValue(uid, out var op)) { From 5a3f7a40d96eb6587fdfe1d6fd28fca0e7e18596 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 17 Sep 2023 04:02:27 +0200 Subject: [PATCH 14/97] Remove extra class --- .../Utils/Shell/ShellFileOperations2.cs | 333 ------------------ 1 file changed, 333 deletions(-) diff --git a/src/Files.App/Utils/Shell/ShellFileOperations2.cs b/src/Files.App/Utils/Shell/ShellFileOperations2.cs index 058bf936931d..ef36ddafe494 100644 --- a/src/Files.App/Utils/Shell/ShellFileOperations2.cs +++ b/src/Files.App/Utils/Shell/ShellFileOperations2.cs @@ -11,7 +11,6 @@ namespace Vanara.Windows.Shell; public class ShellFileOperations2 : IDisposable { private const OperationFlags defaultOptions = OperationFlags.AllowUndo | OperationFlags.NoConfirmMkDir; - private ShellFileOperationDialog customProgressDialog; private bool disposedValue = false; private IFileOperation op; private OperationFlags opFlags = defaultOptions; @@ -94,14 +93,6 @@ public ShellFileOperations2(IFileOperation operation) /// if any file operations were aborted before they were complete; otherwise, . public bool AnyOperationsAborted => op.GetAnyOperationsAborted(); - /// Specifies a dialog box used to display the progress of the operation. - /// A ShellFileOperationDialog object that represents the dialog box. - public ShellFileOperationDialog CustomProgressDialog - { - get => customProgressDialog; - set => op.SetProgressDialog((customProgressDialog = value)?.iProgressDialog); - } - /// Gets or sets options that control file operations. public OperationFlags Options { @@ -719,330 +710,6 @@ internal ShellFileOpEventArgs(TRANSFER_SOURCE_FLAGS flags, IShellItem source, IS public override string ToString() => $"HR:{Result};Src:{SourceItem};DFld:{DestFolder};Dst:{DestItem};Name:{Name}"; } - /// The Shell's progress dialog. - /// - public class ShellFileOperationDialog : Component - { - internal IOperationsProgressDialog iProgressDialog; - - private ShellItem currentItem; - private long currentItems; - private long currentProgress; - private long currentSize; - private ShellItem destItem; - private OperationMode mode; - private OperationType operation; - private ShellItem sourceItem; - private long totalItems = 100; - private long totalProgress = 100; - private long totalSize = 100; - - /// Initializes a new instance of the class. - public ShellFileOperationDialog() - { - } - - /// Initializes a new instance of the class. - /// The container. - public ShellFileOperationDialog(IContainer container) : this() - { - container.Add(this); - } - - /// Provides operation status flags for ShellFileOperationDialog. - public enum DialogStatus - { - /// The dialog has not been started. - NotStarted = 0, - - /// Operation is running, no user intervention. - Running = PDOPSTATUS.PDOPS_RUNNING, - - /// Operation has been paused by the user. - Paused = PDOPSTATUS.PDOPS_PAUSED, - - /// Operation has been canceled by the user - now go undo. - Cancelled = PDOPSTATUS.PDOPS_CANCELLED, - - /// Operation has been stopped by the user - terminate completely. - Stopped = PDOPSTATUS.PDOPS_STOPPED, - - /// Operation has gone as far as it can go without throwing error dialogs. - Errors = PDOPSTATUS.PDOPS_ERRORS, - } - - /// Flags used in Mode - [Flags] - public enum OperationMode - { - /// Use the default progress dialog operations mode. - Default = PDMODE.PDM_DEFAULT, - - /// The operation is running. - Running = PDMODE.PDM_RUN, - - /// The operation is gathering data before it begins, such as calculating the predicted operation time. - Starting = PDMODE.PDM_PREFLIGHT, - - /// The operation is rolling back due to an Undo command from the user. - Undoing = PDMODE.PDM_UNDOING, - - /// Error dialogs are blocking progress from continuing. - BlockedByErrors = PDMODE.PDM_ERRORSBLOCKING, - - /// The length of the operation is indeterminate. Do not show a timer and display the progress bar in marquee mode. - Indeterminate = PDMODE.PDM_INDETERMINATE, - } - - /// Describes an action being performed that requires progress to be shown to the user using progress dialog. - public enum OperationType - { - /// No action is being performed. - None = SPACTION.SPACTION_NONE, - - /// Files are being moved. - Moving = SPACTION.SPACTION_MOVING, - - /// Files are being copied. - Copying = SPACTION.SPACTION_COPYING, - - /// Files are being deleted. - Recycling = SPACTION.SPACTION_RECYCLING, - - /// A set of attributes are being applied to files. - ApplyingAttributes = SPACTION.SPACTION_APPLYINGATTRIBS, - - /// A file is being downloaded from a remote source. - Downloading = SPACTION.SPACTION_DOWNLOADING, - - /// An Internet search is being performed. - SearchingInternet = SPACTION.SPACTION_SEARCHING_INTERNET, - - /// A calculation is being performed. - Calculating = SPACTION.SPACTION_CALCULATING, - - /// A file is being uploaded to a remote source. - Uploading = SPACTION.SPACTION_UPLOADING, - - /// A local search is being performed. - SearchingFiles = SPACTION.SPACTION_SEARCHING_FILES, - - /// Windows Vista and later. A deletion is being performed. - Deleting = SPACTION.SPACTION_DELETING, - - /// Windows Vista and later. A renaming action is being performed. - Renaming = SPACTION.SPACTION_RENAMING, - - /// Windows Vista and later. A formatting action is being performed. - Formatting = SPACTION.SPACTION_FORMATTING, - - /// Windows 7 and later. A copy or move action is being performed. - CopyMoving = SPACTION.SPACTION_COPY_MOVING, - } - - /// The operation can be undone in the dialog. (The Stop button becomes Undo) - [DefaultValue(false)] - public bool AllowUndo { get; set; } - - /// - /// A ShellItem that represents the item currently being operated on by the operation engine. This property is only used in Windows - /// 7 and later. In earlier versions, this property should be - /// - public ShellItem CurrentItem { get => currentItem; set { currentItem = value; UpdateLocations(); } } - - /// A ShellItem that represents the target Shell item. - public ShellItem Destination { get => destItem; set { destItem = value ?? throw new ArgumentNullException(nameof(Source)); UpdateLocations(); } } - - /// Gets the elapsed time. - /// The elapsed time, accurate to milliseconds. - public TimeSpan ElapsedTime - { - get - { - ulong t = 0; - if (CanProcess) - try { iProgressDialog.GetMilliseconds(out t, out _); } catch { } - return TimeSpan.FromMilliseconds(t); - } - } - - /// Don't display the path of destination file in progress dialog - [DefaultValue(false)] - public bool HideDestinationPath { get; set; } - - /// Don't display the location line in the progress dialog - [DefaultValue(false)] - public bool HideLocations { get; set; } - - /// Don't display the path of source file in progress dialog - [DefaultValue(false)] - public bool HideSourcePath { get; set; } - - /// Gets or sets progress dialog operations mode. - /// The mode. - public OperationMode Mode - { - get => mode; - set - { - if (mode == value) return; - mode = value; - if (CanProcess) - iProgressDialog.SetMode((PDMODE)mode); - } - } - - /// Sets which progress dialog operation is occurring, and whether we are in pre-flight or undo mode. - /// Specifies operation. See . - public OperationType Operation - { - get => operation; - set - { - if (operation == value) return; - operation = value; - if (CanProcess) - iProgressDialog.SetOperation((SPACTION)operation); - } - } - - /// Total points, used for showing progress in points. - [DefaultValue(100)] - public long ProgressBarMaxValue - { - get => totalProgress; - set { totalProgress = value; UpdateProgress(); } - } - - /// Current points, used for showing progress in points. - [DefaultValue(0)] - public long ProgressBarValue - { - get => currentProgress; - set { currentProgress = value; UpdateProgress(); } - } - - /// Specifies total items, used for showing progress in items. - [DefaultValue(100)] - public long ProgressDialogItemsMaxValue - { - get => totalItems; - set { totalItems = value; UpdateProgress(); } - } - - /// Current items, used for showing progress in items. - [DefaultValue(0)] - public long ProgressDialogItemsValue - { - get => currentItems; - set { currentItems = value; UpdateProgress(); } - } - - /// Total size in bytes, used for showing progress in bytes. - [DefaultValue(100)] - public long ProgressDialogSizeMaxValue - { - get => totalSize; - set { totalSize = value; UpdateProgress(); } - } - - /// Current size in bytes, used for showing progress in bytes. - [DefaultValue(0)] - public long ProgressDialogSizeValue - { - get => currentSize; - set { currentSize = value; UpdateProgress(); } - } - - /// Gets the remaining time. - /// The remaining time, accurate to milliseconds. - public TimeSpan RemainingTime - { - get - { - ulong t = 0; - if (CanProcess) - try { iProgressDialog.GetMilliseconds(out _, out t); } catch { } - return TimeSpan.FromMilliseconds(t); - } - } - - /// Add a pause button (operation can be paused) - [DefaultValue(false)] - public bool ShowPauseButton { get; set; } - - /// A ShellItem that represents the source Shell item. - public ShellItem Source { get => sourceItem; set { sourceItem = value ?? throw new ArgumentNullException(nameof(Source)); UpdateLocations(); } } - - /// Gets operation status for progress dialog. - /// The operation status. See . - public DialogStatus Status => (DialogStatus)(CanProcess ? iProgressDialog.GetOperationStatus() : 0); - - private bool CanProcess => iProgressDialog != null; - - private OPPROGDLGF DialogFlags => (ShowPauseButton ? OPPROGDLGF.OPPROGDLG_ENABLEPAUSE : 0) | - (AllowUndo ? OPPROGDLGF.OPPROGDLG_ALLOWUNDO : 0) | - (HideSourcePath ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYSOURCEPATH : 0) | - (HideDestinationPath ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYDESTPATH : 0) | - (HideLocations ? OPPROGDLGF.OPPROGDLG_DONTDISPLAYLOCATIONS : 0); - - /// Pauses progress dialog timer. - public void PauseTimer() { if (CanProcess) iProgressDialog.PauseTimer(); } - - /// Resets progress dialog timer to 0. - public void ResetTimer() { if (CanProcess) iProgressDialog.ResetTimer(); } - - /// Resumes progress dialog timer. - public void ResumeTimer() { if (CanProcess) iProgressDialog.ResumeTimer(); } - - /// Starts the specified progress dialog. - /// - /// A value that represents the window of the owner window for the common dialog box. This value can be . - /// - public void Start(HWND owner = default) - { - iProgressDialog = new IOperationsProgressDialog(); - iProgressDialog.StartProgressDialog(owner, DialogFlags); - iProgressDialog.SetOperation((SPACTION)operation); - iProgressDialog.SetMode((PDMODE)mode); - UpdateLocations(); - UpdateProgress(); - } - - /// Stops current progress dialog. - public void Stop() - { - if (!CanProcess) - return; - - iProgressDialog.StopProgressDialog(); - Thread.Sleep(500); - iProgressDialog = null; - } - - /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - Stop(); - base.Dispose(disposing); - } - - private void UpdateLocations() - { - if (CanProcess) - iProgressDialog.UpdateLocations(sourceItem.IShellItem, destItem.IShellItem, currentItem?.IShellItem); - } - - private void UpdateProgress() - { - if (CanProcess) - iProgressDialog.UpdateProgress((ulong)currentProgress, (ulong)totalProgress, (ulong)currentSize, (ulong)totalSize, (ulong)currentItems, (ulong)totalItems); - } - } - public delegate void ProgressChangedEventHandler(object? sender, ProgressChangedEventArgs e); public class ProgressChangedEventArgs : EventArgs From f43af45dcb1da5a3c76e924fb2152f2d655cdee0 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sun, 17 Sep 2023 22:48:47 +0900 Subject: [PATCH 15/97] Fixed build issues --- .../StatusCenterStyles.xaml | 8 +- .../StatusCenterItemProgressModel.cs | 5 + .../FileOperationsHelpers.cs | 125 +++++++++++------- .../Operations/ShellFilesystemOperations.cs | 27 +--- 4 files changed, 86 insertions(+), 79 deletions(-) rename src/Files.App/Utils/Storage/{Helpers => Operations}/FileOperationsHelpers.cs (92%) diff --git a/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml b/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml index 4dd35bac125a..4c975d9a5886 100644 --- a/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml +++ b/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml @@ -7,7 +7,7 @@ - + @@ -15,7 +15,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -43,7 +43,7 @@ - + diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index 15e031d83dbf..1883589307f4 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -9,6 +9,11 @@ namespace Files.App.Utils.StatusCenter /// /// Represents a model for file system operation progress. /// + /// + /// Every instance that have the same instance will update the same progress. + ///
+ /// Therefore, the storage operation classes can portably instance this class and update progress from everywhere with the same instance. + ///
public class StatusCenterItemProgressModel : INotifyPropertyChanged { private readonly IProgress? _progress; diff --git a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs b/src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.cs similarity index 92% rename from src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs rename to src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.cs index c2c74ad0e26f..f36975cb1154 100644 --- a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs +++ b/src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.cs @@ -199,17 +199,13 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera { operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; - long totalSize = 0; - foreach (var item in fileToCopyPath) - { - totalSize += GetFileSize(item); - } + long totalSize = fileToDeletePath.Select(GetFileSize).Sum(); StatusCenterItemProgressModel fsProgress = new( progress, true, FileSystemStatusCode.InProgress, - fileToCopyPath.Count(), + fileToDeletePath.Count(), totalSize); fsProgress.Report(); @@ -218,19 +214,26 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera return Win32API.StartSTATask(async () => { using var op = new ShellFileOperations(); - op.Options = ShellFileOperations.OperationFlags.Silent - | ShellFileOperations.OperationFlags.NoConfirmation - | ShellFileOperations.OperationFlags.NoErrorUI; + + op.Options = + ShellFileOperations.OperationFlags.Silent | + ShellFileOperations.OperationFlags.NoConfirmation | + ShellFileOperations.OperationFlags.NoErrorUI; + if (asAdmin) { - op.Options |= ShellFileOperations.OperationFlags.ShowElevationPrompt - | ShellFileOperations.OperationFlags.RequireElevation; + op.Options |= + ShellFileOperations.OperationFlags.ShowElevationPrompt | + ShellFileOperations.OperationFlags.RequireElevation; } + op.OwnerWindow = (IntPtr)ownerHwnd; + if (!permanently) { - op.Options |= ShellFileOperations.OperationFlags.RecycleOnDelete - | ShellFileOperations.OperationFlags.WantNukeWarning; + op.Options |= + ShellFileOperations.OperationFlags.RecycleOnDelete | + ShellFileOperations.OperationFlags.WantNukeWarning; } var shellOperationResult = new ShellOperationResult(); @@ -240,6 +243,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera if (!SafetyExtensions.IgnoreExceptions(() => { using var shi = new ShellItem(fileToDeletePath[i]); + op.QueueDeleteOperation(shi); })) { @@ -256,13 +260,16 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera progressHandler.AddOperation(operationID); var deleteTcs = new TaskCompletionSource(); + + // Right before deleting item op.PreDeleteItem += (s, e) => { + // E_FAIL, stops operation if (!permanently && !e.Flags.HasFlag(ShellFileOperations.TransferFlags.DeleteRecycleIfPossible)) - { - throw new Win32Exception(HRESULT.COPYENGINE_E_RECYCLE_BIN_NOT_FOUND); // E_FAIL, stops operation - } + throw new Win32Exception(HRESULT.COPYENGINE_E_RECYCLE_BIN_NOT_FOUND); }; + + // Right after deleted item op.PostDeleteItem += (s, e) => { shellOperationResult.Items.Add(new ShellOperationItemResult() @@ -272,15 +279,19 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera Destination = e.DestItem.GetParsingPath(), HResult = (int)e.Result }); + + UpdateFileTagsDb(e, "delete"); }; - op.PostDeleteItem += (_, e) => UpdateFileTagsDb(e, "delete"); - op.FinishOperations += (s, e) => deleteTcs.TrySetResult(e.Result.Succeeded); + + op.FinishOperations += (s, e) + => deleteTcs.TrySetResult(e.Result.Succeeded); + op.UpdateProgress += (s, e) => { + // E_FAIL, stops operation if (progressHandler.CheckCanceled(operationID)) - { - throw new Win32Exception(unchecked((int)0x80004005)); // E_FAIL, stops operation - } + throw new Win32Exception(unchecked((int)0x80004005)); + fsProgress.Report(e.ProgressPercentage); progressHandler.UpdateOperation(operationID, e.ProgressPercentage); }; @@ -371,17 +382,13 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera { operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; - long totalSize = 0; - foreach (var item in fileToCopyPath) - { - totalSize += GetFileSize(item); - } + long totalSize = fileToMovePath.Select(GetFileSize).Sum(); StatusCenterItemProgressModel fsProgress = new( progress, true, FileSystemStatusCode.InProgress, - fileToCopyPath.Count(), + fileToMovePath.Count(), totalSize); fsProgress.Report(); @@ -392,24 +399,32 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera using var op = new ShellFileOperations(); var shellOperationResult = new ShellOperationResult(); - op.Options = ShellFileOperations.OperationFlags.NoConfirmMkDir - | ShellFileOperations.OperationFlags.Silent - | ShellFileOperations.OperationFlags.NoErrorUI; + op.Options = + ShellFileOperations.OperationFlags.NoConfirmMkDir | + ShellFileOperations.OperationFlags.Silent | + ShellFileOperations.OperationFlags.NoErrorUI; + if (asAdmin) { - op.Options |= ShellFileOperations.OperationFlags.ShowElevationPrompt - | ShellFileOperations.OperationFlags.RequireElevation; + op.Options |= + ShellFileOperations.OperationFlags.ShowElevationPrompt | + ShellFileOperations.OperationFlags.RequireElevation; } + op.OwnerWindow = (IntPtr)ownerHwnd; - op.Options |= !overwriteOnMove ? ShellFileOperations.OperationFlags.PreserveFileExtensions | ShellFileOperations.OperationFlags.RenameOnCollision - : ShellFileOperations.OperationFlags.NoConfirmation; + + op.Options |= + !overwriteOnMove + ? ShellFileOperations.OperationFlags.PreserveFileExtensions | ShellFileOperations.OperationFlags.RenameOnCollision + : ShellFileOperations.OperationFlags.NoConfirmation; for (var i = 0; i < fileToMovePath.Length; i++) { if (!SafetyExtensions.IgnoreExceptions(() => { - using ShellItem shi = new ShellItem(fileToMovePath[i]); - using ShellFolder shd = new ShellFolder(Path.GetDirectoryName(moveDestination[i])); + using ShellItem shi = new(fileToMovePath[i]); + using ShellFolder shd = new(Path.GetDirectoryName(moveDestination[i])); + op.QueueMoveOperation(shi, shd, Path.GetFileName(moveDestination[i])); })) { @@ -427,6 +442,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera progressHandler.AddOperation(operationID); var moveTcs = new TaskCompletionSource(); + op.PostMoveItem += (s, e) => { shellOperationResult.Items.Add(new ShellOperationItemResult() @@ -437,14 +453,19 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera HResult = (int)e.Result }); }; - op.PostMoveItem += (_, e) => UpdateFileTagsDb(e, "move"); - op.FinishOperations += (s, e) => moveTcs.TrySetResult(e.Result.Succeeded); + + op.PostMoveItem += (_, e) + => UpdateFileTagsDb(e, "move"); + + op.FinishOperations += (s, e) + => moveTcs.TrySetResult(e.Result.Succeeded); + op.UpdateProgress += (s, e) => { + // E_FAIL, stops operation if (progressHandler.CheckCanceled(operationID)) - { - throw new Win32Exception(unchecked((int)0x80004005)); // E_FAIL, stops operation - } + throw new Win32Exception(unchecked((int)0x80004005)); + fsProgress.Report(e.ProgressPercentage); progressHandler.UpdateOperation(operationID, e.ProgressPercentage); }; @@ -468,11 +489,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera { operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID; - long totalSize = 0; - foreach (var item in fileToCopyPath) - { - totalSize += GetFileSize(item); - } + long totalSize = fileToCopyPath.Select(GetFileSize).Sum(); StatusCenterItemProgressModel fsProgress = new( progress, @@ -503,6 +520,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera } op.OwnerWindow = (IntPtr)ownerHwnd; + op.Options |= !overwriteOnCopy ? ShellFileOperations.OperationFlags.PreserveFileExtensions | ShellFileOperations.OperationFlags.RenameOnCollision @@ -543,14 +561,19 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera HResult = (int)e.Result }); }; - op.PostCopyItem += (_, e) => UpdateFileTagsDb(e, "copy"); - op.FinishOperations += (s, e) => copyTcs.TrySetResult(e.Result.Succeeded); + + op.PostCopyItem += (_, e) + => UpdateFileTagsDb(e, "copy"); + + op.FinishOperations += (s, e) + => copyTcs.TrySetResult(e.Result.Succeeded); + op.UpdateProgress += (s, e) => { + // E_FAIL, stops operation if (progressHandler.CheckCanceled(operationID)) - { - throw new Win32Exception(unchecked((int)0x80004005)); // E_FAIL, stops operation - } + throw new Win32Exception(unchecked((int)0x80004005)); + fsProgress.Report(e.ProgressPercentage); progressHandler.UpdateOperation(operationID, e.ProgressPercentage); }; diff --git a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs index 999e2119034f..1ba84ed19283 100644 --- a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs +++ b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs @@ -44,18 +44,11 @@ public async Task CopyItemsAsync(IList so return await _filesystemOperations.CopyItemsAsync(source, destination, collisions, progress, cancellationToken); } - long totalSize = 0; - foreach (var item in source) - { - totalSize += FileOperationsHelpers.GetFileSize(item.Path); - } - StatusCenterItemProgressModel fsProgress = new( progress, true, FileSystemStatusCode.InProgress, - source.Count(), - totalSize); + source.Count()); fsProgress.Report(); @@ -355,18 +348,11 @@ public async Task DeleteItemsAsync(IList return await _filesystemOperations.DeleteItemsAsync(source, progress, permanently, cancellationToken); } - long totalSize = 0; - foreach (var item in source) - { - totalSize += FileOperationsHelpers.GetFileSize(item.Path); - } - StatusCenterItemProgressModel fsProgress = new( progress, true, FileSystemStatusCode.InProgress, - source.Count(), - totalSize); + source.Count()); fsProgress.Report(); @@ -481,18 +467,11 @@ public async Task MoveItemsAsync(IList so return await _filesystemOperations.MoveItemsAsync(source, destination, collisions, progress, cancellationToken); } - long totalSize = 0; - foreach (var item in source) - { - totalSize += FileOperationsHelpers.GetFileSize(item.Path); - } - StatusCenterItemProgressModel fsProgress = new( progress, true, FileSystemStatusCode.InProgress, - source.Count(), - totalSize); + source.Count()); fsProgress.Report(); From e8322d2dc82b4ae3fab0767adbef328992c1a1de Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sun, 17 Sep 2023 22:57:40 +0900 Subject: [PATCH 16/97] fix --- .../Utils/Archives/CompressArchiveModel.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Files.App/Utils/Archives/CompressArchiveModel.cs b/src/Files.App/Utils/Archives/CompressArchiveModel.cs index 622837c74781..c1f8a9799295 100644 --- a/src/Files.App/Utils/Archives/CompressArchiveModel.cs +++ b/src/Files.App/Utils/Archives/CompressArchiveModel.cs @@ -77,7 +77,19 @@ public IProgress Progress set { _Progress = value; - _fileSystemProgress = new(Progress, true, FileSystemStatusCode.InProgress); + + long totalSize = 0; + + if (Sources is not null) + totalSize = Sources.Select(FileOperationsHelpers.GetFileSize).Sum(); + + _fileSystemProgress = new( + Progress, + true, + FileSystemStatusCode.InProgress, + Sources is null ? 0 : Sources.Count(), + totalSize); + _fileSystemProgress.Report(0); } } @@ -115,19 +127,6 @@ public CompressArchiveModel( ArchiveCompressionLevels compressionLevel = ArchiveCompressionLevels.Normal, ArchiveSplittingSizes splittingSize = ArchiveSplittingSizes.None) { - long totalSize = 0; - foreach (var item in Sources) - { - totalSize += FileOperationsHelpers.GetFileSize(item); - } - - _fileSystemProgress = new( - Progress, - true, - FileSystemStatusCode.InProgress, - Sources.Count(), - totalSize); - _Progress = new Progress(); Sources = source; From b90dd1f440c9af6a26483af26827ef28d928e1c6 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 18 Sep 2023 22:35:09 +0900 Subject: [PATCH 17/97] Update --- .../Decompress/BaseDecompressArchiveAction.cs | 2 +- .../Archives/Decompress/DecompressArchive.cs | 2 +- .../Decompress/DecompressArchiveHere.cs | 2 +- .../DecompressArchiveToChildFolderAction.cs | 2 +- .../MenuFlyout/ContextFlyoutItemHelper.cs | 2 +- src/Files.App/Strings/en-US/Resources.resw | 218 +++++++----- .../Utils/Archives/CompressHelper.cs | 211 ++--------- .../Utils/Archives/DecompressHelper.cs | 168 +++++++++ .../Utils/StatusCenter/StatusCenterHelper.cs | 330 +++++++++++++++--- .../Utils/StatusCenter/StatusCenterItem.cs | 54 ++- 10 files changed, 620 insertions(+), 371 deletions(-) diff --git a/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs b/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs index f5f47979ab74..f7bfd13e9094 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs @@ -16,7 +16,7 @@ public virtual HotKey HotKey public override bool IsExecutable => (IsContextPageTypeAdaptedToCommand() && - CompressHelper.CanDecompress(context.SelectedItems) || + DecompressHelper.CanDecompress(context.SelectedItems) || CanDecompressInsideArchive()) && UIHelpers.CanShowDialog; diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs index c9ed484b265b..089e7aada9f9 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs @@ -23,7 +23,7 @@ public DecompressArchive() public override Task ExecuteAsync() { - return CompressHelper.DecompressArchive(context.ShellPage); + return DecompressHelper.DecompressArchive(context.ShellPage); } protected override bool CanDecompressInsideArchive() diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs index 7e0419bfc613..e41c9d6487c9 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs @@ -17,7 +17,7 @@ public DecompressArchiveHere() public override Task ExecuteAsync() { - return CompressHelper.DecompressArchiveHere(context.ShellPage); + return DecompressHelper.DecompressArchiveHere(context.ShellPage); } } } diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs index 44836ae649d7..dd7359ee9f01 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs @@ -17,7 +17,7 @@ public DecompressArchiveToChildFolderAction() public override Task ExecuteAsync() { - return CompressHelper.DecompressArchiveToChildFolder(context.ShellPage); + return DecompressHelper.DecompressArchiveToChildFolder(context.ShellPage); } protected override void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs b/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs index 643d3cc54726..080bcf5227ff 100644 --- a/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs +++ b/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs @@ -519,7 +519,7 @@ public static List GetBaseItemMenuItems( new ContextMenuFlyoutItemViewModelBuilder(commands.DecompressArchiveHere).Build(), new ContextMenuFlyoutItemViewModelBuilder(commands.DecompressArchiveToChildFolder).Build(), }, - ShowItem = CompressHelper.CanDecompress(selectedItems) + ShowItem = DecompressHelper.CanDecompress(selectedItems) }, new ContextMenuFlyoutItemViewModel() { diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index e6b9d123db9e..5c34cf80041f 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -426,11 +426,11 @@ items - - Deleting files + + Delete in progress - - Moving files to the Recycle Bin + + Recycle in progress Yes @@ -1176,22 +1176,22 @@ Item count - - Deletion Failed + + Delete error - - Deletion complete + + Delete done successfully - - Deletion cancelled + + Canceled deleting - - Recycle complete + + Recycle done successfully - - Move complete + + Move successfully - + Copy complete @@ -1416,101 +1416,95 @@ Privacy - - Move canceled + + Canceled move - - Copy canceled + + Canceled copying - - Moving {0} item from {1} to {2} was canceled + + Move in progress - - Moving {0} items from {1} to {2} was canceled after moving {3} items + + Successfully copied {0} items to {1} - - Moving {0} items from {1} to {2} was canceled after moving {3} item + + Canceled move - - Moving {0} item from {1} to {2} + + Canceled move - - Moving {0} items from {1} to {2} + + Canceled move - - Successfully moved {0} item from {1} to {2} + + Move in progress - - Successfully moved {0} items from {1} to {2} + + Move successfully - - Failed to move {0} item from {1} to {2} + + Move successfully - - Failed to move {0} items from {1} to {2} + + Move error - - Copying {0} item to {1} was canceled + + Move error - - Copying {0} items to {1} was canceled after copying {2} items + + {0} item copied to "{1}" - - Copying {0} items to {1} was canceled after copying {2} item + + {0} / {1} items copied to"{2}" - + Copying {0} item to {1} - + Copying {0} items to {1} - + Successfully copied {0} item to {1} - - Successfully copied {0} items to {1} - - - Deleting {0} item from {1} was canceled - - - Deleting {0} items from {1} was canceled after deleting {3} items + + Canceled deleting - - Deleting {0} items from {1} was canceled after deleting {3} item + + Canceled deleting - - Deleting {0} item from {1} + + Delete in progress - - Deleting {0} items from {1} + + Delete in progress - - Successfully deleted {0} item from {1} + + Delete done successfully - - Successfully deleted {0} items from {1} + + Delete done successfully - - Failed to delete {0} item from {1} + + Delete error - - Failed to delete {0} items from {1} + + Delete error the Recycle Bin - - Moving items + + Move in progress - + Copying items - - Recycle failed + + Recycle error - - Recycle cancelled + + Canceled recycling canceling @@ -1791,11 +1785,11 @@ The archive extraction completed successfully. - - Extracting archive + + Decompress in progress - - Extracting complete! + + Decompress done successfully Open parent folder @@ -2097,23 +2091,23 @@ Update Files - + Preparing {0} items - + Preparing items - - Copy failed + + Copy unsuccessful - - Failed to copy {0} items from {1} to {2} + + {0} items failed to copy to {1} - - Failed to copy {0} item from {1} to {2} + + {0} item failed to copy to {1} - - Move failed + + Move error Tags @@ -2205,17 +2199,17 @@ Opening items - - Compression completed + + Archive "{0}" created - - {0} has been compressed + + Archive "{0}" created - - {0} couldn't be compressed + + Failed to create an archive - - Compressing archive + + Creating archive "{0}" Compress @@ -3526,4 +3520,34 @@ Clear completed + + Canceled creating archive "{0}" + + + Canceled creating archive "{0}" + + + Failed to create an archive + + + Creating archive "{0}" + + + Decompress cancel + + + Decompress cancel + + + Decompress error + + + Decompress error + + + Decompress in progress + + + Decompress done successfully + \ No newline at end of file diff --git a/src/Files.App/Utils/Archives/CompressHelper.cs b/src/Files.App/Utils/Archives/CompressHelper.cs index 554d2ee9b712..5eebdeae3ebd 100644 --- a/src/Files.App/Utils/Archives/CompressHelper.cs +++ b/src/Files.App/Utils/Archives/CompressHelper.cs @@ -1,13 +1,7 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Dialogs; -using Files.App.ViewModels.Dialogs; -using Files.Shared.Helpers; -using Microsoft.UI.Xaml.Controls; using System.IO; -using System.Text; -using Windows.Storage; namespace Files.App.Utils.Archives { @@ -18,16 +12,9 @@ public static class CompressHelper { private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); - public static bool CanDecompress(IReadOnlyList selectedItems) - { - return selectedItems.Any() && - (selectedItems.All(x => x.IsArchive) - || selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.File && FileExtensionHelpers.IsZipFile(x.FileExtension))); - } - public static bool CanCompress(IReadOnlyList selectedItems) { - return !CanDecompress(selectedItems) || selectedItems.Count > 1; + return !DecompressHelper.CanDecompress(selectedItems) || selectedItems.Count > 1; } public static string DetermineArchiveNameFromSelection(IReadOnlyList selectedItems) @@ -67,197 +54,49 @@ public static async Task CompressArchiveAsync(ICompressArchiveModel creator) var archivePath = creator.GetArchivePath(); int index = 1; - while (File.Exists(archivePath) || System.IO.Directory.Exists(archivePath)) + + while (File.Exists(archivePath) || Directory.Exists(archivePath)) archivePath = creator.GetArchivePath($" ({++index})"); + creator.ArchivePath = archivePath; - CancellationTokenSource compressionToken = new(); - StatusCenterItem banner = _statusCenterViewModel.AddItem - ( - "CompressionInProgress".GetLocalizedResource(), - archivePath, - initialProgress: 0, + // Add in-progress status banner + var banner = StatusCenterHelper.PostBanner_Compress( + creator.Sources, + archivePath.CreateEnumerable(), ReturnResult.InProgress, - FileOperationType.Compressed, - compressionToken - ); + false, + 0); creator.Progress = banner.ProgressEventSource; + + // Perform compress operation bool isSuccess = await creator.RunCreationAsync(); + // Remove in-progress status banner _statusCenterViewModel.RemoveItem(banner); if (isSuccess) { - _statusCenterViewModel.AddItem - ( - "CompressionCompleted".GetLocalizedResource(), - string.Format("CompressionSucceded".GetLocalizedResource(), archivePath), - 0, + // Add successful status banner + StatusCenterHelper.PostBanner_Compress( + creator.Sources, + archivePath.CreateEnumerable(), ReturnResult.Success, - FileOperationType.Compressed - ); + false, + creator.Sources.Count()); } else { NativeFileOperationsHelper.DeleteFileFromApp(archivePath); - _statusCenterViewModel.AddItem - ( - "CompressionCompleted".GetLocalizedResource(), - string.Format("CompressionFailed".GetLocalizedResource(), archivePath), - 0, + // Add error status banner + StatusCenterHelper.PostBanner_Compress( + creator.Sources, + archivePath.CreateEnumerable(), ReturnResult.Failed, - FileOperationType.Compressed - ); - } - } - - // TODO: Move all below code to DecompressHelper class - - private static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFolder? destinationFolder, string password) - { - if (archive is null || destinationFolder is null) - return; - - CancellationTokenSource extractCancellation = new(); - - StatusCenterItem banner = _statusCenterViewModel.AddItem( - "ExtractingArchiveText".GetLocalizedResource(), - archive.Path, - 0, - ReturnResult.InProgress, - FileOperationType.Extract, - extractCancellation); - - await FilesystemTasks.Wrap(() => DecompressHelper.ExtractArchive(archive, destinationFolder, password, banner.ProgressEventSource, extractCancellation.Token)); - - _statusCenterViewModel.RemoveItem(banner); - - _statusCenterViewModel.AddItem( - "ExtractingCompleteText".GetLocalizedResource(), - "ArchiveExtractionCompletedSuccessfullyText".GetLocalizedResource(), - 0, - ReturnResult.Success, - FileOperationType.Extract); - } - - public static async Task DecompressArchive(IShellPage associatedInstance) - { - if (associatedInstance == null) - return; - - BaseStorageFile archive = await StorageHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItems.Count != 0 - ? associatedInstance.SlimContentPage.SelectedItem.ItemPath - : associatedInstance.FilesystemViewModel.WorkingDirectory); - - if (archive is null) - return; - - var isArchiveEncrypted = await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive)); - var password = string.Empty; - - DecompressArchiveDialog decompressArchiveDialog = new(); - DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) - { - IsArchiveEncrypted = isArchiveEncrypted, - ShowPathSelection = true - }; - decompressArchiveDialog.ViewModel = decompressArchiveViewModel; - - ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); - if (option != ContentDialogResult.Primary) - return; - - if (isArchiveEncrypted) - password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); - - // Check if archive still exists - if (!StorageHelpers.Exists(archive.Path)) - return; - - BaseStorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder; - string destinationFolderPath = decompressArchiveViewModel.DestinationFolderPath; - - if (destinationFolder is null) - { - BaseStorageFolder parentFolder = await StorageHelpers.ToStorageItem(Path.GetDirectoryName(archive.Path)); - destinationFolder = await FilesystemTasks.Wrap(() => parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName).AsTask()); - } - - await ExtractArchive(archive, destinationFolder, password); - - if (decompressArchiveViewModel.OpenDestinationFolderOnCompletion) - await NavigationHelpers.OpenPath(destinationFolderPath, associatedInstance, FilesystemItemType.Directory); - } - - public static async Task DecompressArchiveHere(IShellPage associatedInstance) - { - if (associatedInstance == null) - return; - - foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems) - { - var password = string.Empty; - BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); - BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); - - if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) - { - DecompressArchiveDialog decompressArchiveDialog = new(); - DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) - { - IsArchiveEncrypted = true, - ShowPathSelection = false - }; - - decompressArchiveDialog.ViewModel = decompressArchiveViewModel; - - ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); - if (option != ContentDialogResult.Primary) - return; - - password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); - } - - await ExtractArchive(archive, currentFolder, password); - } - } - - public static async Task DecompressArchiveToChildFolder(IShellPage associatedInstance) - { - if (associatedInstance == null) - return; - - foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems) - { - var password = string.Empty; - - BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); - BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); - BaseStorageFolder destinationFolder = null; - - if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) - { - DecompressArchiveDialog decompressArchiveDialog = new(); - DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) - { - IsArchiveEncrypted = true, - ShowPathSelection = false - }; - decompressArchiveDialog.ViewModel = decompressArchiveViewModel; - - ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); - if (option != ContentDialogResult.Primary) - return; - - password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); - } - - if (currentFolder is not null) - destinationFolder = await FilesystemTasks.Wrap(() => currentFolder.CreateFolderAsync(Path.GetFileNameWithoutExtension(archive.Path), CreationCollisionOption.GenerateUniqueName).AsTask()); - - await ExtractArchive(archive, destinationFolder, password); + false, + 0); } } } diff --git a/src/Files.App/Utils/Archives/DecompressHelper.cs b/src/Files.App/Utils/Archives/DecompressHelper.cs index a4acf926643d..65b63d498f96 100644 --- a/src/Files.App/Utils/Archives/DecompressHelper.cs +++ b/src/Files.App/Utils/Archives/DecompressHelper.cs @@ -1,14 +1,29 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using Files.App.Dialogs; +using Files.App.ViewModels.Dialogs; +using Files.Shared.Helpers; using Microsoft.Extensions.Logging; +using Microsoft.UI.Xaml.Controls; using SevenZip; using System.IO; +using System.Text; +using Windows.Storage; namespace Files.App.Utils.Archives { public static class DecompressHelper { + private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); + + public static bool CanDecompress(IReadOnlyList selectedItems) + { + return selectedItems.Any() && + (selectedItems.All(x => x.IsArchive) + || selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.File && FileExtensionHelpers.IsZipFile(x.FileExtension))); + } + private static async Task GetZipFile(BaseStorageFile archive, string password = "") { return await FilesystemTasks.Wrap(async () => @@ -137,5 +152,158 @@ public static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFold fsProgress.Report(); } } + + private static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFolder? destinationFolder, string password) + { + if (archive is null || destinationFolder is null) + return; + + // Add in-progress status banner + var banner = StatusCenterHelper.PostBanner_Compress( + archive.Path.CreateEnumerable(), + destinationFolder.Path.CreateEnumerable(), + ReturnResult.InProgress, + false, + 0); + + // Perform decompress operation + await FilesystemTasks.Wrap(() + => ExtractArchive( + archive, + destinationFolder, + password, + banner.ProgressEventSource, + banner.CancellationToken)); + + // Remove in-progress status banner + _statusCenterViewModel.RemoveItem(banner); + + // Add successful status banner + StatusCenterHelper.PostBanner_Compress( + archive.Path.CreateEnumerable(), + destinationFolder.Path.CreateEnumerable(), + ReturnResult.Success, + false, + 1); + } + + public static async Task DecompressArchive(IShellPage associatedInstance) + { + if (associatedInstance == null) + return; + + BaseStorageFile archive = await StorageHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItems.Count != 0 + ? associatedInstance.SlimContentPage.SelectedItem.ItemPath + : associatedInstance.FilesystemViewModel.WorkingDirectory); + + if (archive is null) + return; + + var isArchiveEncrypted = await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive)); + var password = string.Empty; + + DecompressArchiveDialog decompressArchiveDialog = new(); + DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) + { + IsArchiveEncrypted = isArchiveEncrypted, + ShowPathSelection = true + }; + decompressArchiveDialog.ViewModel = decompressArchiveViewModel; + + ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); + if (option != ContentDialogResult.Primary) + return; + + if (isArchiveEncrypted) + password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); + + // Check if archive still exists + if (!StorageHelpers.Exists(archive.Path)) + return; + + BaseStorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder; + string destinationFolderPath = decompressArchiveViewModel.DestinationFolderPath; + + if (destinationFolder is null) + { + BaseStorageFolder parentFolder = await StorageHelpers.ToStorageItem(Path.GetDirectoryName(archive.Path)); + destinationFolder = await FilesystemTasks.Wrap(() => parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName).AsTask()); + } + + await ExtractArchive(archive, destinationFolder, password); + + if (decompressArchiveViewModel.OpenDestinationFolderOnCompletion) + await NavigationHelpers.OpenPath(destinationFolderPath, associatedInstance, FilesystemItemType.Directory); + } + + public static async Task DecompressArchiveHere(IShellPage associatedInstance) + { + if (associatedInstance == null) + return; + + foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems) + { + var password = string.Empty; + BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); + BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); + + if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) + { + DecompressArchiveDialog decompressArchiveDialog = new(); + DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) + { + IsArchiveEncrypted = true, + ShowPathSelection = false + }; + + decompressArchiveDialog.ViewModel = decompressArchiveViewModel; + + ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); + if (option != ContentDialogResult.Primary) + return; + + password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); + } + + await ExtractArchive(archive, currentFolder, password); + } + } + + public static async Task DecompressArchiveToChildFolder(IShellPage associatedInstance) + { + if (associatedInstance == null) + return; + + foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems) + { + var password = string.Empty; + + BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); + BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); + BaseStorageFolder destinationFolder = null; + + if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) + { + DecompressArchiveDialog decompressArchiveDialog = new(); + DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) + { + IsArchiveEncrypted = true, + ShowPathSelection = false + }; + decompressArchiveDialog.ViewModel = decompressArchiveViewModel; + + ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); + if (option != ContentDialogResult.Primary) + return; + + password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); + } + + if (currentFolder is not null) + destinationFolder = await FilesystemTasks.Wrap(() => currentFolder.CreateFolderAsync(Path.GetFileNameWithoutExtension(archive.Path), CreationCollisionOption.GenerateUniqueName).AsTask()); + + await ExtractArchive(archive, destinationFolder, password); + } + } } } diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs index 7090ccb675ac..d94562058a4f 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs @@ -5,7 +5,7 @@ namespace Files.App.Utils.StatusCenter { public static class StatusCenterHelper { - private readonly static StatusCenterViewModel StatusCenterViewModel = Ioc.Default.GetRequiredService(); + private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); public static StatusCenterItem PostBanner_Delete(IEnumerable source, ReturnResult returnStatus, bool permanently, bool canceled, int itemsDeleted) { @@ -15,22 +15,37 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable 1 ? - itemsDeleted > 1 ? "StatusDeleteCanceledDetails_Plural".GetLocalizedResource() : "StatusDeleteCanceledDetails_Plural2".GetLocalizedResource() - : "StatusDeleteCanceledDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, null, itemsDeleted), + // Cancel permanent deletion + return _statusCenterViewModel.AddItem( + "StatusCenter_DeleteCancel_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? itemsDeleted > 1 + ? "StatusCenter_DeleteCancel_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_DeleteCancel_SubHeader_Plural2".GetLocalizedResource() + : "StatusCenter_DeleteCancel_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + null, + itemsDeleted), 0, ReturnResult.Cancelled, FileOperationType.Delete); } else { - return StatusCenterViewModel.AddItem( - "StatusRecycleCancelled".GetLocalizedResource(), - string.Format(source.Count() > 1 ? - itemsDeleted > 1 ? "StatusMoveCanceledDetails_Plural".GetLocalizedResource() : "StatusMoveCanceledDetails_Plural2".GetLocalizedResource() - : "StatusMoveCanceledDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, "TheRecycleBin".GetLocalizedResource(), itemsDeleted), + // Cancel recycling + return _statusCenterViewModel.AddItem( + "StatusCenter_DeleteCancel_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? itemsDeleted > 1 + ? "StatusCenter_MoveCancel_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_MoveCancel_SubHeader_Plural2".GetLocalizedResource() + : "StatusCenter_MoveCancel_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + "TheRecycleBin".GetLocalizedResource(), itemsDeleted), 0, ReturnResult.Cancelled, FileOperationType.Recycle); @@ -40,9 +55,14 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable - return StatusCenterViewModel.AddItem(string.Empty, - string.Format(source.Count() > 1 ? "StatusDeletingItemsDetails_Plural".GetLocalizedResource() : "StatusDeletingItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir), + // Permanent deletion in progress + return _statusCenterViewModel.AddItem(string.Empty, + string.Format( + source.Count() > 1 + ? "StatusCenter_DeleteInProgress_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_DeleteInProgress_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir), 0, ReturnResult.InProgress, FileOperationType.Delete, @@ -50,9 +70,15 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable to recycle bin" - return StatusCenterViewModel.AddItem(string.Empty, - string.Format(source.Count() > 1 ? "StatusMovingItemsDetails_Plural".GetLocalizedResource() : "StatusMovingItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, "TheRecycleBin".GetLocalizedResource()), + // Recycling in progress + return _statusCenterViewModel.AddItem(string.Empty, + string.Format( + source.Count() > 1 + ? "StatusCenter_MoveInProgress_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_MoveInProgress_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + "TheRecycleBin".GetLocalizedResource()), 0, ReturnResult.InProgress, FileOperationType.Recycle, @@ -63,18 +89,32 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable 1 ? "StatusDeletedItemsDetails_Plural".GetLocalizedResource() : "StatusDeletedItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, itemsDeleted), + // Done done permanent deletion successfully + return _statusCenterViewModel.AddItem( + "StatusCenter_DeleteSuccessful_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? "StatusCenter_DeleteSuccessful_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_DeleteSuccessful_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + itemsDeleted), 0, ReturnResult.Success, FileOperationType.Delete); } else { - return StatusCenterViewModel.AddItem( - "StatusRecycleComplete".GetLocalizedResource(), - string.Format(source.Count() > 1 ? "StatusMovedItemsDetails_Plural".GetLocalizedResource() : "StatusMovedItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, "TheRecycleBin".GetLocalizedResource()), + // Done done recycling successfully + return _statusCenterViewModel.AddItem( + "StatusCenter_RecycleSuccessful_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? "StatusCenter_DeleteSuccessful_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_DeleteSuccessful_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + "TheRecycleBin".GetLocalizedResource()), 0, ReturnResult.Success, FileOperationType.Recycle); @@ -84,18 +124,32 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable 1 ? "StatusDeletionFailedDetails_Plural".GetLocalizedResource() : "StatusDeletionFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir), + // Done permanent deletion with error + return _statusCenterViewModel.AddItem( + "StatusCenter_DeleteError_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? "StatusCenter_DeleteError_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_DeleteError_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir + ), 0, ReturnResult.Failed, FileOperationType.Delete); } else { - return StatusCenterViewModel.AddItem( - "StatusRecycleFailed".GetLocalizedResource(), - string.Format(source.Count() > 1 ? "StatusMoveFailedDetails_Plural".GetLocalizedResource() : "StatusMoveFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, "TheRecycleBin".GetLocalizedResource()), + // Done recycling with error + return _statusCenterViewModel.AddItem( + "StatusCenter_RecycleError_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? "StatusCenter_MoveError_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_MoveError_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + "TheRecycleBin".GetLocalizedResource()), 0, ReturnResult.Failed, FileOperationType.Recycle); @@ -110,38 +164,65 @@ public static StatusCenterItem PostBanner_Copy(IEnumerable if (canceled) { - return StatusCenterViewModel.AddItem( - "StatusCopyCanceled".GetLocalizedResource(), - string.Format(source.Count() > 1 ? - itemsCopied > 1 ? "StatusCopyCanceledDetails_Plural".GetLocalizedResource() : "StatusCopyCanceledDetails_Plural2".GetLocalizedResource() : - "StatusCopyCanceledDetails_Singular".GetLocalizedResource(), source.Count(), destinationDir, itemsCopied), + // Cancel + return _statusCenterViewModel.AddItem( + "StatusCenter_CopyCancel_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? itemsCopied > 1 + ? "StatusCenter_CopyCancel_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_CopyCancel_SubHeader_Plural2".GetLocalizedResource() + : "StatusCenter_CopyCancel_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + destinationDir, + itemsCopied), 0, ReturnResult.Cancelled, FileOperationType.Copy); } else if (returnStatus == ReturnResult.InProgress) { - return StatusCenterViewModel.AddItem( + // In progress + return _statusCenterViewModel.AddItem( string.Empty, - string.Format(source.Count() > 1 ? "StatusCopyingItemsDetails_Plural".GetLocalizedResource() : "StatusCopyingItemsDetails_Singular".GetLocalizedResource(), source.Count(), destinationDir), + string.Format( + source.Count() > 1 + ? "StatusCenter_CopyInProgress_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_CopyInProgress_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + destinationDir), 0, ReturnResult.InProgress, FileOperationType.Copy, new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) { - return StatusCenterViewModel.AddItem( - "StatusCopyComplete".GetLocalizedResource(), - string.Format(source.Count() > 1 ? "StatusCopiedItemsDetails_Plural".GetLocalizedResource() : "StatusCopiedItemsDetails_Singular".GetLocalizedResource(), source.Count(), destinationDir, itemsCopied), + // Done successfully + return _statusCenterViewModel.AddItem( + "StatusCopySuccessful".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? "StatusCenter_CopySuccessfulSubHeader_Plural".GetLocalizedResource() + : "StatusCenter_CopySuccessfulSubHeader_Singular".GetLocalizedResource(), + source.Count(), + destinationDir, + itemsCopied), 0, ReturnResult.Success, FileOperationType.Copy); } else { - return StatusCenterViewModel.AddItem( - "StatusCopyFailed".GetLocalizedResource(), - string.Format(source.Count() > 1 ? "StatusCopyFailedDetails_Plural".GetLocalizedResource() : "StatusCopyFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir), + // Done with error + return _statusCenterViewModel.AddItem( + "StatusCenter_CopyError_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? "StatusCenter_CopyError_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_CopyError_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + destinationDir), 0, ReturnResult.Failed, FileOperationType.Copy); @@ -155,42 +236,181 @@ public static StatusCenterItem PostBanner_Move(IEnumerable if (canceled) { - return StatusCenterViewModel.AddItem( - "StatusMoveCanceled".GetLocalizedResource(), - string.Format(source.Count() > 1 ? - itemsMoved > 1 ? "StatusMoveCanceledDetails_Plural".GetLocalizedResource() : "StatusMoveCanceledDetails_Plural2".GetLocalizedResource() - : "StatusMoveCanceledDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir, itemsMoved), + return _statusCenterViewModel.AddItem( + "StatusCenter_MoveCancel_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? itemsMoved > 1 + ? "StatusCenter_MoveCancel_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_MoveCancel_SubHeader_Plural2".GetLocalizedResource() + : "StatusCenter_MoveCancel_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + destinationDir, + itemsMoved), 0, ReturnResult.Cancelled, FileOperationType.Move); } else if (returnStatus == ReturnResult.InProgress) { - return StatusCenterViewModel.AddItem( + return _statusCenterViewModel.AddItem( string.Empty, - string.Format(source.Count() > 1 ? "StatusMovingItemsDetails_Plural".GetLocalizedResource() : "StatusMovingItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir), + string.Format( + source.Count() > 1 + ? "StatusCenter_MoveInProgress_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_MoveInProgress_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, + destinationDir), 0, ReturnResult.InProgress, FileOperationType.Move, new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) { - return StatusCenterViewModel.AddItem( - "StatusMoveComplete".GetLocalizedResource(), - string.Format(source.Count() > 1 ? "StatusMovedItemsDetails_Plural".GetLocalizedResource() : "StatusMovedItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir, itemsMoved), + return _statusCenterViewModel.AddItem( + "StatusCenter_MoveSuccessful_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? "StatusCenter_MoveSuccessful_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_MoveSuccessful_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, destinationDir, itemsMoved), 0, ReturnResult.Success, FileOperationType.Move); } else { - return StatusCenterViewModel.AddItem( - "StatusMoveFailed".GetLocalizedResource(), - string.Format(source.Count() > 1 ? "StatusMoveFailedDetails_Plural".GetLocalizedResource() : "StatusMoveFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir), + return _statusCenterViewModel.AddItem( + "StatusCenter_MoveError_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? "StatusCenter_MoveError_SubHeader_Plural".GetLocalizedResource() + : "StatusCenter_MoveError_SubHeader_Singular".GetLocalizedResource(), + source.Count(), + sourceDir, destinationDir), 0, ReturnResult.Failed, FileOperationType.Move); } } + + public static StatusCenterItem PostBanner_Compress(IEnumerable source, IEnumerable destination, ReturnResult returnStatus, bool canceled, int itemsProcessedSuccessfully) + { + var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault()); + var destinationDir = PathNormalization.GetParentDir(destination.FirstOrDefault()); + + if (canceled) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_CompressCancel_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? itemsProcessedSuccessfully > 1 + ? "StatusCenter_CompressCancel_SubHeader".GetLocalizedResource() + : "StatusCenter_CompressCancel_SubHeader".GetLocalizedResource() + : "StatusCenter_CompressCancel_SubHeader".GetLocalizedResource(), + source.Count(), + sourceDir, + destinationDir, + itemsProcessedSuccessfully), + 0, + ReturnResult.Cancelled, + FileOperationType.Move); + } + else if (returnStatus == ReturnResult.InProgress) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_CompressInProgress_Header".GetLocalizedResource(), + destinationDir, + initialProgress: 0, + ReturnResult.InProgress, + FileOperationType.Compressed, + new CancellationTokenSource()); + } + else if (returnStatus == ReturnResult.Success) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_CompressSuccessful_Header".GetLocalizedResource(), + string.Format( + "StatusCenter_CompressSuccessful_SubHeader".GetLocalizedResource(), + destinationDir), + 0, + ReturnResult.Success, + FileOperationType.Compressed); + } + else + { + return _statusCenterViewModel.AddItem( + "StatusCenter_CompressError_Header".GetLocalizedResource(), + string.Format( + "StatusCenter_CompressError_SubHeader".GetLocalizedResource(), + destinationDir), + 0, + ReturnResult.Failed, + FileOperationType.Compressed + ); + } + } + + public static StatusCenterItem PostBanner_Decompress(IEnumerable source, IEnumerable destination, ReturnResult returnStatus, bool canceled, int itemsProcessedSuccessfully) + { + var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault()); + var destinationDir = PathNormalization.GetParentDir(destination.FirstOrDefault()); + + if (canceled) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_DecompressCancel_Header".GetLocalizedResource(), + string.Format( + source.Count() > 1 + ? itemsProcessedSuccessfully > 1 + ? "StatusCenter_DecompressCancel_SubHeader".GetLocalizedResource() + : "StatusCenter_DecompressCancel_SubHeader".GetLocalizedResource() + : "StatusCenter_DecompressCancel_SubHeader".GetLocalizedResource(), + source.Count(), + sourceDir, + destinationDir, + itemsProcessedSuccessfully), + 0, + ReturnResult.Cancelled, + FileOperationType.Move); + } + else if (returnStatus == ReturnResult.InProgress) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_DecompressInProgress_Header".GetLocalizedResource(), + destinationDir, + initialProgress: 0, + ReturnResult.InProgress, + FileOperationType.Compressed, + new CancellationTokenSource()); + } + else if (returnStatus == ReturnResult.Success) + { + return _statusCenterViewModel.AddItem( + "StatusCenter_DecompressSuccessful_Header".GetLocalizedResource(), + string.Format( + "StatusCenter_DecompressSuccessful_SubHeader".GetLocalizedResource(), + destinationDir), + 0, + ReturnResult.Success, + FileOperationType.Compressed); + } + else + { + return _statusCenterViewModel.AddItem( + "StatusCenter_DecompressError_Header".GetLocalizedResource(), + string.Format( + "StatusCenter_DecompressError_SubHeader".GetLocalizedResource(), + destinationDir), + 0, + ReturnResult.Failed, + FileOperationType.Compressed + ); + } + } } } diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 8bd21ee885a2..03a4f0cad20c 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -105,11 +105,6 @@ public string? SpeedText MaxLimit = 100, ShowSeparatorLines = false, - //SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) - //{ - // StrokeThickness = 0.5F, - // PathEffect = new DashEffect(new float[] { 3, 3 }) - //} } }; @@ -121,11 +116,6 @@ public string? SpeedText Labels = new List(), ShowSeparatorLines = false, - //SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) - //{ - // StrokeThickness = 0.5F, - // PathEffect = new DashEffect(new float[] { 3, 3 }) - //} } }; @@ -153,15 +143,18 @@ public bool IsCancelable public StatusCenterItem(string message, string title, float progress, ReturnResult status, FileOperationType operation, CancellationTokenSource operationCancellationToken = default) { - _operationCancellationToken = operationCancellationToken; - SubHeader = message; - HeaderBody = title; Header = title; + HeaderBody = title; + SubHeader = message; + FileSystemOperationReturnResult = status; Operation = operation; + ProgressEventSource = new Progress(ReportProgress); Progress = new(ProgressEventSource, status: FileSystemStatusCode.InProgress); + _operationCancellationToken = operationCancellationToken; + CancelCommand = new RelayCommand(ExecuteCancelCommand); Values = new(); @@ -203,13 +196,14 @@ public StatusCenterItem(string message, string title, float progress, ReturnResu HeaderBody = Operation switch { - FileOperationType.Extract => "ExtractInProgress/Title".GetLocalizedResource(), - FileOperationType.Copy => "CopyInProgress/Title".GetLocalizedResource(), - FileOperationType.Move => "MoveInProgress".GetLocalizedResource(), - FileOperationType.Delete => "DeleteInProgress/Title".GetLocalizedResource(), - FileOperationType.Recycle => "RecycleInProgress/Title".GetLocalizedResource(), - FileOperationType.Prepare => "PrepareInProgress".GetLocalizedResource(), - _ => "PrepareInProgress".GetLocalizedResource(), + FileOperationType.Compressed => "StatusCenter_CompressInProgress_Header".GetLocalizedResource(), + FileOperationType.Extract => "StatusCenter_DecompressInProgress_Header".GetLocalizedResource(), + FileOperationType.Copy => "StatusCenter_CopyInProgress_Header".GetLocalizedResource(), + FileOperationType.Move => "StatusCenter_MoveInProgress_Header".GetLocalizedResource(), + FileOperationType.Delete => "StatusCenter_DeleteInProgress_Header".GetLocalizedResource(), + FileOperationType.Recycle => "StatusCenter_RecycleInProgress_Header".GetLocalizedResource(), + FileOperationType.Prepare => "StatusCenter_PrepareInProgress_Header_Plural".GetLocalizedResource(), + _ => "StatusCenter_PrepareInProgress_Header_Plural".GetLocalizedResource(), }; Header = $"{HeaderBody} ({progress}%)"; @@ -217,11 +211,12 @@ public StatusCenterItem(string message, string title, float progress, ReturnResu ItemIconKind = Operation switch { - FileOperationType.Extract => StatusCenterItemIconKind.Extract, - FileOperationType.Copy => StatusCenterItemIconKind.Copy, - FileOperationType.Move => StatusCenterItemIconKind.Move, - FileOperationType.Delete => StatusCenterItemIconKind.Delete, - FileOperationType.Recycle => StatusCenterItemIconKind.Recycle, + FileOperationType.Compressed => StatusCenterItemIconKind.Extract, + FileOperationType.Extract => StatusCenterItemIconKind.Extract, + FileOperationType.Copy => StatusCenterItemIconKind.Copy, + FileOperationType.Move => StatusCenterItemIconKind.Move, + FileOperationType.Delete => StatusCenterItemIconKind.Delete, + FileOperationType.Recycle => StatusCenterItemIconKind.Recycle, _ => StatusCenterItemIconKind.Delete, }; @@ -284,21 +279,24 @@ private void ReportProgress(StatusCenterItemProgressModel value) // In progress, displaying items count & processed size case (not 0, not 0): ProgressPercentage = (int)(value.ProcessedSize * 100.0 / value.TotalSize); - Header = $"{HeaderBody} ({value.ProcessedItemsCount} ({value.ProcessedSize.ToSizeString()}) / {value.ItemsCount} ({value.TotalSize.ToSizeString()}): {ProgressPercentage}%)"; + Header = $"{HeaderBody} ({ProgressPercentage:0}%)"; + SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s"; Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); break; // In progress, displaying processed size case (not 0, _): ProgressPercentage = (int)(value.ProcessedSize * 100.0 / value.TotalSize); - Header = $"{HeaderBody} ({value.ProcessedSize.ToSizeString()} / {value.TotalSize.ToSizeString()}: {ProgressPercentage}%)"; + Header = $"{HeaderBody} ({ProgressPercentage:0}%)"; + SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s"; Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); break; // In progress, displaying items count case (_, not 0): ProgressPercentage = (int)(value.ProcessedItemsCount * 100.0 / value.ItemsCount); - Header = $"{HeaderBody} ({value.ProcessedItemsCount} / {value.ItemsCount}: {ProgressPercentage}%)"; + Header = $"{HeaderBody} ({ProgressPercentage:0}%)"; + SpeedText = $"{value.ProcessedItemsCount:0} items/s"; Values.Add(new(value.ProcessedItemsCount * 100.0 / value.ItemsCount, value.ProcessingItemsCountSpeed)); break; From 80dad8a10df81d03eb3f5ac46e4870707eb221ea Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:39:22 +0900 Subject: [PATCH 18/97] Revert "Update" This reverts commit b90dd1f440c9af6a26483af26827ef28d928e1c6. --- .../Decompress/BaseDecompressArchiveAction.cs | 2 +- .../Archives/Decompress/DecompressArchive.cs | 2 +- .../Decompress/DecompressArchiveHere.cs | 2 +- .../DecompressArchiveToChildFolderAction.cs | 2 +- .../MenuFlyout/ContextFlyoutItemHelper.cs | 2 +- src/Files.App/Strings/en-US/Resources.resw | 218 +++++------- .../Utils/Archives/CompressHelper.cs | 211 +++++++++-- .../Utils/Archives/DecompressHelper.cs | 168 --------- .../Utils/StatusCenter/StatusCenterHelper.cs | 330 +++--------------- .../Utils/StatusCenter/StatusCenterItem.cs | 54 +-- 10 files changed, 371 insertions(+), 620 deletions(-) diff --git a/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs b/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs index f7bfd13e9094..f5f47979ab74 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/BaseDecompressArchiveAction.cs @@ -16,7 +16,7 @@ public virtual HotKey HotKey public override bool IsExecutable => (IsContextPageTypeAdaptedToCommand() && - DecompressHelper.CanDecompress(context.SelectedItems) || + CompressHelper.CanDecompress(context.SelectedItems) || CanDecompressInsideArchive()) && UIHelpers.CanShowDialog; diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs index 089e7aada9f9..c9ed484b265b 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchive.cs @@ -23,7 +23,7 @@ public DecompressArchive() public override Task ExecuteAsync() { - return DecompressHelper.DecompressArchive(context.ShellPage); + return CompressHelper.DecompressArchive(context.ShellPage); } protected override bool CanDecompressInsideArchive() diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs index e41c9d6487c9..7e0419bfc613 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveHere.cs @@ -17,7 +17,7 @@ public DecompressArchiveHere() public override Task ExecuteAsync() { - return DecompressHelper.DecompressArchiveHere(context.ShellPage); + return CompressHelper.DecompressArchiveHere(context.ShellPage); } } } diff --git a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs index dd7359ee9f01..44836ae649d7 100644 --- a/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs +++ b/src/Files.App/Actions/Content/Archives/Decompress/DecompressArchiveToChildFolderAction.cs @@ -17,7 +17,7 @@ public DecompressArchiveToChildFolderAction() public override Task ExecuteAsync() { - return DecompressHelper.DecompressArchiveToChildFolder(context.ShellPage); + return CompressHelper.DecompressArchiveToChildFolder(context.ShellPage); } protected override void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs b/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs index 080bcf5227ff..643d3cc54726 100644 --- a/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs +++ b/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs @@ -519,7 +519,7 @@ public static List GetBaseItemMenuItems( new ContextMenuFlyoutItemViewModelBuilder(commands.DecompressArchiveHere).Build(), new ContextMenuFlyoutItemViewModelBuilder(commands.DecompressArchiveToChildFolder).Build(), }, - ShowItem = DecompressHelper.CanDecompress(selectedItems) + ShowItem = CompressHelper.CanDecompress(selectedItems) }, new ContextMenuFlyoutItemViewModel() { diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 5c34cf80041f..e6b9d123db9e 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -426,11 +426,11 @@ items - - Delete in progress + + Deleting files - - Recycle in progress + + Moving files to the Recycle Bin Yes @@ -1176,22 +1176,22 @@ Item count - - Delete error + + Deletion Failed - - Delete done successfully + + Deletion complete - - Canceled deleting + + Deletion cancelled - - Recycle done successfully + + Recycle complete - - Move successfully + + Move complete - + Copy complete @@ -1416,95 +1416,101 @@ Privacy - - Canceled move + + Move canceled - - Canceled copying + + Copy canceled - - Move in progress + + Moving {0} item from {1} to {2} was canceled - - Successfully copied {0} items to {1} + + Moving {0} items from {1} to {2} was canceled after moving {3} items - - Canceled move + + Moving {0} items from {1} to {2} was canceled after moving {3} item - - Canceled move + + Moving {0} item from {1} to {2} - - Canceled move + + Moving {0} items from {1} to {2} - - Move in progress + + Successfully moved {0} item from {1} to {2} - - Move successfully + + Successfully moved {0} items from {1} to {2} - - Move successfully + + Failed to move {0} item from {1} to {2} - - Move error + + Failed to move {0} items from {1} to {2} - - Move error + + Copying {0} item to {1} was canceled - - {0} item copied to "{1}" + + Copying {0} items to {1} was canceled after copying {2} items - - {0} / {1} items copied to"{2}" + + Copying {0} items to {1} was canceled after copying {2} item - + Copying {0} item to {1} - + Copying {0} items to {1} - + Successfully copied {0} item to {1} - - Canceled deleting + + Successfully copied {0} items to {1} + + + Deleting {0} item from {1} was canceled + + + Deleting {0} items from {1} was canceled after deleting {3} items - - Canceled deleting + + Deleting {0} items from {1} was canceled after deleting {3} item - - Delete in progress + + Deleting {0} item from {1} - - Delete in progress + + Deleting {0} items from {1} - - Delete done successfully + + Successfully deleted {0} item from {1} - - Delete done successfully + + Successfully deleted {0} items from {1} - - Delete error + + Failed to delete {0} item from {1} - - Delete error + + Failed to delete {0} items from {1} the Recycle Bin - - Move in progress + + Moving items - + Copying items - - Recycle error + + Recycle failed - - Canceled recycling + + Recycle cancelled canceling @@ -1785,11 +1791,11 @@ The archive extraction completed successfully. - - Decompress in progress + + Extracting archive - - Decompress done successfully + + Extracting complete! Open parent folder @@ -2091,23 +2097,23 @@ Update Files - + Preparing {0} items - + Preparing items - - Copy unsuccessful + + Copy failed - - {0} items failed to copy to {1} + + Failed to copy {0} items from {1} to {2} - - {0} item failed to copy to {1} + + Failed to copy {0} item from {1} to {2} - - Move error + + Move failed Tags @@ -2199,17 +2205,17 @@ Opening items - - Archive "{0}" created + + Compression completed - - Archive "{0}" created + + {0} has been compressed - - Failed to create an archive + + {0} couldn't be compressed - - Creating archive "{0}" + + Compressing archive Compress @@ -3520,34 +3526,4 @@ Clear completed - - Canceled creating archive "{0}" - - - Canceled creating archive "{0}" - - - Failed to create an archive - - - Creating archive "{0}" - - - Decompress cancel - - - Decompress cancel - - - Decompress error - - - Decompress error - - - Decompress in progress - - - Decompress done successfully - \ No newline at end of file diff --git a/src/Files.App/Utils/Archives/CompressHelper.cs b/src/Files.App/Utils/Archives/CompressHelper.cs index 5eebdeae3ebd..554d2ee9b712 100644 --- a/src/Files.App/Utils/Archives/CompressHelper.cs +++ b/src/Files.App/Utils/Archives/CompressHelper.cs @@ -1,7 +1,13 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using Files.App.Dialogs; +using Files.App.ViewModels.Dialogs; +using Files.Shared.Helpers; +using Microsoft.UI.Xaml.Controls; using System.IO; +using System.Text; +using Windows.Storage; namespace Files.App.Utils.Archives { @@ -12,9 +18,16 @@ public static class CompressHelper { private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); + public static bool CanDecompress(IReadOnlyList selectedItems) + { + return selectedItems.Any() && + (selectedItems.All(x => x.IsArchive) + || selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.File && FileExtensionHelpers.IsZipFile(x.FileExtension))); + } + public static bool CanCompress(IReadOnlyList selectedItems) { - return !DecompressHelper.CanDecompress(selectedItems) || selectedItems.Count > 1; + return !CanDecompress(selectedItems) || selectedItems.Count > 1; } public static string DetermineArchiveNameFromSelection(IReadOnlyList selectedItems) @@ -54,49 +67,197 @@ public static async Task CompressArchiveAsync(ICompressArchiveModel creator) var archivePath = creator.GetArchivePath(); int index = 1; - - while (File.Exists(archivePath) || Directory.Exists(archivePath)) + while (File.Exists(archivePath) || System.IO.Directory.Exists(archivePath)) archivePath = creator.GetArchivePath($" ({++index})"); - creator.ArchivePath = archivePath; - // Add in-progress status banner - var banner = StatusCenterHelper.PostBanner_Compress( - creator.Sources, - archivePath.CreateEnumerable(), + CancellationTokenSource compressionToken = new(); + StatusCenterItem banner = _statusCenterViewModel.AddItem + ( + "CompressionInProgress".GetLocalizedResource(), + archivePath, + initialProgress: 0, ReturnResult.InProgress, - false, - 0); + FileOperationType.Compressed, + compressionToken + ); creator.Progress = banner.ProgressEventSource; - - // Perform compress operation bool isSuccess = await creator.RunCreationAsync(); - // Remove in-progress status banner _statusCenterViewModel.RemoveItem(banner); if (isSuccess) { - // Add successful status banner - StatusCenterHelper.PostBanner_Compress( - creator.Sources, - archivePath.CreateEnumerable(), + _statusCenterViewModel.AddItem + ( + "CompressionCompleted".GetLocalizedResource(), + string.Format("CompressionSucceded".GetLocalizedResource(), archivePath), + 0, ReturnResult.Success, - false, - creator.Sources.Count()); + FileOperationType.Compressed + ); } else { NativeFileOperationsHelper.DeleteFileFromApp(archivePath); - // Add error status banner - StatusCenterHelper.PostBanner_Compress( - creator.Sources, - archivePath.CreateEnumerable(), + _statusCenterViewModel.AddItem + ( + "CompressionCompleted".GetLocalizedResource(), + string.Format("CompressionFailed".GetLocalizedResource(), archivePath), + 0, ReturnResult.Failed, - false, - 0); + FileOperationType.Compressed + ); + } + } + + // TODO: Move all below code to DecompressHelper class + + private static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFolder? destinationFolder, string password) + { + if (archive is null || destinationFolder is null) + return; + + CancellationTokenSource extractCancellation = new(); + + StatusCenterItem banner = _statusCenterViewModel.AddItem( + "ExtractingArchiveText".GetLocalizedResource(), + archive.Path, + 0, + ReturnResult.InProgress, + FileOperationType.Extract, + extractCancellation); + + await FilesystemTasks.Wrap(() => DecompressHelper.ExtractArchive(archive, destinationFolder, password, banner.ProgressEventSource, extractCancellation.Token)); + + _statusCenterViewModel.RemoveItem(banner); + + _statusCenterViewModel.AddItem( + "ExtractingCompleteText".GetLocalizedResource(), + "ArchiveExtractionCompletedSuccessfullyText".GetLocalizedResource(), + 0, + ReturnResult.Success, + FileOperationType.Extract); + } + + public static async Task DecompressArchive(IShellPage associatedInstance) + { + if (associatedInstance == null) + return; + + BaseStorageFile archive = await StorageHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItems.Count != 0 + ? associatedInstance.SlimContentPage.SelectedItem.ItemPath + : associatedInstance.FilesystemViewModel.WorkingDirectory); + + if (archive is null) + return; + + var isArchiveEncrypted = await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive)); + var password = string.Empty; + + DecompressArchiveDialog decompressArchiveDialog = new(); + DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) + { + IsArchiveEncrypted = isArchiveEncrypted, + ShowPathSelection = true + }; + decompressArchiveDialog.ViewModel = decompressArchiveViewModel; + + ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); + if (option != ContentDialogResult.Primary) + return; + + if (isArchiveEncrypted) + password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); + + // Check if archive still exists + if (!StorageHelpers.Exists(archive.Path)) + return; + + BaseStorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder; + string destinationFolderPath = decompressArchiveViewModel.DestinationFolderPath; + + if (destinationFolder is null) + { + BaseStorageFolder parentFolder = await StorageHelpers.ToStorageItem(Path.GetDirectoryName(archive.Path)); + destinationFolder = await FilesystemTasks.Wrap(() => parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName).AsTask()); + } + + await ExtractArchive(archive, destinationFolder, password); + + if (decompressArchiveViewModel.OpenDestinationFolderOnCompletion) + await NavigationHelpers.OpenPath(destinationFolderPath, associatedInstance, FilesystemItemType.Directory); + } + + public static async Task DecompressArchiveHere(IShellPage associatedInstance) + { + if (associatedInstance == null) + return; + + foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems) + { + var password = string.Empty; + BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); + BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); + + if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) + { + DecompressArchiveDialog decompressArchiveDialog = new(); + DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) + { + IsArchiveEncrypted = true, + ShowPathSelection = false + }; + + decompressArchiveDialog.ViewModel = decompressArchiveViewModel; + + ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); + if (option != ContentDialogResult.Primary) + return; + + password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); + } + + await ExtractArchive(archive, currentFolder, password); + } + } + + public static async Task DecompressArchiveToChildFolder(IShellPage associatedInstance) + { + if (associatedInstance == null) + return; + + foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems) + { + var password = string.Empty; + + BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); + BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); + BaseStorageFolder destinationFolder = null; + + if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) + { + DecompressArchiveDialog decompressArchiveDialog = new(); + DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) + { + IsArchiveEncrypted = true, + ShowPathSelection = false + }; + decompressArchiveDialog.ViewModel = decompressArchiveViewModel; + + ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); + if (option != ContentDialogResult.Primary) + return; + + password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); + } + + if (currentFolder is not null) + destinationFolder = await FilesystemTasks.Wrap(() => currentFolder.CreateFolderAsync(Path.GetFileNameWithoutExtension(archive.Path), CreationCollisionOption.GenerateUniqueName).AsTask()); + + await ExtractArchive(archive, destinationFolder, password); } } } diff --git a/src/Files.App/Utils/Archives/DecompressHelper.cs b/src/Files.App/Utils/Archives/DecompressHelper.cs index 65b63d498f96..a4acf926643d 100644 --- a/src/Files.App/Utils/Archives/DecompressHelper.cs +++ b/src/Files.App/Utils/Archives/DecompressHelper.cs @@ -1,29 +1,14 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Dialogs; -using Files.App.ViewModels.Dialogs; -using Files.Shared.Helpers; using Microsoft.Extensions.Logging; -using Microsoft.UI.Xaml.Controls; using SevenZip; using System.IO; -using System.Text; -using Windows.Storage; namespace Files.App.Utils.Archives { public static class DecompressHelper { - private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); - - public static bool CanDecompress(IReadOnlyList selectedItems) - { - return selectedItems.Any() && - (selectedItems.All(x => x.IsArchive) - || selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.File && FileExtensionHelpers.IsZipFile(x.FileExtension))); - } - private static async Task GetZipFile(BaseStorageFile archive, string password = "") { return await FilesystemTasks.Wrap(async () => @@ -152,158 +137,5 @@ public static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFold fsProgress.Report(); } } - - private static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFolder? destinationFolder, string password) - { - if (archive is null || destinationFolder is null) - return; - - // Add in-progress status banner - var banner = StatusCenterHelper.PostBanner_Compress( - archive.Path.CreateEnumerable(), - destinationFolder.Path.CreateEnumerable(), - ReturnResult.InProgress, - false, - 0); - - // Perform decompress operation - await FilesystemTasks.Wrap(() - => ExtractArchive( - archive, - destinationFolder, - password, - banner.ProgressEventSource, - banner.CancellationToken)); - - // Remove in-progress status banner - _statusCenterViewModel.RemoveItem(banner); - - // Add successful status banner - StatusCenterHelper.PostBanner_Compress( - archive.Path.CreateEnumerable(), - destinationFolder.Path.CreateEnumerable(), - ReturnResult.Success, - false, - 1); - } - - public static async Task DecompressArchive(IShellPage associatedInstance) - { - if (associatedInstance == null) - return; - - BaseStorageFile archive = await StorageHelpers.ToStorageItem(associatedInstance.SlimContentPage.SelectedItems.Count != 0 - ? associatedInstance.SlimContentPage.SelectedItem.ItemPath - : associatedInstance.FilesystemViewModel.WorkingDirectory); - - if (archive is null) - return; - - var isArchiveEncrypted = await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive)); - var password = string.Empty; - - DecompressArchiveDialog decompressArchiveDialog = new(); - DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) - { - IsArchiveEncrypted = isArchiveEncrypted, - ShowPathSelection = true - }; - decompressArchiveDialog.ViewModel = decompressArchiveViewModel; - - ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); - if (option != ContentDialogResult.Primary) - return; - - if (isArchiveEncrypted) - password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); - - // Check if archive still exists - if (!StorageHelpers.Exists(archive.Path)) - return; - - BaseStorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder; - string destinationFolderPath = decompressArchiveViewModel.DestinationFolderPath; - - if (destinationFolder is null) - { - BaseStorageFolder parentFolder = await StorageHelpers.ToStorageItem(Path.GetDirectoryName(archive.Path)); - destinationFolder = await FilesystemTasks.Wrap(() => parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName).AsTask()); - } - - await ExtractArchive(archive, destinationFolder, password); - - if (decompressArchiveViewModel.OpenDestinationFolderOnCompletion) - await NavigationHelpers.OpenPath(destinationFolderPath, associatedInstance, FilesystemItemType.Directory); - } - - public static async Task DecompressArchiveHere(IShellPage associatedInstance) - { - if (associatedInstance == null) - return; - - foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems) - { - var password = string.Empty; - BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); - BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); - - if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) - { - DecompressArchiveDialog decompressArchiveDialog = new(); - DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) - { - IsArchiveEncrypted = true, - ShowPathSelection = false - }; - - decompressArchiveDialog.ViewModel = decompressArchiveViewModel; - - ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); - if (option != ContentDialogResult.Primary) - return; - - password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); - } - - await ExtractArchive(archive, currentFolder, password); - } - } - - public static async Task DecompressArchiveToChildFolder(IShellPage associatedInstance) - { - if (associatedInstance == null) - return; - - foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems) - { - var password = string.Empty; - - BaseStorageFile archive = await StorageHelpers.ToStorageItem(selectedItem.ItemPath); - BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath); - BaseStorageFolder destinationFolder = null; - - if (await FilesystemTasks.Wrap(() => DecompressHelper.IsArchiveEncrypted(archive))) - { - DecompressArchiveDialog decompressArchiveDialog = new(); - DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive) - { - IsArchiveEncrypted = true, - ShowPathSelection = false - }; - decompressArchiveDialog.ViewModel = decompressArchiveViewModel; - - ContentDialogResult option = await decompressArchiveDialog.TryShowAsync(); - if (option != ContentDialogResult.Primary) - return; - - password = Encoding.UTF8.GetString(decompressArchiveViewModel.Password); - } - - if (currentFolder is not null) - destinationFolder = await FilesystemTasks.Wrap(() => currentFolder.CreateFolderAsync(Path.GetFileNameWithoutExtension(archive.Path), CreationCollisionOption.GenerateUniqueName).AsTask()); - - await ExtractArchive(archive, destinationFolder, password); - } - } } } diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs index d94562058a4f..7090ccb675ac 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs @@ -5,7 +5,7 @@ namespace Files.App.Utils.StatusCenter { public static class StatusCenterHelper { - private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); + private readonly static StatusCenterViewModel StatusCenterViewModel = Ioc.Default.GetRequiredService(); public static StatusCenterItem PostBanner_Delete(IEnumerable source, ReturnResult returnStatus, bool permanently, bool canceled, int itemsDeleted) { @@ -15,37 +15,22 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable 1 - ? itemsDeleted > 1 - ? "StatusCenter_DeleteCancel_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_DeleteCancel_SubHeader_Plural2".GetLocalizedResource() - : "StatusCenter_DeleteCancel_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - null, - itemsDeleted), + return StatusCenterViewModel.AddItem( + "StatusDeletionCancelled".GetLocalizedResource(), + string.Format(source.Count() > 1 ? + itemsDeleted > 1 ? "StatusDeleteCanceledDetails_Plural".GetLocalizedResource() : "StatusDeleteCanceledDetails_Plural2".GetLocalizedResource() + : "StatusDeleteCanceledDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, null, itemsDeleted), 0, ReturnResult.Cancelled, FileOperationType.Delete); } else { - // Cancel recycling - return _statusCenterViewModel.AddItem( - "StatusCenter_DeleteCancel_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? itemsDeleted > 1 - ? "StatusCenter_MoveCancel_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_MoveCancel_SubHeader_Plural2".GetLocalizedResource() - : "StatusCenter_MoveCancel_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - "TheRecycleBin".GetLocalizedResource(), itemsDeleted), + return StatusCenterViewModel.AddItem( + "StatusRecycleCancelled".GetLocalizedResource(), + string.Format(source.Count() > 1 ? + itemsDeleted > 1 ? "StatusMoveCanceledDetails_Plural".GetLocalizedResource() : "StatusMoveCanceledDetails_Plural2".GetLocalizedResource() + : "StatusMoveCanceledDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, "TheRecycleBin".GetLocalizedResource(), itemsDeleted), 0, ReturnResult.Cancelled, FileOperationType.Recycle); @@ -55,14 +40,9 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable 1 - ? "StatusCenter_DeleteInProgress_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_DeleteInProgress_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir), + // deleting items from + return StatusCenterViewModel.AddItem(string.Empty, + string.Format(source.Count() > 1 ? "StatusDeletingItemsDetails_Plural".GetLocalizedResource() : "StatusDeletingItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir), 0, ReturnResult.InProgress, FileOperationType.Delete, @@ -70,15 +50,9 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable 1 - ? "StatusCenter_MoveInProgress_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_MoveInProgress_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - "TheRecycleBin".GetLocalizedResource()), + // "Moving items from to recycle bin" + return StatusCenterViewModel.AddItem(string.Empty, + string.Format(source.Count() > 1 ? "StatusMovingItemsDetails_Plural".GetLocalizedResource() : "StatusMovingItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, "TheRecycleBin".GetLocalizedResource()), 0, ReturnResult.InProgress, FileOperationType.Recycle, @@ -89,32 +63,18 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable 1 - ? "StatusCenter_DeleteSuccessful_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_DeleteSuccessful_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - itemsDeleted), + return StatusCenterViewModel.AddItem( + "StatusDeletionComplete".GetLocalizedResource(), + string.Format(source.Count() > 1 ? "StatusDeletedItemsDetails_Plural".GetLocalizedResource() : "StatusDeletedItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, itemsDeleted), 0, ReturnResult.Success, FileOperationType.Delete); } else { - // Done done recycling successfully - return _statusCenterViewModel.AddItem( - "StatusCenter_RecycleSuccessful_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? "StatusCenter_DeleteSuccessful_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_DeleteSuccessful_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - "TheRecycleBin".GetLocalizedResource()), + return StatusCenterViewModel.AddItem( + "StatusRecycleComplete".GetLocalizedResource(), + string.Format(source.Count() > 1 ? "StatusMovedItemsDetails_Plural".GetLocalizedResource() : "StatusMovedItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, "TheRecycleBin".GetLocalizedResource()), 0, ReturnResult.Success, FileOperationType.Recycle); @@ -124,32 +84,18 @@ public static StatusCenterItem PostBanner_Delete(IEnumerable 1 - ? "StatusCenter_DeleteError_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_DeleteError_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir - ), + return StatusCenterViewModel.AddItem( + "StatusDeletionFailed".GetLocalizedResource(), + string.Format(source.Count() > 1 ? "StatusDeletionFailedDetails_Plural".GetLocalizedResource() : "StatusDeletionFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir), 0, ReturnResult.Failed, FileOperationType.Delete); } else { - // Done recycling with error - return _statusCenterViewModel.AddItem( - "StatusCenter_RecycleError_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? "StatusCenter_MoveError_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_MoveError_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - "TheRecycleBin".GetLocalizedResource()), + return StatusCenterViewModel.AddItem( + "StatusRecycleFailed".GetLocalizedResource(), + string.Format(source.Count() > 1 ? "StatusMoveFailedDetails_Plural".GetLocalizedResource() : "StatusMoveFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, "TheRecycleBin".GetLocalizedResource()), 0, ReturnResult.Failed, FileOperationType.Recycle); @@ -164,65 +110,38 @@ public static StatusCenterItem PostBanner_Copy(IEnumerable if (canceled) { - // Cancel - return _statusCenterViewModel.AddItem( - "StatusCenter_CopyCancel_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? itemsCopied > 1 - ? "StatusCenter_CopyCancel_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_CopyCancel_SubHeader_Plural2".GetLocalizedResource() - : "StatusCenter_CopyCancel_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - destinationDir, - itemsCopied), + return StatusCenterViewModel.AddItem( + "StatusCopyCanceled".GetLocalizedResource(), + string.Format(source.Count() > 1 ? + itemsCopied > 1 ? "StatusCopyCanceledDetails_Plural".GetLocalizedResource() : "StatusCopyCanceledDetails_Plural2".GetLocalizedResource() : + "StatusCopyCanceledDetails_Singular".GetLocalizedResource(), source.Count(), destinationDir, itemsCopied), 0, ReturnResult.Cancelled, FileOperationType.Copy); } else if (returnStatus == ReturnResult.InProgress) { - // In progress - return _statusCenterViewModel.AddItem( + return StatusCenterViewModel.AddItem( string.Empty, - string.Format( - source.Count() > 1 - ? "StatusCenter_CopyInProgress_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_CopyInProgress_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - destinationDir), + string.Format(source.Count() > 1 ? "StatusCopyingItemsDetails_Plural".GetLocalizedResource() : "StatusCopyingItemsDetails_Singular".GetLocalizedResource(), source.Count(), destinationDir), 0, ReturnResult.InProgress, FileOperationType.Copy, new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) { - // Done successfully - return _statusCenterViewModel.AddItem( - "StatusCopySuccessful".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? "StatusCenter_CopySuccessfulSubHeader_Plural".GetLocalizedResource() - : "StatusCenter_CopySuccessfulSubHeader_Singular".GetLocalizedResource(), - source.Count(), - destinationDir, - itemsCopied), + return StatusCenterViewModel.AddItem( + "StatusCopyComplete".GetLocalizedResource(), + string.Format(source.Count() > 1 ? "StatusCopiedItemsDetails_Plural".GetLocalizedResource() : "StatusCopiedItemsDetails_Singular".GetLocalizedResource(), source.Count(), destinationDir, itemsCopied), 0, ReturnResult.Success, FileOperationType.Copy); } else { - // Done with error - return _statusCenterViewModel.AddItem( - "StatusCenter_CopyError_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? "StatusCenter_CopyError_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_CopyError_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - destinationDir), + return StatusCenterViewModel.AddItem( + "StatusCopyFailed".GetLocalizedResource(), + string.Format(source.Count() > 1 ? "StatusCopyFailedDetails_Plural".GetLocalizedResource() : "StatusCopyFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir), 0, ReturnResult.Failed, FileOperationType.Copy); @@ -236,181 +155,42 @@ public static StatusCenterItem PostBanner_Move(IEnumerable if (canceled) { - return _statusCenterViewModel.AddItem( - "StatusCenter_MoveCancel_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? itemsMoved > 1 - ? "StatusCenter_MoveCancel_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_MoveCancel_SubHeader_Plural2".GetLocalizedResource() - : "StatusCenter_MoveCancel_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - destinationDir, - itemsMoved), + return StatusCenterViewModel.AddItem( + "StatusMoveCanceled".GetLocalizedResource(), + string.Format(source.Count() > 1 ? + itemsMoved > 1 ? "StatusMoveCanceledDetails_Plural".GetLocalizedResource() : "StatusMoveCanceledDetails_Plural2".GetLocalizedResource() + : "StatusMoveCanceledDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir, itemsMoved), 0, ReturnResult.Cancelled, FileOperationType.Move); } else if (returnStatus == ReturnResult.InProgress) { - return _statusCenterViewModel.AddItem( + return StatusCenterViewModel.AddItem( string.Empty, - string.Format( - source.Count() > 1 - ? "StatusCenter_MoveInProgress_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_MoveInProgress_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, - destinationDir), + string.Format(source.Count() > 1 ? "StatusMovingItemsDetails_Plural".GetLocalizedResource() : "StatusMovingItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir), 0, ReturnResult.InProgress, FileOperationType.Move, new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) { - return _statusCenterViewModel.AddItem( - "StatusCenter_MoveSuccessful_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? "StatusCenter_MoveSuccessful_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_MoveSuccessful_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, destinationDir, itemsMoved), + return StatusCenterViewModel.AddItem( + "StatusMoveComplete".GetLocalizedResource(), + string.Format(source.Count() > 1 ? "StatusMovedItemsDetails_Plural".GetLocalizedResource() : "StatusMovedItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir, itemsMoved), 0, ReturnResult.Success, FileOperationType.Move); } else { - return _statusCenterViewModel.AddItem( - "StatusCenter_MoveError_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? "StatusCenter_MoveError_SubHeader_Plural".GetLocalizedResource() - : "StatusCenter_MoveError_SubHeader_Singular".GetLocalizedResource(), - source.Count(), - sourceDir, destinationDir), + return StatusCenterViewModel.AddItem( + "StatusMoveFailed".GetLocalizedResource(), + string.Format(source.Count() > 1 ? "StatusMoveFailedDetails_Plural".GetLocalizedResource() : "StatusMoveFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir), 0, ReturnResult.Failed, FileOperationType.Move); } } - - public static StatusCenterItem PostBanner_Compress(IEnumerable source, IEnumerable destination, ReturnResult returnStatus, bool canceled, int itemsProcessedSuccessfully) - { - var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault()); - var destinationDir = PathNormalization.GetParentDir(destination.FirstOrDefault()); - - if (canceled) - { - return _statusCenterViewModel.AddItem( - "StatusCenter_CompressCancel_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? itemsProcessedSuccessfully > 1 - ? "StatusCenter_CompressCancel_SubHeader".GetLocalizedResource() - : "StatusCenter_CompressCancel_SubHeader".GetLocalizedResource() - : "StatusCenter_CompressCancel_SubHeader".GetLocalizedResource(), - source.Count(), - sourceDir, - destinationDir, - itemsProcessedSuccessfully), - 0, - ReturnResult.Cancelled, - FileOperationType.Move); - } - else if (returnStatus == ReturnResult.InProgress) - { - return _statusCenterViewModel.AddItem( - "StatusCenter_CompressInProgress_Header".GetLocalizedResource(), - destinationDir, - initialProgress: 0, - ReturnResult.InProgress, - FileOperationType.Compressed, - new CancellationTokenSource()); - } - else if (returnStatus == ReturnResult.Success) - { - return _statusCenterViewModel.AddItem( - "StatusCenter_CompressSuccessful_Header".GetLocalizedResource(), - string.Format( - "StatusCenter_CompressSuccessful_SubHeader".GetLocalizedResource(), - destinationDir), - 0, - ReturnResult.Success, - FileOperationType.Compressed); - } - else - { - return _statusCenterViewModel.AddItem( - "StatusCenter_CompressError_Header".GetLocalizedResource(), - string.Format( - "StatusCenter_CompressError_SubHeader".GetLocalizedResource(), - destinationDir), - 0, - ReturnResult.Failed, - FileOperationType.Compressed - ); - } - } - - public static StatusCenterItem PostBanner_Decompress(IEnumerable source, IEnumerable destination, ReturnResult returnStatus, bool canceled, int itemsProcessedSuccessfully) - { - var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault()); - var destinationDir = PathNormalization.GetParentDir(destination.FirstOrDefault()); - - if (canceled) - { - return _statusCenterViewModel.AddItem( - "StatusCenter_DecompressCancel_Header".GetLocalizedResource(), - string.Format( - source.Count() > 1 - ? itemsProcessedSuccessfully > 1 - ? "StatusCenter_DecompressCancel_SubHeader".GetLocalizedResource() - : "StatusCenter_DecompressCancel_SubHeader".GetLocalizedResource() - : "StatusCenter_DecompressCancel_SubHeader".GetLocalizedResource(), - source.Count(), - sourceDir, - destinationDir, - itemsProcessedSuccessfully), - 0, - ReturnResult.Cancelled, - FileOperationType.Move); - } - else if (returnStatus == ReturnResult.InProgress) - { - return _statusCenterViewModel.AddItem( - "StatusCenter_DecompressInProgress_Header".GetLocalizedResource(), - destinationDir, - initialProgress: 0, - ReturnResult.InProgress, - FileOperationType.Compressed, - new CancellationTokenSource()); - } - else if (returnStatus == ReturnResult.Success) - { - return _statusCenterViewModel.AddItem( - "StatusCenter_DecompressSuccessful_Header".GetLocalizedResource(), - string.Format( - "StatusCenter_DecompressSuccessful_SubHeader".GetLocalizedResource(), - destinationDir), - 0, - ReturnResult.Success, - FileOperationType.Compressed); - } - else - { - return _statusCenterViewModel.AddItem( - "StatusCenter_DecompressError_Header".GetLocalizedResource(), - string.Format( - "StatusCenter_DecompressError_SubHeader".GetLocalizedResource(), - destinationDir), - 0, - ReturnResult.Failed, - FileOperationType.Compressed - ); - } - } } } diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 03a4f0cad20c..8bd21ee885a2 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -105,6 +105,11 @@ public string? SpeedText MaxLimit = 100, ShowSeparatorLines = false, + //SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) + //{ + // StrokeThickness = 0.5F, + // PathEffect = new DashEffect(new float[] { 3, 3 }) + //} } }; @@ -116,6 +121,11 @@ public string? SpeedText Labels = new List(), ShowSeparatorLines = false, + //SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) + //{ + // StrokeThickness = 0.5F, + // PathEffect = new DashEffect(new float[] { 3, 3 }) + //} } }; @@ -143,18 +153,15 @@ public bool IsCancelable public StatusCenterItem(string message, string title, float progress, ReturnResult status, FileOperationType operation, CancellationTokenSource operationCancellationToken = default) { - Header = title; - HeaderBody = title; + _operationCancellationToken = operationCancellationToken; SubHeader = message; - + HeaderBody = title; + Header = title; FileSystemOperationReturnResult = status; Operation = operation; - ProgressEventSource = new Progress(ReportProgress); Progress = new(ProgressEventSource, status: FileSystemStatusCode.InProgress); - _operationCancellationToken = operationCancellationToken; - CancelCommand = new RelayCommand(ExecuteCancelCommand); Values = new(); @@ -196,14 +203,13 @@ public StatusCenterItem(string message, string title, float progress, ReturnResu HeaderBody = Operation switch { - FileOperationType.Compressed => "StatusCenter_CompressInProgress_Header".GetLocalizedResource(), - FileOperationType.Extract => "StatusCenter_DecompressInProgress_Header".GetLocalizedResource(), - FileOperationType.Copy => "StatusCenter_CopyInProgress_Header".GetLocalizedResource(), - FileOperationType.Move => "StatusCenter_MoveInProgress_Header".GetLocalizedResource(), - FileOperationType.Delete => "StatusCenter_DeleteInProgress_Header".GetLocalizedResource(), - FileOperationType.Recycle => "StatusCenter_RecycleInProgress_Header".GetLocalizedResource(), - FileOperationType.Prepare => "StatusCenter_PrepareInProgress_Header_Plural".GetLocalizedResource(), - _ => "StatusCenter_PrepareInProgress_Header_Plural".GetLocalizedResource(), + FileOperationType.Extract => "ExtractInProgress/Title".GetLocalizedResource(), + FileOperationType.Copy => "CopyInProgress/Title".GetLocalizedResource(), + FileOperationType.Move => "MoveInProgress".GetLocalizedResource(), + FileOperationType.Delete => "DeleteInProgress/Title".GetLocalizedResource(), + FileOperationType.Recycle => "RecycleInProgress/Title".GetLocalizedResource(), + FileOperationType.Prepare => "PrepareInProgress".GetLocalizedResource(), + _ => "PrepareInProgress".GetLocalizedResource(), }; Header = $"{HeaderBody} ({progress}%)"; @@ -211,12 +217,11 @@ public StatusCenterItem(string message, string title, float progress, ReturnResu ItemIconKind = Operation switch { - FileOperationType.Compressed => StatusCenterItemIconKind.Extract, - FileOperationType.Extract => StatusCenterItemIconKind.Extract, - FileOperationType.Copy => StatusCenterItemIconKind.Copy, - FileOperationType.Move => StatusCenterItemIconKind.Move, - FileOperationType.Delete => StatusCenterItemIconKind.Delete, - FileOperationType.Recycle => StatusCenterItemIconKind.Recycle, + FileOperationType.Extract => StatusCenterItemIconKind.Extract, + FileOperationType.Copy => StatusCenterItemIconKind.Copy, + FileOperationType.Move => StatusCenterItemIconKind.Move, + FileOperationType.Delete => StatusCenterItemIconKind.Delete, + FileOperationType.Recycle => StatusCenterItemIconKind.Recycle, _ => StatusCenterItemIconKind.Delete, }; @@ -279,24 +284,21 @@ private void ReportProgress(StatusCenterItemProgressModel value) // In progress, displaying items count & processed size case (not 0, not 0): ProgressPercentage = (int)(value.ProcessedSize * 100.0 / value.TotalSize); - Header = $"{HeaderBody} ({ProgressPercentage:0}%)"; - + Header = $"{HeaderBody} ({value.ProcessedItemsCount} ({value.ProcessedSize.ToSizeString()}) / {value.ItemsCount} ({value.TotalSize.ToSizeString()}): {ProgressPercentage}%)"; SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s"; Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); break; // In progress, displaying processed size case (not 0, _): ProgressPercentage = (int)(value.ProcessedSize * 100.0 / value.TotalSize); - Header = $"{HeaderBody} ({ProgressPercentage:0}%)"; - + Header = $"{HeaderBody} ({value.ProcessedSize.ToSizeString()} / {value.TotalSize.ToSizeString()}: {ProgressPercentage}%)"; SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s"; Values.Add(new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed)); break; // In progress, displaying items count case (_, not 0): ProgressPercentage = (int)(value.ProcessedItemsCount * 100.0 / value.ItemsCount); - Header = $"{HeaderBody} ({ProgressPercentage:0}%)"; - + Header = $"{HeaderBody} ({value.ProcessedItemsCount} / {value.ItemsCount}: {ProgressPercentage}%)"; SpeedText = $"{value.ProcessedItemsCount:0} items/s"; Values.Add(new(value.ProcessedItemsCount * 100.0 / value.ItemsCount, value.ProcessingItemsCountSpeed)); break; From 19c4d7cc59beb361a872a7c423f65b02125cc5be Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Tue, 19 Sep 2023 01:22:04 +0900 Subject: [PATCH 19/97] Update --- src/Files.App/UserControls/StatusCenter.xaml | 47 ++++++++++++++----- .../Utils/StatusCenter/StatusCenterItem.cs | 19 +++----- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/Files.App/UserControls/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter.xaml index 3e534e66efc5..359175a91244 100644 --- a/src/Files.App/UserControls/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter.xaml @@ -82,7 +82,7 @@ VerticalAlignment="Center"> + ToolTipService.ToolTip="{x:Bind SubHeader, Mode=OneWay}" + Visibility="{x:Bind IsInProgress, Converter={StaticResource NegateBoolToVisibilityConverter}, Mode=OneWay}" /> @@ -160,13 +161,17 @@ - + + + + + @@ -175,7 +180,8 @@ @@ -184,7 +190,8 @@ + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -472,7 +56,6 @@ Grid.Row="1" Padding="12,0,12,12" VerticalAlignment="Stretch" - ItemTemplate="{StaticResource StatusCenterItemCardStyleDataTemplate}" ItemsSource="{x:Bind ViewModel.StatusCenterItems, Mode=OneWay}" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollMode="Enabled" @@ -486,6 +69,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (); - public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool permanently, IEnumerable? source) + public static StatusCenterItem AddCard_Delete( + ReturnResult returnStatus, + bool permanently, + IEnumerable? source, + long itemsCount = 0, + long totalSize = 0) { string? sourceDir = string.Empty; @@ -25,7 +30,9 @@ public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool pe FileOperationType.Delete, source?.Select(x => x.Path) ?? string.Empty.CreateEnumerable(), null, - true); + true, + itemsCount, + totalSize); } else { @@ -36,7 +43,9 @@ public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool pe FileOperationType.Recycle, source?.Select(x => x.Path), null, - true); + true, + itemsCount, + totalSize); } } else if (returnStatus == ReturnResult.InProgress) @@ -51,6 +60,8 @@ public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool pe source?.Select(x => x.Path), null, true, + itemsCount, + totalSize, new CancellationTokenSource()); } else @@ -63,6 +74,8 @@ public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool pe source?.Select(x => x.Path), null, true, + itemsCount, + totalSize, new CancellationTokenSource()); } } @@ -77,7 +90,9 @@ public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool pe FileOperationType.Delete, source?.Select(x => x.Path), null, - true); + true, + itemsCount, + totalSize); } else { @@ -88,7 +103,9 @@ public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool pe FileOperationType.Recycle, source?.Select(x => x.Path), null, - true); + true, + itemsCount, + totalSize); } } else @@ -102,7 +119,9 @@ public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool pe FileOperationType.Delete, source?.Select(x => x.Path), null, - true); + true, + itemsCount, + totalSize); } else { @@ -113,12 +132,19 @@ public static StatusCenterItem AddCard_Delete(ReturnResult returnStatus, bool pe FileOperationType.Recycle, source?.Select(x => x.Path), null, - true); + true, + itemsCount, + totalSize); } } } - public static StatusCenterItem AddCard_Copy(ReturnResult returnStatus, IEnumerable source, IEnumerable destination) + public static StatusCenterItem AddCard_Copy( + ReturnResult returnStatus, + IEnumerable source, + IEnumerable destination, + long itemsCount = 0, + long totalSize = 0) { string? sourceDir = string.Empty; string? destinationDir = string.Empty; @@ -138,7 +164,9 @@ public static StatusCenterItem AddCard_Copy(ReturnResult returnStatus, IEnumerab FileOperationType.Copy, source?.Select(x => x.Path), destination, - true); + true, + itemsCount, + totalSize); } else if (returnStatus == ReturnResult.InProgress) { @@ -150,6 +178,8 @@ public static StatusCenterItem AddCard_Copy(ReturnResult returnStatus, IEnumerab source?.Select(x => x.Path), destination, true, + itemsCount, + totalSize, new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) @@ -161,7 +191,9 @@ public static StatusCenterItem AddCard_Copy(ReturnResult returnStatus, IEnumerab FileOperationType.Copy, source?.Select(x => x.Path), destination, - true); + true, + itemsCount, + totalSize); } else { @@ -172,11 +204,18 @@ public static StatusCenterItem AddCard_Copy(ReturnResult returnStatus, IEnumerab FileOperationType.Copy, source?.Select(x => x.Path), destination, - true); + true, + itemsCount, + totalSize); } } - public static StatusCenterItem AddCard_Move(ReturnResult returnStatus, IEnumerable source, IEnumerable destination) + public static StatusCenterItem AddCard_Move( + ReturnResult returnStatus, + IEnumerable source, + IEnumerable destination, + long itemsCount = 0, + long totalSize = 0) { var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault()?.Path); var destinationDir = PathNormalization.GetParentDir(destination.FirstOrDefault()); @@ -190,7 +229,9 @@ public static StatusCenterItem AddCard_Move(ReturnResult returnStatus, IEnumerab FileOperationType.Move, source.Select(x => x.Path), destination, - true); + true, + itemsCount, + totalSize); } else if (returnStatus == ReturnResult.InProgress) { @@ -202,6 +243,8 @@ public static StatusCenterItem AddCard_Move(ReturnResult returnStatus, IEnumerab source.Select(x => x.Path), destination, true, + itemsCount, + totalSize, new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) @@ -213,7 +256,9 @@ public static StatusCenterItem AddCard_Move(ReturnResult returnStatus, IEnumerab FileOperationType.Move, source.Select(x => x.Path), destination, - true); + true, + itemsCount, + totalSize); } else { @@ -224,11 +269,18 @@ public static StatusCenterItem AddCard_Move(ReturnResult returnStatus, IEnumerab FileOperationType.Move, source.Select(x => x.Path), destination, - true); + true, + itemsCount, + totalSize); } } - public static StatusCenterItem AddCard_Compress(IEnumerable source, IEnumerable destination, ReturnResult returnStatus) + public static StatusCenterItem AddCard_Compress( + IEnumerable source, + IEnumerable destination, + ReturnResult returnStatus, + long itemsCount = 0, + long totalSize = 0) { // Currently not supported accurate progress report for emptying the recycle bin @@ -244,7 +296,9 @@ public static StatusCenterItem AddCard_Compress(IEnumerable source, IEnu FileOperationType.Compressed, source, destination, - false); + false, + itemsCount, + totalSize); } else if (returnStatus == ReturnResult.InProgress) { @@ -256,6 +310,8 @@ public static StatusCenterItem AddCard_Compress(IEnumerable source, IEnu source, destination, false, + itemsCount, + totalSize, new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) @@ -267,7 +323,9 @@ public static StatusCenterItem AddCard_Compress(IEnumerable source, IEnu FileOperationType.Compressed, source, destination, - false); + false, + itemsCount, + totalSize); } else { @@ -278,11 +336,18 @@ public static StatusCenterItem AddCard_Compress(IEnumerable source, IEnu FileOperationType.Compressed, source, destination, - false); + false, + itemsCount, + totalSize); } } - public static StatusCenterItem AddCard_Decompress(IEnumerable source, IEnumerable destination, ReturnResult returnStatus) + public static StatusCenterItem AddCard_Decompress( + IEnumerable source, + IEnumerable destination, + ReturnResult returnStatus, + long itemsCount = 0, + long totalSize = 0) { // Currently not supported accurate progress report for emptying the recycle bin @@ -298,7 +363,9 @@ public static StatusCenterItem AddCard_Decompress(IEnumerable source, IE FileOperationType.Extract, source, destination, - false); + false, + itemsCount, + totalSize); } else if (returnStatus == ReturnResult.InProgress) { @@ -310,6 +377,8 @@ public static StatusCenterItem AddCard_Decompress(IEnumerable source, IE source, destination, false, + itemsCount, + totalSize, new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) @@ -321,7 +390,9 @@ public static StatusCenterItem AddCard_Decompress(IEnumerable source, IE FileOperationType.Extract, source, destination, - false); + false, + itemsCount, + totalSize); } else { @@ -332,11 +403,16 @@ public static StatusCenterItem AddCard_Decompress(IEnumerable source, IE FileOperationType.Extract, source, destination, - false); + false, + itemsCount, + totalSize); } } - public static StatusCenterItem AddCard_EmptyRecycleBin(ReturnResult returnStatus) + public static StatusCenterItem AddCard_EmptyRecycleBin( + ReturnResult returnStatus, + long itemsCount = 0, + long totalSize = 0) { // Currently not supported accurate progress report for emptying the recycle bin @@ -349,7 +425,9 @@ public static StatusCenterItem AddCard_EmptyRecycleBin(ReturnResult returnStatus FileOperationType.Delete, null, null, - false); + false, + itemsCount, + totalSize); } else if (returnStatus == ReturnResult.InProgress) { @@ -360,7 +438,10 @@ public static StatusCenterItem AddCard_EmptyRecycleBin(ReturnResult returnStatus FileOperationType.Delete, null, null, - false); + false, + itemsCount, + totalSize, + new CancellationTokenSource()); } else if (returnStatus == ReturnResult.Success) { @@ -371,7 +452,9 @@ public static StatusCenterItem AddCard_EmptyRecycleBin(ReturnResult returnStatus FileOperationType.Delete, null, null, - false); + false, + itemsCount, + totalSize); } else { @@ -382,7 +465,9 @@ public static StatusCenterItem AddCard_EmptyRecycleBin(ReturnResult returnStatus FileOperationType.Delete, null, null, - false); + false, + itemsCount, + totalSize); } } diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index c66de8b26556..3cb95584d5fd 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -188,6 +188,8 @@ public StatusCenterItem( IEnumerable? source, IEnumerable? destination, bool canProvideProgress = false, + long itemsCount = 0, + long totalSize = 0, CancellationTokenSource operationCancellationToken = default) { _operationCancellationToken = operationCancellationToken; @@ -280,7 +282,7 @@ public StatusCenterItem( } } - StatusCenterHelper.UpdateCardStrings(this, Source, Destination, Source?.Count() ?? 0); + StatusCenterHelper.UpdateCardStrings(this, Source, Destination, itemsCount); } private void ReportProgress(StatusCenterItemProgressModel value) @@ -316,7 +318,6 @@ private void ReportProgress(StatusCenterItemProgressModel value) value.ProcessedSize.ToSizeString(), value.TotalSize.ToSizeString()); } - } if (CurrentProcessingItemNameText != value.FileName) diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs index d7bb21ed39af..c6e8d89e8270 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs @@ -167,6 +167,8 @@ showDialog is DeleteConfirmationPolicies.PermanentOnly && // Remove items from jump list source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); + var itemsCount = banner.Progress.ItemsCount; + // Remove the in-progress card from the StatusCenter _statusCenterViewModel.RemoveItem(banner); @@ -176,7 +178,8 @@ showDialog is DeleteConfirmationPolicies.PermanentOnly && StatusCenterHelper.AddCard_Delete( token.IsCancellationRequested ? ReturnResult.Cancelled : returnStatus, permanently, - source); + source, + itemsCount); return returnStatus; } @@ -335,12 +338,15 @@ public async Task CopyItemsAsync(IEnumerable App.HistoryWrapper.AddHistory(history); } + var itemsCount = banner.Progress.ItemsCount; + _statusCenterViewModel.RemoveItem(banner); StatusCenterHelper.AddCard_Copy( token.IsCancellationRequested ? ReturnResult.Cancelled : returnStatus, source, - destination); + destination, + itemsCount); return returnStatus; } @@ -474,6 +480,8 @@ public async Task MoveItemsAsync(IEnumerable // Remove items from jump list source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); + var itemsCount = banner.Progress.ItemsCount; + _statusCenterViewModel.RemoveItem(banner); sw.Stop(); @@ -481,7 +489,8 @@ public async Task MoveItemsAsync(IEnumerable StatusCenterHelper.AddCard_Move( token.IsCancellationRequested ? ReturnResult.Cancelled : returnStatus, source, - destination); + destination, + itemsCount); return returnStatus; } diff --git a/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs b/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs index eff962a45293..d6277ceba819 100644 --- a/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs @@ -72,6 +72,8 @@ public StatusCenterItem AddItem( IEnumerable? source, IEnumerable? destination, bool canProvideProgress = true, + long itemsCount = 0, + long totalSize = 0, CancellationTokenSource cancellationTokenSource = null) { var banner = new StatusCenterItem( @@ -82,6 +84,8 @@ public StatusCenterItem AddItem( source, destination, canProvideProgress, + itemsCount = 0, + totalSize = 0, cancellationTokenSource); StatusCenterItems.Insert(0, banner); From ef3ec5c7c8583c9db782e1b1c60e21ef0d272c54 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Fri, 29 Sep 2023 22:59:58 +0900 Subject: [PATCH 60/97] Update --- src/Files.App/UserControls/StatusCenter.xaml | 31 ++++++------------- .../Utils/StatusCenter/StatusCenterItem.cs | 19 ++++++++++-- .../Storage/Operations/FilesystemHelpers.cs | 6 ++-- .../UserControls/StatusCenterViewModel.cs | 4 +-- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/Files.App/UserControls/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter.xaml index 90ead3e86130..7228d8bf8050 100644 --- a/src/Files.App/UserControls/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter.xaml @@ -77,7 +77,7 @@ Padding="8,8,8,8" HorizontalAlignment="Stretch" AutomationProperties.Name="{x:Bind Header, Mode=OneWay}" - Background="{StaticResource CardBackgroundFillColorDefaultBrush}" + Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1" CornerRadius="{StaticResource OverlayCornerRadius}"> @@ -104,13 +104,13 @@ - + CornerRadius="16" /> - - @@ -395,8 +384,8 @@ - - + + @@ -405,7 +394,7 @@ - + @@ -414,7 +403,7 @@ - + diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 3cb95584d5fd..22035f2bb894 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -160,6 +160,10 @@ public bool IsCancelable public CancellationToken CancellationToken => _operationCancellationToken?.Token ?? default; + public long TotalSize { get; set; } + + public long TotalItemsCount { get; set; } + public bool IsInProgress { get; set; } public ReturnResult FileSystemOperationReturnResult { get; set; } @@ -200,6 +204,8 @@ public StatusCenterItem( ProgressEventSource = new Progress(ReportProgress); Progress = new(ProgressEventSource, status: FileSystemStatusCode.InProgress); IsCancelable = _operationCancellationToken is not null; + TotalItemsCount = itemsCount; + TotalSize = totalSize; if (source is not null) Source = source; @@ -282,12 +288,13 @@ public StatusCenterItem( } } - StatusCenterHelper.UpdateCardStrings(this, Source, Destination, itemsCount); + StatusCenterHelper.UpdateCardStrings(this, Source, Destination, TotalItemsCount); } private void ReportProgress(StatusCenterItemProgressModel value) { - // The operation has been canceled. Do update neither progress value nor text. + // The operation has been canceled. + // Do update neither progress value nor text. if (CancellationToken.IsCancellationRequested) return; @@ -326,6 +333,12 @@ private void ReportProgress(StatusCenterItemProgressModel value) StatusCenterHelper.UpdateCardStrings(this, Source, Destination, value.ItemsCount); + if (TotalItemsCount < value.ItemsCount) + TotalItemsCount = value.ItemsCount; + + if (TotalSize < value.TotalSize) + TotalSize = value.TotalSize; + ObservablePoint point; switch (value.TotalSize, value.ItemsCount) @@ -391,9 +404,9 @@ public void ExecuteCancelCommand() _operationCancellationToken?.Cancel(); IsIndeterminateProgress = true; IsCancelable = false; - Header = $"{Header} ({"Canceling".GetLocalizedResource()})"; IsExpanded = false; IsSpeedAndProgressAvailable = false; + Header = $"{Header} ({"Canceling".GetLocalizedResource()})"; } } } diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs index c6e8d89e8270..b1873633f84f 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs @@ -167,7 +167,7 @@ showDialog is DeleteConfirmationPolicies.PermanentOnly && // Remove items from jump list source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); - var itemsCount = banner.Progress.ItemsCount; + var itemsCount = banner.TotalItemsCount; // Remove the in-progress card from the StatusCenter _statusCenterViewModel.RemoveItem(banner); @@ -338,7 +338,7 @@ public async Task CopyItemsAsync(IEnumerable App.HistoryWrapper.AddHistory(history); } - var itemsCount = banner.Progress.ItemsCount; + var itemsCount = banner.TotalItemsCount; _statusCenterViewModel.RemoveItem(banner); @@ -480,7 +480,7 @@ public async Task MoveItemsAsync(IEnumerable // Remove items from jump list source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); - var itemsCount = banner.Progress.ItemsCount; + var itemsCount = banner.TotalItemsCount; _statusCenterViewModel.RemoveItem(banner); diff --git a/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs b/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs index d6277ceba819..dc5c5554be90 100644 --- a/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs @@ -84,8 +84,8 @@ public StatusCenterItem AddItem( source, destination, canProvideProgress, - itemsCount = 0, - totalSize = 0, + itemsCount, + totalSize, cancellationTokenSource); StatusCenterItems.Insert(0, banner); From 0dbfc5cf6c6d04b6b35aa368d75ef63445a8b493 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 30 Sep 2023 23:02:25 +0900 Subject: [PATCH 61/97] Update --- src/Files.App/App.xaml | 5 +- .../StatusCenterStateToBrushConverter.cs | 100 +++ .../StatusCenterStateToStateIconConverter.cs | 52 ++ .../ResourceDictionaries/AppThemeGeneric.xaml | 29 + .../StatusCenterStyles.xaml | 20 +- src/Files.App/Strings/en-US/Resources.resw | 2 +- src/Files.App/UserControls/StatusCenter.xaml | 630 ++++++++---------- .../Utils/StatusCenter/StatusCenterItem.cs | 228 ++++--- 8 files changed, 580 insertions(+), 486 deletions(-) create mode 100644 src/Files.App/Converters/StatusCenterStateToBrushConverter.cs create mode 100644 src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs create mode 100644 src/Files.App/ResourceDictionaries/AppThemeGeneric.xaml diff --git a/src/Files.App/App.xaml b/src/Files.App/App.xaml index 51f0b13ff276..11cfcaaa3cb5 100644 --- a/src/Files.App/App.xaml +++ b/src/Files.App/App.xaml @@ -30,8 +30,9 @@ - - + + + diff --git a/src/Files.App/Converters/StatusCenterStateToBrushConverter.cs b/src/Files.App/Converters/StatusCenterStateToBrushConverter.cs new file mode 100644 index 000000000000..ac786858011e --- /dev/null +++ b/src/Files.App/Converters/StatusCenterStateToBrushConverter.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; + +namespace Files.App.Converters +{ + public class StatusCenterStateToBrushConverter : DependencyObject, IValueConverter + { + public static readonly DependencyProperty InProgressBackgroundBrushProperty = + DependencyProperty.Register(nameof(InProgressBackgroundBrush), typeof(SolidColorBrush), typeof(StatusCenterStateToBrushConverter), new PropertyMetadata(null)); + + public static readonly DependencyProperty InProgressForegroundBrushProperty = + DependencyProperty.Register(nameof(InProgressForegroundBrush), typeof(SolidColorBrush), typeof(StatusCenterStateToBrushConverter), new PropertyMetadata(null)); + + public static readonly DependencyProperty SuccessfulBackgroundBrushProperty = + DependencyProperty.Register(nameof(SuccessfulBackgroundBrush), typeof(SolidColorBrush), typeof(StatusCenterStateToBrushConverter), new PropertyMetadata(null)); + + public static readonly DependencyProperty SuccessfulForegroundBrushProperty = + DependencyProperty.Register(nameof(SuccessfulForegroundBrush), typeof(SolidColorBrush), typeof(StatusCenterStateToBrushConverter), new PropertyMetadata(null)); + + public static readonly DependencyProperty ErrorBackgroundBrushProperty = + DependencyProperty.Register(nameof(ErrorBackgroundBrush), typeof(SolidColorBrush), typeof(StatusCenterStateToBrushConverter), new PropertyMetadata(null)); + + public static readonly DependencyProperty ErrorForegroundBrushProperty = + DependencyProperty.Register(nameof(ErrorForegroundBrush), typeof(SolidColorBrush), typeof(StatusCenterStateToBrushConverter), new PropertyMetadata(null)); + + public SolidColorBrush InProgressBackgroundBrush + { + get => (SolidColorBrush)GetValue(InProgressBackgroundBrushProperty); + set => SetValue(InProgressBackgroundBrushProperty, value); + } + + public SolidColorBrush InProgressForegroundBrush + { + get => (SolidColorBrush)GetValue(InProgressForegroundBrushProperty); + set => SetValue(InProgressForegroundBrushProperty, value); + } + + public SolidColorBrush SuccessfulBackgroundBrush + { + get => (SolidColorBrush)GetValue(SuccessfulBackgroundBrushProperty); + set => SetValue(SuccessfulBackgroundBrushProperty, value); + } + + public SolidColorBrush SuccessfulForegroundBrush + { + get => (SolidColorBrush)GetValue(SuccessfulForegroundBrushProperty); + set => SetValue(SuccessfulForegroundBrushProperty, value); + } + + public SolidColorBrush ErrorBackgroundBrush + { + get => (SolidColorBrush)GetValue(ErrorBackgroundBrushProperty); + set => SetValue(ErrorBackgroundBrushProperty, value); + } + + public SolidColorBrush ErrorForegroundBrush + { + get => (SolidColorBrush)GetValue(ErrorForegroundBrushProperty); + set => SetValue(ErrorForegroundBrushProperty, value); + } + + public object? Convert(object value, Type targetType, object parameter, string language) + { + if (value is StatusCenterItemKind state) + { + if (bool.TryParse(parameter?.ToString(), out var isBackground) && isBackground) + { + return state switch + { + StatusCenterItemKind.InProgress => InProgressBackgroundBrush, + StatusCenterItemKind.Successful => SuccessfulBackgroundBrush, + StatusCenterItemKind.Error => ErrorBackgroundBrush, + _ => InProgressBackgroundBrush + }; + } + else + { + return state switch + { + StatusCenterItemKind.InProgress => InProgressForegroundBrush, + StatusCenterItemKind.Successful => SuccessfulForegroundBrush, + StatusCenterItemKind.Error => ErrorForegroundBrush, + _ => InProgressForegroundBrush + }; + } + } + + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs b/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs new file mode 100644 index 000000000000..28fc1dd792bb --- /dev/null +++ b/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Markup; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Shapes; + +namespace Files.App.Converters +{ + class StatusCenterStateToStateIconConverter : IValueConverter + { + public object? Convert(object value, Type targetType, object parameter, string language) + { + if (value is StatusCenterItemIconKind state) + { + var pathMarkup = state switch + { + StatusCenterItemIconKind.Copy => Application.Current.Resources["App.Theme.PathIcon.ActionCopy"] as string, + StatusCenterItemIconKind.Move => Application.Current.Resources["App.Theme.PathIcon.ActionMove"] as string, + StatusCenterItemIconKind.Delete => Application.Current.Resources["App.Theme.PathIcon.ActionDelete"] as string, + StatusCenterItemIconKind.Recycle => Application.Current.Resources["App.Theme.PathIcon.ActionDelete"] as string, + StatusCenterItemIconKind.Extract => Application.Current.Resources["App.Theme.PathIcon.ActionCopy"] as string, + StatusCenterItemIconKind.Successful => Application.Current.Resources["App.Theme.PathIcon.ActionSuccess"] as string, + StatusCenterItemIconKind.Error => Application.Current.Resources["App.Theme.PathIcon.ActionInfo"] as string, + _ => "" + }; + + string xaml = @$"{pathMarkup}"; + + if (XamlReader.Load(xaml) is not Path path) + return null; + + // Initialize a new instance + Geometry geometry = path.Data; + + // Destroy + path.Data = null; + + return geometry; + } + + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Files.App/ResourceDictionaries/AppThemeGeneric.xaml b/src/Files.App/ResourceDictionaries/AppThemeGeneric.xaml new file mode 100644 index 000000000000..1ef4d2f550e0 --- /dev/null +++ b/src/Files.App/ResourceDictionaries/AppThemeGeneric.xaml @@ -0,0 +1,29 @@ + + + + + + + #0070CB + + + + + + #50C0FF + + + + + + #50C0FF + + + + + + diff --git a/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml b/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml index 182fa7b64d3e..0298d59e770e 100644 --- a/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml +++ b/src/Files.App/ResourceDictionaries/StatusCenterStyles.xaml @@ -6,8 +6,7 @@ xmlns:wctconverters="using:CommunityToolkit.WinUI.UI.Converters"> - - + @@ -20,10 +19,8 @@ - - - + @@ -36,7 +33,6 @@ - @@ -55,6 +51,16 @@ + + + diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index a6081dfeb88d..0cc9687c377c 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -1393,7 +1393,7 @@ the Recycle Bin - Canceling... + Canceling Clear diff --git a/src/Files.App/UserControls/StatusCenter.xaml b/src/Files.App/UserControls/StatusCenter.xaml index 7228d8bf8050..a704118ffcc0 100644 --- a/src/Files.App/UserControls/StatusCenter.xaml +++ b/src/Files.App/UserControls/StatusCenter.xaml @@ -28,7 +28,7 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x:Name="StatusCenterItemRichProgressDisplayPanel" + Grid.Row="1" + Margin="0,8,0,8" + ColumnSpacing="4" + Visibility="{x:Bind IsInProgress, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}"> + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + - - + + - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + ToolTipService.ToolTip="{x:Bind CurrentProcessingItemName, Mode=OneWay}"> + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + TextWrapping="NoWrap" /> - - - - - - - - - - - - - - - - - + - - - - - - - + - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 22035f2bb894..619a8cb73e98 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -15,18 +15,13 @@ namespace Files.App.Utils.StatusCenter { /// /// Represents an item for Status Center operation tasks. + ///
+ /// Handles all operation's functionality and UI. ///
public sealed class StatusCenterItem : ObservableObject { private readonly StatusCenterViewModel _viewModel = Ioc.Default.GetRequiredService(); - private string? _Header; - public string? Header - { - get => _Header; - set => SetProperty(ref _Header, value); - } - private int _ProgressPercentage; public int ProgressPercentage { @@ -38,6 +33,51 @@ public int ProgressPercentage } } + private string? _Header; + public string? Header + { + get => _Header; + set => SetProperty(ref _Header, value); + } + + private string? _Message; + public string? Message + { + get => _Message; + set => SetProperty(ref _Message, value); + } + + private string? _SpeedText; + public string? SpeedText + { + get => _SpeedText; + set => SetProperty(ref _SpeedText, value); + } + + private string? _ProgressPercentageText; + public string? ProgressPercentageText + { + get => _ProgressPercentageText; + set => SetProperty(ref _ProgressPercentageText, value); + } + + // Gets or sets the value that represents the current processing item name. + private string? _CurrentProcessingItemName; + public string? CurrentProcessingItemName + { + get => _CurrentProcessingItemName; + set => SetProperty(ref _CurrentProcessingItemName, value); + } + + // TODO: Remove and replace with Message + private string? _CurrentProcessedSizeText; + public string? CurrentProcessedSizeHumanized + { + get => _CurrentProcessedSizeText; + set => SetProperty(ref _CurrentProcessedSizeText, value); + } + + // This property is basically handled by an UI element - ToggleButton private bool _IsExpanded; public bool IsExpanded { @@ -45,33 +85,29 @@ public bool IsExpanded set { AnimatedIconState = value ? "NormalOn" : "NormalOff"; - IsSubFooterVisible = value; SetProperty(ref _IsExpanded, value); } } - private bool _IsSubFooterVisible; - public bool IsSubFooterVisible - { - get => _IsSubFooterVisible; - set => SetProperty(ref _IsSubFooterVisible, value); - } - - private string _AnimatedIconState = "NormalOff"; + // This property is used for AnimatedIcon state + private string _AnimatedIconState; public string AnimatedIconState { get => _AnimatedIconState; set => SetProperty(ref _AnimatedIconState, value); } - private bool _IsSpeedAndProgressAvailable; // Item type is InProgress && is the operation in progress. If true, the chevron won't be shown + // If true, the chevron won't be shown. + // This property will be false basically if the proper progress report is not supported in the operation. + private bool _IsSpeedAndProgressAvailable; public bool IsSpeedAndProgressAvailable { get => _IsSpeedAndProgressAvailable; set => SetProperty(ref _IsSpeedAndProgressAvailable, value); } + // This property will be true basically if the operation was canceled or the operation doesn't support proper progress update. private bool _IsIndeterminateProgress; public bool IsIndeterminateProgress { @@ -79,6 +115,15 @@ public bool IsIndeterminateProgress set => SetProperty(ref _IsIndeterminateProgress, value); } + // This property will be true if the item card is for in-progress and the operation supports cancellation token also. + private bool _IsCancelable; + public bool IsCancelable + { + get => _IsCancelable; + set => SetProperty(ref _IsCancelable, value); + } + + // This property is not updated for now. Should be removed. private StatusCenterItemProgressModel _Progress = null!; public StatusCenterItemProgressModel Progress { @@ -86,97 +131,41 @@ public StatusCenterItemProgressModel Progress set => SetProperty(ref _Progress, value); } - private string? _SpeedText; - public string? SpeedText - { - get => _SpeedText; - set => SetProperty(ref _SpeedText, value); - } - - private string? _CurrentProcessingItemNameText; - public string? CurrentProcessingItemNameText - { - get => _CurrentProcessingItemNameText; - set => SetProperty(ref _CurrentProcessingItemNameText, value); - } - private string? _CurrentProcessedSizeText; - public string? CurrentProcessedSizeText - { - get => _CurrentProcessedSizeText; - set => SetProperty(ref _CurrentProcessedSizeText, value); - } - private string? _ProgressPercentageText; - public string? ProgressPercentageText - { - get => _ProgressPercentageText; - set => SetProperty(ref _ProgressPercentageText, value); - } - private string? _ProgressOverviewText; - public string? ProgressOverviewText - { - get => _ProgressOverviewText; - set => SetProperty(ref _ProgressOverviewText, value); - } + public ReturnResult FileSystemOperationReturnResult { get; private set; } - private bool _IsCancelable; - public bool IsCancelable - { - get => _IsCancelable; - set => SetProperty(ref _IsCancelable, value); - } - - public string? HeaderStringResource { get; set; } - - public ObservableCollection Values { get; set; } - - public ObservableCollection Series { get; set; } + public FileOperationType Operation { get; private set; } - public IList XAxes { get; set; } = new ICartesianAxis[] - { - new Axis - { - Padding = new Padding(0, 0), - Labels = new List(), - MaxLimit = 100, + public StatusCenterItemKind ItemKind { get; private set; } - ShowSeparatorLines = false, - } - }; + public StatusCenterItemIconKind ItemIconKind { get; private set; } - public IList YAxes { get; set; } = new ICartesianAxis[] - { - new Axis - { - Padding = new Padding(0, 0), - Labels = new List(), + public long TotalSize { get; private set; } - ShowSeparatorLines = false, - } - }; + public long TotalItemsCount { get; private set; } - public CancellationToken CancellationToken - => _operationCancellationToken?.Token ?? default; + public bool IsInProgress { get; private set; } - public long TotalSize { get; set; } + public IEnumerable? Source { get; private set; } - public long TotalItemsCount { get; set; } + public IEnumerable? Destination { get; private set; } - public bool IsInProgress { get; set; } + public string? HeaderStringResource { get; private set; } - public ReturnResult FileSystemOperationReturnResult { get; set; } + public ObservableCollection Values { get; private set; } - public FileOperationType Operation { get; private set; } + public ObservableCollection Series { get; private set; } - public StatusCenterItemKind ItemKind { get; private set; } + public IList XAxes { get; private set; } - public StatusCenterItemIconKind ItemIconKind { get; private set; } + public IList YAxes { get; private set; } - public IEnumerable? Source { get; private set; } + public double IconBackgroundCircleBorderOpacity { get; private set; } - public IEnumerable? Destination { get; private set; } + public CancellationToken CancellationToken + => _operationCancellationToken?.Token ?? default; public readonly Progress ProgressEventSource; @@ -206,6 +195,8 @@ public StatusCenterItem( IsCancelable = _operationCancellationToken is not null; TotalItemsCount = itemsCount; TotalSize = totalSize; + IconBackgroundCircleBorderOpacity = 1; + AnimatedIconState = "NormalOff"; if (source is not null) Source = source; @@ -217,8 +208,7 @@ public StatusCenterItem( Values = new(); - // TODO: One-time fetch could cause an issue where the color won't be changed when user change accent color - var accentBrush = App.Current.Resources["AccentFillColorDefaultBrush"] as SolidColorBrush; + var accentBrush = App.Current.Resources["AppThemeFillColorAttentionBrush"] as SolidColorBrush; Series = new() { @@ -246,6 +236,29 @@ public StatusCenterItem( } }; + XAxes = new ICartesianAxis[] + { + new Axis + { + Padding = new Padding(0, 0), + Labels = new List(), + MaxLimit = 100, + + ShowSeparatorLines = false, + } + }; + + YAxes = new ICartesianAxis[] + { + new Axis + { + Padding = new Padding(0, 0), + Labels = new List(), + + ShowSeparatorLines = false, + } + }; + switch (FileSystemOperationReturnResult) { case ReturnResult.InProgress: @@ -253,6 +266,7 @@ public StatusCenterItem( IsSpeedAndProgressAvailable = canProvideProgress; IsInProgress = true; IsIndeterminateProgress = !canProvideProgress; + IconBackgroundCircleBorderOpacity = 0.1d; if (Operation is FileOperationType.Prepare) Header = "StatusCenter_PrepareInProgress".GetLocalizedResource(); @@ -313,22 +327,24 @@ private void ReportProgress(StatusCenterItemProgressModel value) Operation == FileOperationType.Extract || Operation == FileOperationType.Compressed) { - CurrentProcessedSizeText = string.Format( - "StatusCenter_ProcessedItems_Header".GetLocalizedResource(), - value.ProcessedItemsCount, - value.ItemsCount); + Message = + $"{string.Format( + "StatusCenter_ProcessedItems_Header".GetLocalizedResource(), + value.ProcessedItemsCount, + value.ItemsCount)} - {ProgressPercentage}%"; } else { - CurrentProcessedSizeText = string.Format( - "StatusCenter_ProcessedSize_Header".GetLocalizedResource(), - value.ProcessedSize.ToSizeString(), - value.TotalSize.ToSizeString()); + Message = + $"{string.Format( + "StatusCenter_ProcessedSize_Header".GetLocalizedResource(), + value.ProcessedSize.ToSizeString(), + value.TotalSize.ToSizeString())} - {ProgressPercentage}%"; } } - if (CurrentProcessingItemNameText != value.FileName) - CurrentProcessingItemNameText = value.FileName; + if (CurrentProcessingItemName != value.FileName) + CurrentProcessingItemName = value.FileName; } StatusCenterHelper.UpdateCardStrings(this, Source, Destination, value.ItemsCount); @@ -388,11 +404,11 @@ private void ReportProgress(StatusCenterItemProgressModel value) Values.Add(point); - if (!IsIndeterminateProgress) + if (IsIndeterminateProgress) + Message = "ProcessingItems".GetLocalizedResource(); + else Header = $"{Header} ({ProgressPercentage}%)"; - ProgressOverviewText = $"{value.ProcessedItemsCount}/{value.ItemsCount} {"items".GetLocalizedResource()}"; - _viewModel.NotifyChanges(); _viewModel.UpdateAverageProgressValue(); } @@ -406,7 +422,7 @@ public void ExecuteCancelCommand() IsCancelable = false; IsExpanded = false; IsSpeedAndProgressAvailable = false; - Header = $"{Header} ({"Canceling".GetLocalizedResource()})"; + Header = $"{"Canceling".GetLocalizedResource()} - {Header}"; } } } From c798203c403d30e816b9306e8faf7cd7332434f8 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sat, 30 Sep 2023 18:49:38 +0200 Subject: [PATCH 62/97] Remove unused code --- .../Operations/FileOperationsHelpers.cs | 75 ------------------- 1 file changed, 75 deletions(-) diff --git a/src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.cs b/src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.cs index 36b70e8714ee..81768464c294 100644 --- a/src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.cs +++ b/src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.cs @@ -937,74 +937,6 @@ private static void UpdateFileTagsDb(ShellFileOperations2.ShellFileOpEventArgs e public static void WaitForCompletion() => progressHandler?.WaitForCompletion(); - public static long GetFileOrFolderSize(string path, CancellationToken token) - { - var isDirectory = NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory); - return isDirectory ? (long)GetFolderSize(path, token) : GetFileSize(path); - } - - public static long GetFileSize(string path) - { - using var hFile = Kernel32.CreateFile( - path, - Kernel32.FileAccess.FILE_READ_ATTRIBUTES, - FileShare.Read, - null, - FileMode.Open, - 0, - null); - - if (!hFile.IsInvalid && Kernel32.GetFileSizeEx(hFile, out var size)) - return size; - - return 0; - } - - public static ulong GetFolderSize(string path, CancellationToken token) - { - ulong size = 0; - - using var hFile = Kernel32.FindFirstFileEx( - path + "\\*.*", - Kernel32.FINDEX_INFO_LEVELS.FindExInfoBasic, - out WIN32_FIND_DATA findData, - Kernel32.FINDEX_SEARCH_OPS.FindExSearchNameMatch, - IntPtr.Zero, - Kernel32.FIND_FIRST.FIND_FIRST_EX_LARGE_FETCH); - - if (!hFile.IsInvalid) - { - do - { - if ((findData.dwFileAttributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) - // Skip symbolic links and junctions - continue; - - if ((findData.dwFileAttributes & FileAttributes.Directory) != FileAttributes.Directory) - { - size += findData.FileSize; - } - else if (findData.cFileName != "." && findData.cFileName != "..") - { - var itemPath = Path.Combine(path, findData.cFileName); - - var folderSize = GetFolderSize(itemPath, token); - size += folderSize; - } - - if (token.IsCancellationRequested) - break; - } - while (Kernel32.FindNextFile(hFile, out findData)); - - return size; - } - else - { - return 0; - } - } - private class ProgressHandler : Disposable { private readonly ManualResetEvent operationsCompletedEvent; @@ -1062,13 +994,6 @@ public void UpdateOperation(string uid, double progress) } } - public OperationWithProgress GetOperation(string uid) - { - if (operations.TryGetValue(uid, out var op)) - return op; - throw new KeyNotFoundException(); - } - public bool CheckCanceled(string uid) { return !operations.TryGetValue(uid, out var op) || op.Canceled; From 80a58c11d2319712c7bf267c8e2eb8f7efb23ff5 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 1 Oct 2023 18:29:43 +0200 Subject: [PATCH 63/97] Show progress for extract --- .../Archives/Compress/CompressIntoZipAction.cs | 2 +- src/Files.App/Files.App.csproj | 2 +- .../Utils/Archives/DecompressHelper.cs | 9 +++++++-- .../Utils/StatusCenter/StatusCenterHelper.cs | 2 +- src/Files.App/nupkgs/SevenZipSharp.1.0.0.nupkg | Bin 71375 -> 0 bytes src/Files.App/nupkgs/SevenZipSharp.1.0.1.nupkg | Bin 0 -> 71618 bytes 6 files changed, 10 insertions(+), 5 deletions(-) delete mode 100644 src/Files.App/nupkgs/SevenZipSharp.1.0.0.nupkg create mode 100644 src/Files.App/nupkgs/SevenZipSharp.1.0.1.nupkg diff --git a/src/Files.App/Actions/Content/Archives/Compress/CompressIntoZipAction.cs b/src/Files.App/Actions/Content/Archives/Compress/CompressIntoZipAction.cs index c611d47bacd7..780cddf96788 100644 --- a/src/Files.App/Actions/Content/Archives/Compress/CompressIntoZipAction.cs +++ b/src/Files.App/Actions/Content/Archives/Compress/CompressIntoZipAction.cs @@ -23,7 +23,7 @@ public override Task ExecuteAsync() sources, directory, fileName, - fileFormat: ArchiveFormats.SevenZip); + fileFormat: ArchiveFormats.Zip); return CompressHelper.CompressArchiveAsync(creator); } diff --git a/src/Files.App/Files.App.csproj b/src/Files.App/Files.App.csproj index f3eda3229fcc..ac8c459c33a9 100644 --- a/src/Files.App/Files.App.csproj +++ b/src/Files.App/Files.App.csproj @@ -83,7 +83,7 @@ - + diff --git a/src/Files.App/Utils/Archives/DecompressHelper.cs b/src/Files.App/Utils/Archives/DecompressHelper.cs index f39c9cb699cb..2e73138e3173 100644 --- a/src/Files.App/Utils/Archives/DecompressHelper.cs +++ b/src/Files.App/Utils/Archives/DecompressHelper.cs @@ -99,9 +99,14 @@ public static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFold entriesAmount); fsProgress.TotalSize = zipFile.ArchiveFileData.Select(x => (long)x.Size).Sum(); - fsProgress.Report(); + zipFile.Extracting += (s, e) => + { + if (fsProgress.TotalSize > 0) + fsProgress.Report((fsProgress.ProcessedSize + e.PercentDelta / 100.0 * e.BytesCount) / fsProgress.TotalSize * 100); + }; + foreach (var entry in fileEntries) { if (cancellationToken.IsCancellationRequested) // Check if canceled @@ -155,7 +160,7 @@ private static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFol destinationFolder.Path.CreateEnumerable(), ReturnResult.InProgress); - await FilesystemTasks.Wrap(() => + await FilesystemTasks.Wrap(() => ExtractArchive(archive, destinationFolder, password, banner.ProgressEventSource, extractCancellation.Token)); _statusCenterViewModel.RemoveItem(banner); diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs index 411c585be66a..e30c6b818a15 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs @@ -376,7 +376,7 @@ public static StatusCenterItem AddCard_Decompress( FileOperationType.Extract, source, destination, - false, + true, itemsCount, totalSize, new CancellationTokenSource()); diff --git a/src/Files.App/nupkgs/SevenZipSharp.1.0.0.nupkg b/src/Files.App/nupkgs/SevenZipSharp.1.0.0.nupkg deleted file mode 100644 index ced7d4de30f61ac4ed407aed2871617cdc0133b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71375 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℞pl3GzOd>w~-wW0vyf5Dw$ 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

7$yLeKP?Scqe(QX0DWvK1#j>S?X{lo;ruO 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 literal 0 HcmV?d00001 From 19d3247040c3c4474c0fa83177db3657d5acc64b Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 1 Oct 2023 18:30:45 +0200 Subject: [PATCH 64/97] Fix graph not updating each percentage --- .../StatusCenterItemProgressModel.cs | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs index bfbd560609be..fa1d76d23192 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs @@ -20,7 +20,7 @@ public class StatusCenterItemProgressModel : INotifyPropertyChanged private readonly ConcurrentDictionary _dirtyTracker; - private readonly IntervalSampler _sampler; + private readonly IntervalSampler _sampler, _sampler2; private bool _criticalReport; @@ -121,6 +121,7 @@ public StatusCenterItemProgressModel(IProgress? p // Initialize _progress = progress; _sampler = new(samplerInterval); + _sampler2 = new(samplerInterval); _dirtyTracker = new(); EnumerationCompleted = enumerationCompleted; Status = status; @@ -168,34 +169,36 @@ TotalSize is not 0 || if (_Status is FileSystemStatusCode.Success) CompletedTime = DateTimeOffset.Now; - if (_criticalReport || _sampler.CheckNow()) + if (percentage is not null && Percentage != percentage) { - _criticalReport = false; - foreach (var propertyName in _dirtyTracker.Keys) - { - if (_dirtyTracker[propertyName]) - { - _dirtyTracker[propertyName] = false; - PropertyChanged?.Invoke(this, new(propertyName)); - } - } + SetProcessedSize((long)(TotalSize * percentage / 100)); - if (percentage is not null && Percentage != percentage) + if (_sampler2.CheckNow()) { - SetProcessedSize((long)(TotalSize * percentage / 100)); - ProcessingSizeSpeed = (ProcessedSize - _previousProcessedSize) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; - ProcessingItemsCountSpeed = (ProcessedItemsCount - _previousProcessedItemsCount) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds; - PropertyChanged?.Invoke(this, new(nameof(ProcessingSizeSpeed))); - PropertyChanged?.Invoke(this, new(nameof(ProcessingItemsCountSpeed))); + _dirtyTracker[nameof(ProcessingSizeSpeed)] = true; + _dirtyTracker[nameof(ProcessingItemsCountSpeed)] = true; _previousReportTime = DateTimeOffset.Now; _previousProcessedSize = ProcessedSize; _previousProcessedItemsCount = ProcessedItemsCount; + } - Percentage = percentage; + Percentage = percentage; + } + + if (_criticalReport || _sampler.CheckNow()) + { + _criticalReport = false; + foreach (var propertyName in _dirtyTracker.Keys) + { + if (_dirtyTracker[propertyName]) + { + _dirtyTracker[propertyName] = false; + PropertyChanged?.Invoke(this, new(propertyName)); + } } _progress?.Report(this); From bd43685a10a6c0ce173564827a21e0bfa58b21a0 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 1 Oct 2023 21:24:06 +0200 Subject: [PATCH 65/97] Show progress for compress --- .../Utils/Archives/CompressArchiveModel.cs | 55 +++++++++---------- .../Utils/StatusCenter/StatusCenterHelper.cs | 2 +- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/Files.App/Utils/Archives/CompressArchiveModel.cs b/src/Files.App/Utils/Archives/CompressArchiveModel.cs index 1a53179d527e..1987a47d4a97 100644 --- a/src/Files.App/Utils/Archives/CompressArchiveModel.cs +++ b/src/Files.App/Utils/Archives/CompressArchiveModel.cs @@ -1,6 +1,7 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. +using Files.App.Utils.Storage.Operations; using Microsoft.Extensions.Logging; using SevenZip; using System.IO; @@ -12,19 +13,10 @@ namespace Files.App.Utils.Archives ///

public class CompressArchiveModel : ICompressArchiveModel { - /// - /// Represents the total number of items to be processed. - /// - /// - /// It is used to calculate a weighted progress with this formula: - /// Progress = [OldProgress + (ProgressDelta / ItemsAmount)] - /// - private int _itemsAmount = 1; - - private int _processedItems = 0; - private StatusCenterItemProgressModel _fileSystemProgress; + private FileSizeCalculator _sizeCalculator; + private string ArchiveExtension => FileFormat switch { ArchiveFormats.Zip => ".zip", @@ -80,9 +72,8 @@ public IProgress Progress _fileSystemProgress = new( Progress, - true, - FileSystemStatusCode.InProgress, - Sources is null ? 0 : Sources.Count()); + false, + FileSystemStatusCode.InProgress); _fileSystemProgress.Report(0); } @@ -157,15 +148,25 @@ public async Task RunCreationAsync() }; compressor.Compressing += Compressor_Compressing; - compressor.CompressionFinished += Compressor_CompressionFinished; compressor.FileCompressionStarted += Compressor_FileCompressionStarted; + compressor.FileCompressionFinished += Compressor_FileCompressionFinished; + + var cts = new CancellationTokenSource(); try { var files = sources.Where(File.Exists).ToArray(); var directories = sources.Where(SystemIO.Directory.Exists); - _itemsAmount = files.Length + directories.Count(); + _sizeCalculator = new FileSizeCalculator(files.Concat(directories).ToArray()); + var sizeTask = _sizeCalculator.ComputeSizeAsync(cts.Token); + _ = sizeTask.ContinueWith(_ => + { + _fileSystemProgress.TotalSize = _sizeCalculator.Size; + _fileSystemProgress.ItemsCount = _sizeCalculator.ItemsCount; + _fileSystemProgress.EnumerationCompleted = true; + _fileSystemProgress.Report(); + }); foreach (string directory in directories) { @@ -182,6 +183,8 @@ public async Task RunCreationAsync() await compressor.CompressFilesEncryptedAsync(ArchivePath, Password, files); } + cts.Cancel(); + return true; } catch (Exception ex) @@ -189,33 +192,29 @@ public async Task RunCreationAsync() var logger = Ioc.Default.GetRequiredService>(); logger?.LogWarning(ex, $"Error compressing folder: {ArchivePath}"); + cts.Cancel(); + return false; } } private void Compressor_FileCompressionStarted(object? sender, FileNameEventArgs e) { + _sizeCalculator.ForceComputeFileSize(e.FilePath); _fileSystemProgress.FileName = e.FileName; _fileSystemProgress.Report(); } - private void Compressor_CompressionFinished(object? sender, EventArgs e) + private void Compressor_FileCompressionFinished(object? sender, EventArgs e) { - if (++_processedItems == _itemsAmount) - { - _fileSystemProgress.ReportStatus(FileSystemStatusCode.Success); - } - else - { - _fileSystemProgress.Report(_processedItems * 100.0 / _itemsAmount); - } + _fileSystemProgress.AddProcessedItemsCount(1); + _fileSystemProgress.Report(); } private void Compressor_Compressing(object? _, ProgressEventArgs e) { - // TODO: edit lib to get total/current bytes - - _fileSystemProgress.Report((double)e.PercentDelta / _itemsAmount); + if (_fileSystemProgress.TotalSize > 0) + _fileSystemProgress.Report((_fileSystemProgress.ProcessedSize + e.PercentDelta / 100.0 * e.BytesCount) / _fileSystemProgress.TotalSize * 100); } } } diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs index e30c6b818a15..49a1f26bab8e 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs @@ -309,7 +309,7 @@ public static StatusCenterItem AddCard_Compress( FileOperationType.Compressed, source, destination, - false, + true, itemsCount, totalSize, new CancellationTokenSource()); From 70b5eedaaca16f8f1bacec0da8bb37aa638800b4 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 1 Oct 2023 22:14:37 +0200 Subject: [PATCH 66/97] Set icon for compressing --- .../Converters/StatusCenterStateToStateIconConverter.cs | 1 + src/Files.App/Utils/StatusCenter/StatusCenterItem.cs | 1 + src/Files.Core/Data/Enums/StatusCenterItemIconKind.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs b/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs index 28fc1dd792bb..c8a4b6bfacfe 100644 --- a/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs +++ b/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs @@ -22,6 +22,7 @@ class StatusCenterStateToStateIconConverter : IValueConverter StatusCenterItemIconKind.Delete => Application.Current.Resources["App.Theme.PathIcon.ActionDelete"] as string, StatusCenterItemIconKind.Recycle => Application.Current.Resources["App.Theme.PathIcon.ActionDelete"] as string, StatusCenterItemIconKind.Extract => Application.Current.Resources["App.Theme.PathIcon.ActionCopy"] as string, + StatusCenterItemIconKind.Compress => Application.Current.Resources["App.Theme.PathIcon.ActionCopy"] as string, StatusCenterItemIconKind.Successful => Application.Current.Resources["App.Theme.PathIcon.ActionSuccess"] as string, StatusCenterItemIconKind.Error => Application.Current.Resources["App.Theme.PathIcon.ActionInfo"] as string, _ => "" diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs index 619a8cb73e98..53ff771dd579 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs @@ -280,6 +280,7 @@ public StatusCenterItem( FileOperationType.Move => StatusCenterItemIconKind.Move, FileOperationType.Delete => StatusCenterItemIconKind.Delete, FileOperationType.Recycle => StatusCenterItemIconKind.Recycle, + FileOperationType.Compressed => StatusCenterItemIconKind.Compress, _ => StatusCenterItemIconKind.Delete, }; diff --git a/src/Files.Core/Data/Enums/StatusCenterItemIconKind.cs b/src/Files.Core/Data/Enums/StatusCenterItemIconKind.cs index f8a1b625f6d8..e018de6eec0a 100644 --- a/src/Files.Core/Data/Enums/StatusCenterItemIconKind.cs +++ b/src/Files.Core/Data/Enums/StatusCenterItemIconKind.cs @@ -13,6 +13,7 @@ public enum StatusCenterItemIconKind Delete, Recycle, Extract, + Compress, Successful, Error, } From 5a5c2a02dbb5170051928c84f0a3b7aeb4c38274 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:27:13 +0900 Subject: [PATCH 67/97] Update --- src/Files.App/Strings/en-US/Resources.resw | 20 +++++++++---------- .../Utils/Archives/CompressHelper.cs | 9 ++++++--- .../Utils/StatusCenter/StatusCenterHelper.cs | 10 +++++----- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 0cc9687c377c..8dd148bcd090 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -3381,7 +3381,7 @@ Files can't initialize this directory as a Git repository. - Canceled compressing "{0}" items + Canceled compressing {0} items Shown in StatusCenter card item handling compress operation. @@ -3429,19 +3429,19 @@ Shown in StatusCenter card item handling decompress operation. - Canceled deleting {0} items + Canceled deleting {0} items permanently Shown in StatusCenter card item handling deleting operation. - Permanently deleted {0} items + Permanently deleted {0} items permanently Shown in StatusCenter card item handling deleting operation. - Failed to delete items + Failed to delete items permanently Shown in StatusCenter card item handling deleting operation. - Parmanently deleting {0} items + Parmanently deleting {0} items permanently Shown in StatusCenter card item handling deleting operation. @@ -3477,20 +3477,20 @@ Shown in StatusCenter card item handling move operation. - Preparing operation... + Preparing the operation... Shown in StatusCenter card item initializing an operation. - Canceled recycling {0} items + Canceled deleting all items permanently from the Recycle Bin - Successfully recycled {0} items + Successfully delete all items permanently from the Recycle Bin - Emptying the Recycle Bin + Deleting all items permanently from the Recycle Bin - Failed to empty the Rrecycle Bin + Failed to delete all items permanently from the Recycle Bin Processing items... diff --git a/src/Files.App/Utils/Archives/CompressHelper.cs b/src/Files.App/Utils/Archives/CompressHelper.cs index 5b5b3db8f904..0e1e74844428 100644 --- a/src/Files.App/Utils/Archives/CompressHelper.cs +++ b/src/Files.App/Utils/Archives/CompressHelper.cs @@ -72,7 +72,8 @@ public static async Task CompressArchiveAsync(ICompressArchiveModel creator) var banner = StatusCenterHelper.AddCard_Compress( creator.Sources, archivePath.CreateEnumerable(), - ReturnResult.InProgress); + ReturnResult.InProgress, + creator.Sources.Count()); creator.Progress = banner.ProgressEventSource; @@ -85,7 +86,8 @@ public static async Task CompressArchiveAsync(ICompressArchiveModel creator) StatusCenterHelper.AddCard_Compress( creator.Sources, archivePath.CreateEnumerable(), - ReturnResult.Success); + ReturnResult.Success, + creator.Sources.Count()); } else { @@ -94,7 +96,8 @@ public static async Task CompressArchiveAsync(ICompressArchiveModel creator) StatusCenterHelper.AddCard_Compress( creator.Sources, archivePath.CreateEnumerable(), - ReturnResult.Failed); + ReturnResult.Failed, + creator.Sources.Count()); } } } diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs index 411c585be66a..0007fb35b71b 100644 --- a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs +++ b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs @@ -559,11 +559,11 @@ public static void UpdateCardStrings(StatusCenterItem card, IEnumerable? case FileOperationType.Compressed: card.Header = card.FileSystemOperationReturnResult switch { - ReturnResult.Cancelled => string.Format(card.HeaderStringResource.GetLocalizedResource(), fileName), - ReturnResult.Success => string.Format(card.HeaderStringResource.GetLocalizedResource(), fileName), - ReturnResult.Failed => string.Format(card.HeaderStringResource.GetLocalizedResource(), fileName), - ReturnResult.InProgress => string.Format(card.HeaderStringResource.GetLocalizedResource(), fileName), - _ => string.Format(card.HeaderStringResource.GetLocalizedResource(), fileName), + ReturnResult.Cancelled => string.Format(card.HeaderStringResource.GetLocalizedResource(), totalItemCount), + ReturnResult.Success => string.Format(card.HeaderStringResource.GetLocalizedResource(), totalItemCount), + ReturnResult.Failed => string.Format(card.HeaderStringResource.GetLocalizedResource(), totalItemCount), + ReturnResult.InProgress => string.Format(card.HeaderStringResource.GetLocalizedResource(), totalItemCount), + _ => string.Format(card.HeaderStringResource.GetLocalizedResource(), totalItemCount), }; break; } From 1c8910b4dccc17e7cf7be9a8cb442038618f5dbd Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 2 Oct 2023 22:27:47 +0900 Subject: [PATCH 68/97] Update --- src/Files.App/Strings/en-US/Resources.resw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 8dd148bcd090..93ee1dead08f 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -3433,7 +3433,7 @@ Shown in StatusCenter card item handling deleting operation. - Permanently deleted {0} items permanently + Permanently deleted {0} items Shown in StatusCenter card item handling deleting operation. @@ -3441,7 +3441,7 @@ Shown in StatusCenter card item handling deleting operation. - Parmanently deleting {0} items permanently + Parmanently deleting {0} items Shown in StatusCenter card item handling deleting operation. From 303962fcb6e5a3721d4b3b0c458c312b30d05bcf Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Thu, 5 Oct 2023 22:07:28 +0900 Subject: [PATCH 69/97] Fix icons --- .../StatusCenterStateToStateIconConverter.cs | 16 +++++++------- .../ResourceDictionaries/PathIcons.xaml | 14 ++++++------ src/Files.App/Strings/en-US/Resources.resw | 22 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs b/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs index c8a4b6bfacfe..91e597470a0f 100644 --- a/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs +++ b/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs @@ -17,14 +17,14 @@ class StatusCenterStateToStateIconConverter : IValueConverter { var pathMarkup = state switch { - StatusCenterItemIconKind.Copy => Application.Current.Resources["App.Theme.PathIcon.ActionCopy"] as string, - StatusCenterItemIconKind.Move => Application.Current.Resources["App.Theme.PathIcon.ActionMove"] as string, - StatusCenterItemIconKind.Delete => Application.Current.Resources["App.Theme.PathIcon.ActionDelete"] as string, - StatusCenterItemIconKind.Recycle => Application.Current.Resources["App.Theme.PathIcon.ActionDelete"] as string, - StatusCenterItemIconKind.Extract => Application.Current.Resources["App.Theme.PathIcon.ActionCopy"] as string, - StatusCenterItemIconKind.Compress => Application.Current.Resources["App.Theme.PathIcon.ActionCopy"] as string, - StatusCenterItemIconKind.Successful => Application.Current.Resources["App.Theme.PathIcon.ActionSuccess"] as string, - StatusCenterItemIconKind.Error => Application.Current.Resources["App.Theme.PathIcon.ActionInfo"] as string, + StatusCenterItemIconKind.Copy => Application.Current.Resources["AppThemePathIconActionCopy"] as string, + StatusCenterItemIconKind.Move => Application.Current.Resources["AppThemePathIconActionMove"] as string, + StatusCenterItemIconKind.Delete => Application.Current.Resources["AppThemePathIconActionDelete"] as string, + StatusCenterItemIconKind.Recycle => Application.Current.Resources["AppThemePathIconActionDelete"] as string, + StatusCenterItemIconKind.Extract => Application.Current.Resources["AppThemePathIconActionArchive"] as string, + StatusCenterItemIconKind.Compress => Application.Current.Resources["AppThemePathIconActionArchive"] as string, + StatusCenterItemIconKind.Successful => Application.Current.Resources["AppThemePathIconActionSuccess"] as string, + StatusCenterItemIconKind.Error => Application.Current.Resources["AppThemePathIconActionInfo"] as string, _ => "" }; diff --git a/src/Files.App/ResourceDictionaries/PathIcons.xaml b/src/Files.App/ResourceDictionaries/PathIcons.xaml index 204ff2d321f4..9e0704cea003 100644 --- a/src/Files.App/ResourceDictionaries/PathIcons.xaml +++ b/src/Files.App/ResourceDictionaries/PathIcons.xaml @@ -103,19 +103,19 @@ M15.9918 3.0252C15.9902 1.9269 15.1032 1.03539 14.0049 1.02819L9.11457 0.996137C8.58049 0.992636 8.06719 1.2029 7.68905 1.58008L1.00751 8.24461C0.224764 9.02537 0.223956 10.2931 1.00571 11.0748L5.9541 16.0232C6.73515 16.8043 8.00148 16.8043 8.78253 16.0232L15.4133 9.39243C15.7891 9.01663 15.9999 8.50672 15.9991 7.97527L15.9918 3.0252ZM11.9853 5.99203C11.4331 5.99203 10.9853 5.54432 10.9853 4.99203C10.9853 4.43975 11.4331 3.99203 11.9853 3.99203C12.5376 3.99203 12.9853 4.43975 12.9853 4.99203C12.9853 5.54432 12.5376 5.99203 11.9853 5.99203Z - M0 4.5C0 3.88021 0.117188 3.29688 0.351562 2.75C0.591146 2.20312 0.914062 1.72656 1.32031 1.32031C1.72656 0.914062 2.20312 0.59375 2.75 0.359375C3.29688 0.119792 3.88021 0 4.5 0C4.91146 0 5.30729 0.0546875 5.6875 0.164062C6.07292 0.268229 6.43229 0.419271 6.76562 0.617188C7.09896 0.809896 7.40365 1.04427 7.67969 1.32031C7.95573 1.59635 8.1901 1.90104 8.38281 2.23438C8.58073 2.56771 8.73177 2.92708 8.83594 3.3125C8.94531 3.69271 9 4.08854 9 4.5C9 5.11979 8.88021 5.70312 8.64062 6.25C8.40625 6.79688 8.08594 7.27344 7.67969 7.67969C7.27344 8.08594 6.79688 8.40885 6.25 8.64844C5.70312 8.88281 5.11979 9 4.5 9C3.875 9 3.28906 8.88281 2.74219 8.64844C2.19531 8.41406 1.71875 8.09375 1.3125 7.6875C0.90625 7.28125 0.585938 6.80469 0.351562 6.25781C0.117188 5.71094 0 5.125 0 4.5ZM8.45312 12C8.17188 12 7.89844 11.9505 7.63281 11.8516C7.3724 11.7474 7.13281 11.6094 6.91406 11.4375C6.70052 11.2604 6.51823 11.0521 6.36719 10.8125C6.21615 10.5729 6.11198 10.3151 6.05469 10.0391C6.3776 9.94531 6.69531 9.82292 7.00781 9.67188C7.02865 9.85938 7.08073 10.0339 7.16406 10.1953C7.2526 10.3568 7.36458 10.4974 7.5 10.6172C7.63542 10.737 7.78906 10.8307 7.96094 10.8984C8.13281 10.9661 8.3125 11 8.5 11H12.5C12.7031 11 12.8958 10.9609 13.0781 10.8828C13.2604 10.8047 13.4193 10.6979 13.5547 10.5625C13.6953 10.4219 13.8047 10.2604 13.8828 10.0781C13.9609 9.89583 14 9.70312 14 9.5V3.5C14 3.29688 13.9609 3.10417 13.8828 2.92188C13.8047 2.73958 13.6953 2.58073 13.5547 2.44531C13.4193 2.30469 13.2604 2.19531 13.0781 2.11719C12.8958 2.03906 12.7031 2 12.5 2H9.67969C9.59115 1.82292 9.49479 1.65104 9.39062 1.48438C9.29167 1.31771 9.18229 1.15625 9.0625 1H12.5547C12.8828 1 13.1953 1.06771 13.4922 1.20312C13.7891 1.33333 14.0469 1.51042 14.2656 1.73438C14.4896 1.95312 14.6667 2.21094 14.7969 2.50781C14.9323 2.80469 15 3.11719 15 3.44531V9.55469C15 9.88281 14.9323 10.1953 14.7969 10.4922C14.6667 10.7891 14.4896 11.0495 14.2656 11.2734C14.0469 11.4922 13.7891 11.6693 13.4922 11.8047C13.1953 11.9349 12.8828 12 12.5547 12H8.45312ZM4.5 7.0625C4.65625 7.0625 4.78906 7.00781 4.89844 6.89844L6.89844 4.89844C7.00781 4.78906 7.0625 4.65625 7.0625 4.5C7.0625 4.34375 7.00781 4.21094 6.89844 4.10156L4.89844 2.10156C4.78906 1.99219 4.65625 1.9375 4.5 1.9375C4.34375 1.9375 4.21094 1.99219 4.10156 2.10156C3.99219 2.21094 3.9375 2.34375 3.9375 2.5C3.9375 2.65625 3.99219 2.78906 4.10156 2.89844L5.20312 4H2.5C2.36458 4 2.2474 4.04948 2.14844 4.14844C2.04948 4.2474 2 4.36458 2 4.5C2 4.63542 2.04948 4.7526 2.14844 4.85156C2.2474 4.95052 2.36458 5 2.5 5H5.20312L4.10156 6.10156C3.99219 6.21094 3.9375 6.34375 3.9375 6.5C3.9375 6.65625 3.99219 6.78906 4.10156 6.89844C4.21094 7.00781 4.34375 7.0625 4.5 7.0625ZM3.44531 15C3.11719 15 2.80469 14.9349 2.50781 14.8047C2.21094 14.6693 1.95052 14.4922 1.72656 14.2734C1.50781 14.0495 1.33073 13.7891 1.19531 13.4922C1.0651 13.1953 1 12.8828 1 12.5547V9.0625C1.15625 9.18229 1.31771 9.29427 1.48438 9.39844C1.65104 9.4974 1.82292 9.59115 2 9.67969V12.5C2 12.7031 2.03906 12.8958 2.11719 13.0781C2.19531 13.2604 2.30208 13.4219 2.4375 13.5625C2.57812 13.6979 2.73958 13.8047 2.92188 13.8828C3.10417 13.9609 3.29688 14 3.5 14H7.5C7.66146 14 7.8151 13.9766 7.96094 13.9297C8.10677 13.8828 8.24219 13.8151 8.36719 13.7266C8.49219 13.638 8.60156 13.5339 8.69531 13.4141C8.78906 13.2891 8.86198 13.151 8.91406 13H9.95312C9.89583 13.2865 9.78906 13.5521 9.63281 13.7969C9.48177 14.0417 9.29688 14.2526 9.07812 14.4297C8.85938 14.6068 8.61458 14.7474 8.34375 14.8516C8.07292 14.9505 7.79167 15 7.5 15H3.44531Z + M0 4.5C0 3.88021 0.117188 3.29688 0.351562 2.75C0.591146 2.20312 0.914062 1.72656 1.32031 1.32031C1.72656 0.914062 2.20312 0.59375 2.75 0.359375C3.29688 0.119792 3.88021 0 4.5 0C4.91146 0 5.30729 0.0546875 5.6875 0.164062C6.07292 0.268229 6.43229 0.419271 6.76562 0.617188C7.09896 0.809896 7.40365 1.04427 7.67969 1.32031C7.95573 1.59635 8.1901 1.90104 8.38281 2.23438C8.58073 2.56771 8.73177 2.92708 8.83594 3.3125C8.94531 3.69271 9 4.08854 9 4.5C9 5.11979 8.88021 5.70312 8.64062 6.25C8.40625 6.79688 8.08594 7.27344 7.67969 7.67969C7.27344 8.08594 6.79688 8.40885 6.25 8.64844C5.70312 8.88281 5.11979 9 4.5 9C3.875 9 3.28906 8.88281 2.74219 8.64844C2.19531 8.41406 1.71875 8.09375 1.3125 7.6875C0.90625 7.28125 0.585938 6.80469 0.351562 6.25781C0.117188 5.71094 0 5.125 0 4.5ZM8.45312 12C8.17188 12 7.89844 11.9505 7.63281 11.8516C7.3724 11.7474 7.13281 11.6094 6.91406 11.4375C6.70052 11.2604 6.51823 11.0521 6.36719 10.8125C6.21615 10.5729 6.11198 10.3151 6.05469 10.0391C6.3776 9.94531 6.69531 9.82292 7.00781 9.67188C7.02865 9.85938 7.08073 10.0339 7.16406 10.1953C7.2526 10.3568 7.36458 10.4974 7.5 10.6172C7.63542 10.737 7.78906 10.8307 7.96094 10.8984C8.13281 10.9661 8.3125 11 8.5 11H12.5C12.7031 11 12.8958 10.9609 13.0781 10.8828C13.2604 10.8047 13.4193 10.6979 13.5547 10.5625C13.6953 10.4219 13.8047 10.2604 13.8828 10.0781C13.9609 9.89583 14 9.70312 14 9.5V3.5C14 3.29688 13.9609 3.10417 13.8828 2.92188C13.8047 2.73958 13.6953 2.58073 13.5547 2.44531C13.4193 2.30469 13.2604 2.19531 13.0781 2.11719C12.8958 2.03906 12.7031 2 12.5 2H9.67969C9.59115 1.82292 9.49479 1.65104 9.39062 1.48438C9.29167 1.31771 9.18229 1.15625 9.0625 1H12.5547C12.8828 1 13.1953 1.06771 13.4922 1.20312C13.7891 1.33333 14.0469 1.51042 14.2656 1.73438C14.4896 1.95312 14.6667 2.21094 14.7969 2.50781C14.9323 2.80469 15 3.11719 15 3.44531V9.55469C15 9.88281 14.9323 10.1953 14.7969 10.4922C14.6667 10.7891 14.4896 11.0495 14.2656 11.2734C14.0469 11.4922 13.7891 11.6693 13.4922 11.8047C13.1953 11.9349 12.8828 12 12.5547 12H8.45312ZM4.5 7.0625C4.65625 7.0625 4.78906 7.00781 4.89844 6.89844L6.89844 4.89844C7.00781 4.78906 7.0625 4.65625 7.0625 4.5C7.0625 4.34375 7.00781 4.21094 6.89844 4.10156L4.89844 2.10156C4.78906 1.99219 4.65625 1.9375 4.5 1.9375C4.34375 1.9375 4.21094 1.99219 4.10156 2.10156C3.99219 2.21094 3.9375 2.34375 3.9375 2.5C3.9375 2.65625 3.99219 2.78906 4.10156 2.89844L5.20312 4H2.5C2.36458 4 2.2474 4.04948 2.14844 4.14844C2.04948 4.2474 2 4.36458 2 4.5C2 4.63542 2.04948 4.7526 2.14844 4.85156C2.2474 4.95052 2.36458 5 2.5 5H5.20312L4.10156 6.10156C3.99219 6.21094 3.9375 6.34375 3.9375 6.5C3.9375 6.65625 3.99219 6.78906 4.10156 6.89844C4.21094 7.00781 4.34375 7.0625 4.5 7.0625ZM3.44531 15C3.11719 15 2.80469 14.9349 2.50781 14.8047C2.21094 14.6693 1.95052 14.4922 1.72656 14.2734C1.50781 14.0495 1.33073 13.7891 1.19531 13.4922C1.0651 13.1953 1 12.8828 1 12.5547V9.0625C1.15625 9.18229 1.31771 9.29427 1.48438 9.39844C1.65104 9.4974 1.82292 9.59115 2 9.67969V12.5C2 12.7031 2.03906 12.8958 2.11719 13.0781C2.19531 13.2604 2.30208 13.4219 2.4375 13.5625C2.57812 13.6979 2.73958 13.8047 2.92188 13.8828C3.10417 13.9609 3.29688 14 3.5 14H7.5C7.66146 14 7.8151 13.9766 7.96094 13.9297C8.10677 13.8828 8.24219 13.8151 8.36719 13.7266C8.49219 13.638 8.60156 13.5339 8.69531 13.4141C8.78906 13.2891 8.86198 13.151 8.91406 13H9.95312C9.89583 13.2865 9.78906 13.5521 9.63281 13.7969C9.48177 14.0417 9.29688 14.2526 9.07812 14.4297C8.85938 14.6068 8.61458 14.7474 8.34375 14.8516C8.07292 14.9505 7.79167 15 7.5 15H3.44531Z - M2.44531 15C2.11719 15 1.80469 14.9349 1.50781 14.8047C1.21094 14.6693 0.950521 14.4922 0.726562 14.2734C0.507812 14.0495 0.330729 13.7891 0.195312 13.4922C0.0651042 13.1953 0 12.8828 0 12.5547V3.44531C0 3.11719 0.0651042 2.80469 0.195312 2.50781C0.330729 2.21094 0.507812 1.95312 0.726562 1.73438C0.950521 1.51042 1.21094 1.33333 1.50781 1.20312C1.80469 1.06771 2.11719 1 2.44531 1H5.5C5.85938 1 6.16927 1.05729 6.42969 1.17188C6.6901 1.28125 6.92188 1.42969 7.125 1.61719C7.33333 1.80469 7.52604 2.01823 7.70312 2.25781C7.88021 2.4974 8.0625 2.74479 8.25 3H13.5547C13.8828 3 14.1953 3.06771 14.4922 3.20312C14.7891 3.33333 15.0469 3.51042 15.2656 3.73438C15.4896 3.95312 15.6667 4.21094 15.7969 4.50781C15.9323 4.80469 16 5.11719 16 5.44531V7.92188C15.8542 7.73958 15.6979 7.5651 15.5312 7.39844C15.3646 7.23177 15.1875 7.07812 15 6.9375V5.5C15 5.29688 14.9609 5.10417 14.8828 4.92188C14.8047 4.73958 14.6953 4.58073 14.5547 4.44531C14.4193 4.30469 14.2604 4.19531 14.0781 4.11719C13.8958 4.03906 13.7031 4 13.5 4H8.14844C7.96615 4.11979 7.78906 4.23958 7.61719 4.35938C7.44531 4.47917 7.26823 4.58594 7.08594 4.67969C6.90885 4.77344 6.72135 4.85156 6.52344 4.91406C6.33073 4.97135 6.11979 5 5.89062 5H1V12.5C1 12.7031 1.03906 12.8958 1.11719 13.0781C1.19531 13.2604 1.30208 13.4219 1.4375 13.5625C1.57812 13.6979 1.73958 13.8047 1.92188 13.8828C2.10417 13.9609 2.29688 14 2.5 14H6.32031C6.40885 14.1771 6.5026 14.349 6.60156 14.5156C6.70573 14.6823 6.81771 14.8438 6.9375 15H2.44531ZM5.89062 4C6.03125 4 6.16146 3.98177 6.28125 3.94531C6.40104 3.90885 6.51823 3.86198 6.63281 3.80469C6.7474 3.74219 6.85677 3.67448 6.96094 3.60156C7.0651 3.52865 7.17188 3.45312 7.28125 3.375C7.16146 3.21354 7.04427 3.05208 6.92969 2.89062C6.8151 2.72396 6.6901 2.57552 6.55469 2.44531C6.41927 2.3151 6.26562 2.20833 6.09375 2.125C5.92708 2.04167 5.72917 2 5.5 2H2.5C2.29688 2 2.10417 2.03906 1.92188 2.11719C1.73958 2.19531 1.57812 2.30469 1.4375 2.44531C1.30208 2.58073 1.19531 2.73958 1.11719 2.92188C1.03906 3.10417 1 3.29688 1 3.5V4H5.89062ZM7 11.5C7 10.8802 7.11719 10.2969 7.35156 9.75C7.59115 9.20312 7.91406 8.72656 8.32031 8.32031C8.72656 7.91406 9.20312 7.59375 9.75 7.35938C10.2969 7.11979 10.8802 7 11.5 7C11.9115 7 12.3073 7.05469 12.6875 7.16406C13.0729 7.26823 13.4323 7.41927 13.7656 7.61719C14.099 7.8099 14.4036 8.04427 14.6797 8.32031C14.9557 8.59635 15.1901 8.90104 15.3828 9.23438C15.5807 9.56771 15.7318 9.92708 15.8359 10.3125C15.9453 10.6927 16 11.0885 16 11.5C16 12.1198 15.8802 12.7031 15.6406 13.25C15.4062 13.7969 15.0859 14.2734 14.6797 14.6797C14.2734 15.0859 13.7969 15.4089 13.25 15.6484C12.7031 15.8828 12.1198 16 11.5 16C10.875 16 10.2891 15.8828 9.74219 15.6484C9.19531 15.4141 8.71875 15.0938 8.3125 14.6875C7.90625 14.2812 7.58594 13.8047 7.35156 13.2578C7.11719 12.7109 7 12.125 7 11.5ZM11.5 14.0625C11.6562 14.0625 11.7891 14.0078 11.8984 13.8984L13.8984 11.8984C14.0078 11.7891 14.0625 11.6562 14.0625 11.5C14.0625 11.3438 14.0078 11.2109 13.8984 11.1016L11.8984 9.10156C11.7891 8.99219 11.6562 8.9375 11.5 8.9375C11.3438 8.9375 11.2109 8.99219 11.1016 9.10156C10.9922 9.21094 10.9375 9.34375 10.9375 9.5C10.9375 9.65625 10.9922 9.78906 11.1016 9.89844L12.2031 11H9.5C9.36458 11 9.2474 11.0495 9.14844 11.1484C9.04948 11.2474 9 11.3646 9 11.5C9 11.6354 9.04948 11.7526 9.14844 11.8516C9.2474 11.9505 9.36458 12 9.5 12H12.2031L11.1016 13.1016C10.9922 13.2109 10.9375 13.3438 10.9375 13.5C10.9375 13.6562 10.9922 13.7891 11.1016 13.8984C11.2109 14.0078 11.3438 14.0625 11.5 14.0625Z + M2.44531 15C2.11719 15 1.80469 14.9349 1.50781 14.8047C1.21094 14.6693 0.950521 14.4922 0.726562 14.2734C0.507812 14.0495 0.330729 13.7891 0.195312 13.4922C0.0651042 13.1953 0 12.8828 0 12.5547V3.44531C0 3.11719 0.0651042 2.80469 0.195312 2.50781C0.330729 2.21094 0.507812 1.95312 0.726562 1.73438C0.950521 1.51042 1.21094 1.33333 1.50781 1.20312C1.80469 1.06771 2.11719 1 2.44531 1H5.5C5.85938 1 6.16927 1.05729 6.42969 1.17188C6.6901 1.28125 6.92188 1.42969 7.125 1.61719C7.33333 1.80469 7.52604 2.01823 7.70312 2.25781C7.88021 2.4974 8.0625 2.74479 8.25 3H13.5547C13.8828 3 14.1953 3.06771 14.4922 3.20312C14.7891 3.33333 15.0469 3.51042 15.2656 3.73438C15.4896 3.95312 15.6667 4.21094 15.7969 4.50781C15.9323 4.80469 16 5.11719 16 5.44531V7.92188C15.8542 7.73958 15.6979 7.5651 15.5312 7.39844C15.3646 7.23177 15.1875 7.07812 15 6.9375V5.5C15 5.29688 14.9609 5.10417 14.8828 4.92188C14.8047 4.73958 14.6953 4.58073 14.5547 4.44531C14.4193 4.30469 14.2604 4.19531 14.0781 4.11719C13.8958 4.03906 13.7031 4 13.5 4H8.14844C7.96615 4.11979 7.78906 4.23958 7.61719 4.35938C7.44531 4.47917 7.26823 4.58594 7.08594 4.67969C6.90885 4.77344 6.72135 4.85156 6.52344 4.91406C6.33073 4.97135 6.11979 5 5.89062 5H1V12.5C1 12.7031 1.03906 12.8958 1.11719 13.0781C1.19531 13.2604 1.30208 13.4219 1.4375 13.5625C1.57812 13.6979 1.73958 13.8047 1.92188 13.8828C2.10417 13.9609 2.29688 14 2.5 14H6.32031C6.40885 14.1771 6.5026 14.349 6.60156 14.5156C6.70573 14.6823 6.81771 14.8438 6.9375 15H2.44531ZM5.89062 4C6.03125 4 6.16146 3.98177 6.28125 3.94531C6.40104 3.90885 6.51823 3.86198 6.63281 3.80469C6.7474 3.74219 6.85677 3.67448 6.96094 3.60156C7.0651 3.52865 7.17188 3.45312 7.28125 3.375C7.16146 3.21354 7.04427 3.05208 6.92969 2.89062C6.8151 2.72396 6.6901 2.57552 6.55469 2.44531C6.41927 2.3151 6.26562 2.20833 6.09375 2.125C5.92708 2.04167 5.72917 2 5.5 2H2.5C2.29688 2 2.10417 2.03906 1.92188 2.11719C1.73958 2.19531 1.57812 2.30469 1.4375 2.44531C1.30208 2.58073 1.19531 2.73958 1.11719 2.92188C1.03906 3.10417 1 3.29688 1 3.5V4H5.89062ZM7 11.5C7 10.8802 7.11719 10.2969 7.35156 9.75C7.59115 9.20312 7.91406 8.72656 8.32031 8.32031C8.72656 7.91406 9.20312 7.59375 9.75 7.35938C10.2969 7.11979 10.8802 7 11.5 7C11.9115 7 12.3073 7.05469 12.6875 7.16406C13.0729 7.26823 13.4323 7.41927 13.7656 7.61719C14.099 7.8099 14.4036 8.04427 14.6797 8.32031C14.9557 8.59635 15.1901 8.90104 15.3828 9.23438C15.5807 9.56771 15.7318 9.92708 15.8359 10.3125C15.9453 10.6927 16 11.0885 16 11.5C16 12.1198 15.8802 12.7031 15.6406 13.25C15.4062 13.7969 15.0859 14.2734 14.6797 14.6797C14.2734 15.0859 13.7969 15.4089 13.25 15.6484C12.7031 15.8828 12.1198 16 11.5 16C10.875 16 10.2891 15.8828 9.74219 15.6484C9.19531 15.4141 8.71875 15.0938 8.3125 14.6875C7.90625 14.2812 7.58594 13.8047 7.35156 13.2578C7.11719 12.7109 7 12.125 7 11.5ZM11.5 14.0625C11.6562 14.0625 11.7891 14.0078 11.8984 13.8984L13.8984 11.8984C14.0078 11.7891 14.0625 11.6562 14.0625 11.5C14.0625 11.3438 14.0078 11.2109 13.8984 11.1016L11.8984 9.10156C11.7891 8.99219 11.6562 8.9375 11.5 8.9375C11.3438 8.9375 11.2109 8.99219 11.1016 9.10156C10.9922 9.21094 10.9375 9.34375 10.9375 9.5C10.9375 9.65625 10.9922 9.78906 11.1016 9.89844L12.2031 11H9.5C9.36458 11 9.2474 11.0495 9.14844 11.1484C9.04948 11.2474 9 11.3646 9 11.5C9 11.6354 9.04948 11.7526 9.14844 11.8516C9.2474 11.9505 9.36458 12 9.5 12H12.2031L11.1016 13.1016C10.9922 13.2109 10.9375 13.3438 10.9375 13.5C10.9375 13.6562 10.9922 13.7891 11.1016 13.8984C11.2109 14.0078 11.3438 14.0625 11.5 14.0625Z - M16 12.5547C16 12.8828 15.9323 13.1953 15.7969 13.4922C15.6667 13.7891 15.4896 14.0495 15.2656 14.2734C15.0469 14.4922 14.7891 14.6693 14.4922 14.8047C14.1953 14.9349 13.8828 15 13.5547 15H2.44531C2.11719 15 1.80469 14.9349 1.50781 14.8047C1.21094 14.6693 0.950521 14.4922 0.726562 14.2734C0.507812 14.0495 0.330729 13.7891 0.195312 13.4922C0.0651042 13.1953 0 12.8828 0 12.5547V3.44531C0 3.11719 0.0651042 2.80469 0.195312 2.50781C0.330729 2.21094 0.507812 1.95312 0.726562 1.73438C0.950521 1.51042 1.21094 1.33333 1.50781 1.20312C1.80469 1.06771 2.11719 1 2.44531 1H5.5C5.90104 1 6.27344 1.08854 6.61719 1.26562C6.96615 1.4375 7.26042 1.68229 7.5 2L8.25 3H13.5547C13.8828 3 14.1953 3.06771 14.4922 3.20312C14.7891 3.33333 15.0469 3.51042 15.2656 3.73438C15.4896 3.95312 15.6667 4.21094 15.7969 4.50781C15.9323 4.80469 16 5.11719 16 5.44531V12.5547ZM1 4H5.89062C6.03125 4 6.16146 3.98177 6.28125 3.94531C6.40625 3.90885 6.52344 3.86198 6.63281 3.80469C6.74219 3.74219 6.84896 3.67448 6.95312 3.60156C7.0625 3.52865 7.17188 3.45312 7.28125 3.375C7.15625 3.20833 7.03646 3.04427 6.92188 2.88281C6.80729 2.71615 6.68229 2.56771 6.54688 2.4375C6.41667 2.30729 6.26562 2.20312 6.09375 2.125C5.92708 2.04167 5.72917 2 5.5 2H2.5C2.29688 2 2.10417 2.03906 1.92188 2.11719C1.73958 2.19531 1.57812 2.30469 1.4375 2.44531C1.30208 2.58073 1.19531 2.73958 1.11719 2.92188C1.03906 3.10417 1 3.29688 1 3.5V4ZM1 12.5C1 12.7031 1.03906 12.8958 1.11719 13.0781C1.19531 13.2604 1.30208 13.4219 1.4375 13.5625C1.57812 13.6979 1.73958 13.8047 1.92188 13.8828C2.10417 13.9609 2.29688 14 2.5 14H11V13C10.9583 13 10.9141 13.0026 10.8672 13.0078C10.8255 13.0078 10.7812 13.0078 10.7344 13.0078C10.6458 13.0078 10.5573 13.0026 10.4688 12.9922C10.3854 12.9818 10.3073 12.9609 10.2344 12.9297C10.1667 12.8932 10.1094 12.8411 10.0625 12.7734C10.0208 12.7057 10 12.6146 10 12.5C10 12.3854 10.0208 12.2969 10.0625 12.2344C10.1094 12.1667 10.1667 12.1172 10.2344 12.0859C10.3073 12.0495 10.3854 12.026 10.4688 12.0156C10.5573 12.0052 10.6458 12 10.7344 12H11V9C10.9583 9 10.9141 9.0026 10.8672 9.00781C10.8255 9.00781 10.7812 9.00781 10.7344 9.00781C10.6458 9.00781 10.5573 9.0026 10.4688 8.99219C10.3854 8.98177 10.3073 8.96094 10.2344 8.92969C10.1667 8.89323 10.1094 8.84115 10.0625 8.77344C10.0208 8.70573 10 8.61458 10 8.5C10 8.38542 10.0208 8.29688 10.0625 8.23438C10.1094 8.16667 10.1667 8.11719 10.2344 8.08594C10.3073 8.04948 10.3854 8.02604 10.4688 8.01562C10.5573 8.00521 10.6458 8 10.7344 8H11V7C10.8594 7 10.7266 6.97396 10.6016 6.92188C10.4818 6.86979 10.3776 6.79948 10.2891 6.71094C10.2005 6.6224 10.1302 6.51823 10.0781 6.39844C10.026 6.27344 10 6.14062 10 6V4H8.14844C7.96094 4.125 7.78125 4.2474 7.60938 4.36719C7.4375 4.48698 7.26302 4.59375 7.08594 4.6875C6.90885 4.78125 6.72396 4.85677 6.53125 4.91406C6.33854 4.97135 6.125 5 5.89062 5H1V12.5ZM12 6V4H11V6H12ZM15 5.5C15 5.29688 14.9609 5.10417 14.8828 4.92188C14.8047 4.73958 14.6953 4.58073 14.5547 4.44531C14.4193 4.30469 14.2604 4.19531 14.0781 4.11719C13.8958 4.03906 13.7031 4 13.5 4H13V6C13 6.14062 12.974 6.27083 12.9219 6.39062C12.8698 6.51042 12.7969 6.61719 12.7031 6.71094C12.6146 6.79948 12.5104 6.86979 12.3906 6.92188C12.2708 6.97396 12.1406 7 12 7V10H12.2656C12.3542 10 12.4401 10.0052 12.5234 10.0156C12.612 10.026 12.6901 10.0495 12.7578 10.0859C12.8307 10.1172 12.888 10.1667 12.9297 10.2344C12.9766 10.2969 13 10.3854 13 10.5C13 10.6146 12.9766 10.7057 12.9297 10.7734C12.888 10.8411 12.8307 10.8932 12.7578 10.9297C12.6901 10.9609 12.612 10.9818 12.5234 10.9922C12.4401 11.0026 12.3542 11.0078 12.2656 11.0078C12.2188 11.0078 12.1719 11.0078 12.125 11.0078C12.0833 11.0026 12.0417 11 12 11V14H13.5C13.7031 14 13.8958 13.9609 14.0781 13.8828C14.2604 13.8047 14.4193 13.6979 14.5547 13.5625C14.6953 13.4219 14.8047 13.2604 14.8828 13.0781C14.9609 12.8958 15 12.7031 15 12.5V5.5Z + M16 12.5547C16 12.8828 15.9323 13.1953 15.7969 13.4922C15.6667 13.7891 15.4896 14.0495 15.2656 14.2734C15.0469 14.4922 14.7891 14.6693 14.4922 14.8047C14.1953 14.9349 13.8828 15 13.5547 15H2.44531C2.11719 15 1.80469 14.9349 1.50781 14.8047C1.21094 14.6693 0.950521 14.4922 0.726562 14.2734C0.507812 14.0495 0.330729 13.7891 0.195312 13.4922C0.0651042 13.1953 0 12.8828 0 12.5547V3.44531C0 3.11719 0.0651042 2.80469 0.195312 2.50781C0.330729 2.21094 0.507812 1.95312 0.726562 1.73438C0.950521 1.51042 1.21094 1.33333 1.50781 1.20312C1.80469 1.06771 2.11719 1 2.44531 1H5.5C5.90104 1 6.27344 1.08854 6.61719 1.26562C6.96615 1.4375 7.26042 1.68229 7.5 2L8.25 3H13.5547C13.8828 3 14.1953 3.06771 14.4922 3.20312C14.7891 3.33333 15.0469 3.51042 15.2656 3.73438C15.4896 3.95312 15.6667 4.21094 15.7969 4.50781C15.9323 4.80469 16 5.11719 16 5.44531V12.5547ZM1 4H5.89062C6.03125 4 6.16146 3.98177 6.28125 3.94531C6.40625 3.90885 6.52344 3.86198 6.63281 3.80469C6.74219 3.74219 6.84896 3.67448 6.95312 3.60156C7.0625 3.52865 7.17188 3.45312 7.28125 3.375C7.15625 3.20833 7.03646 3.04427 6.92188 2.88281C6.80729 2.71615 6.68229 2.56771 6.54688 2.4375C6.41667 2.30729 6.26562 2.20312 6.09375 2.125C5.92708 2.04167 5.72917 2 5.5 2H2.5C2.29688 2 2.10417 2.03906 1.92188 2.11719C1.73958 2.19531 1.57812 2.30469 1.4375 2.44531C1.30208 2.58073 1.19531 2.73958 1.11719 2.92188C1.03906 3.10417 1 3.29688 1 3.5V4ZM1 12.5C1 12.7031 1.03906 12.8958 1.11719 13.0781C1.19531 13.2604 1.30208 13.4219 1.4375 13.5625C1.57812 13.6979 1.73958 13.8047 1.92188 13.8828C2.10417 13.9609 2.29688 14 2.5 14H11V13C10.9583 13 10.9141 13.0026 10.8672 13.0078C10.8255 13.0078 10.7812 13.0078 10.7344 13.0078C10.6458 13.0078 10.5573 13.0026 10.4688 12.9922C10.3854 12.9818 10.3073 12.9609 10.2344 12.9297C10.1667 12.8932 10.1094 12.8411 10.0625 12.7734C10.0208 12.7057 10 12.6146 10 12.5C10 12.3854 10.0208 12.2969 10.0625 12.2344C10.1094 12.1667 10.1667 12.1172 10.2344 12.0859C10.3073 12.0495 10.3854 12.026 10.4688 12.0156C10.5573 12.0052 10.6458 12 10.7344 12H11V9C10.9583 9 10.9141 9.0026 10.8672 9.00781C10.8255 9.00781 10.7812 9.00781 10.7344 9.00781C10.6458 9.00781 10.5573 9.0026 10.4688 8.99219C10.3854 8.98177 10.3073 8.96094 10.2344 8.92969C10.1667 8.89323 10.1094 8.84115 10.0625 8.77344C10.0208 8.70573 10 8.61458 10 8.5C10 8.38542 10.0208 8.29688 10.0625 8.23438C10.1094 8.16667 10.1667 8.11719 10.2344 8.08594C10.3073 8.04948 10.3854 8.02604 10.4688 8.01562C10.5573 8.00521 10.6458 8 10.7344 8H11V7C10.8594 7 10.7266 6.97396 10.6016 6.92188C10.4818 6.86979 10.3776 6.79948 10.2891 6.71094C10.2005 6.6224 10.1302 6.51823 10.0781 6.39844C10.026 6.27344 10 6.14062 10 6V4H8.14844C7.96094 4.125 7.78125 4.2474 7.60938 4.36719C7.4375 4.48698 7.26302 4.59375 7.08594 4.6875C6.90885 4.78125 6.72396 4.85677 6.53125 4.91406C6.33854 4.97135 6.125 5 5.89062 5H1V12.5ZM12 6V4H11V6H12ZM15 5.5C15 5.29688 14.9609 5.10417 14.8828 4.92188C14.8047 4.73958 14.6953 4.58073 14.5547 4.44531C14.4193 4.30469 14.2604 4.19531 14.0781 4.11719C13.8958 4.03906 13.7031 4 13.5 4H13V6C13 6.14062 12.974 6.27083 12.9219 6.39062C12.8698 6.51042 12.7969 6.61719 12.7031 6.71094C12.6146 6.79948 12.5104 6.86979 12.3906 6.92188C12.2708 6.97396 12.1406 7 12 7V10H12.2656C12.3542 10 12.4401 10.0052 12.5234 10.0156C12.612 10.026 12.6901 10.0495 12.7578 10.0859C12.8307 10.1172 12.888 10.1667 12.9297 10.2344C12.9766 10.2969 13 10.3854 13 10.5C13 10.6146 12.9766 10.7057 12.9297 10.7734C12.888 10.8411 12.8307 10.8932 12.7578 10.9297C12.6901 10.9609 12.612 10.9818 12.5234 10.9922C12.4401 11.0026 12.3542 11.0078 12.2656 11.0078C12.2188 11.0078 12.1719 11.0078 12.125 11.0078C12.0833 11.0026 12.0417 11 12 11V14H13.5C13.7031 14 13.8958 13.9609 14.0781 13.8828C14.2604 13.8047 14.4193 13.6979 14.5547 13.5625C14.6953 13.4219 14.8047 13.2604 14.8828 13.0781C14.9609 12.8958 15 12.7031 15 12.5V5.5Z - M7.02579 12.0266L13.7795 5.28064C14.0726 4.98791 14.0729 4.51304 13.7801 4.21998C13.4874 3.92691 13.0126 3.92664 12.7195 4.21936L6.49609 10.4356L4.28035 8.21968C3.98746 7.92678 3.51259 7.92677 3.21969 8.21965C2.92678 8.51253 2.92677 8.98741 3.21965 9.28031L5.96542 12.0262C6.25819 12.319 6.73284 12.3192 7.02579 12.0266Z + M7.02579 12.0266L13.7795 5.28064C14.0726 4.98791 14.0729 4.51304 13.7801 4.21998C13.4874 3.92691 13.0126 3.92664 12.7195 4.21936L6.49609 10.4356L4.28035 8.21968C3.98746 7.92678 3.51259 7.92677 3.21969 8.21965C2.92678 8.51253 2.92677 8.98741 3.21965 9.28031L5.96542 12.0262C6.25819 12.319 6.73284 12.3192 7.02579 12.0266Z - M3.5 14C3.29688 14 3.10417 13.9609 2.92188 13.8828C2.73958 13.8047 2.57812 13.6979 2.4375 13.5625C2.30208 13.4219 2.19531 13.2604 2.11719 13.0781C2.03906 12.8958 2 12.7031 2 12.5V3.5C2 3.29688 2.03906 3.10417 2.11719 2.92188C2.19531 2.73958 2.30208 2.58073 2.4375 2.44531C2.57812 2.30469 2.73958 2.19531 2.92188 2.11719C3.10417 2.03906 3.29688 2 3.5 2H5.5C5.70312 2 5.89583 2.03906 6.07812 2.11719C6.26042 2.19531 6.41927 2.30469 6.55469 2.44531C6.69531 2.58073 6.80469 2.73958 6.88281 2.92188C6.96094 3.10417 7 3.29688 7 3.5V12.5C7 12.7031 6.96094 12.8958 6.88281 13.0781C6.80469 13.2604 6.69531 13.4219 6.55469 13.5625C6.41927 13.6979 6.26042 13.8047 6.07812 13.8828C5.89583 13.9609 5.70312 14 5.5 14H3.5ZM10.5 14C10.2969 14 10.1042 13.9609 9.92188 13.8828C9.73958 13.8047 9.57812 13.6979 9.4375 13.5625C9.30208 13.4219 9.19531 13.2604 9.11719 13.0781C9.03906 12.8958 9 12.7031 9 12.5V3.5C9 3.29688 9.03906 3.10417 9.11719 2.92188C9.19531 2.73958 9.30208 2.58073 9.4375 2.44531C9.57812 2.30469 9.73958 2.19531 9.92188 2.11719C10.1042 2.03906 10.2969 2 10.5 2H12.5C12.7031 2 12.8958 2.03906 13.0781 2.11719C13.2604 2.19531 13.4193 2.30469 13.5547 2.44531C13.6953 2.58073 13.8047 2.73958 13.8828 2.92188C13.9609 3.10417 14 3.29688 14 3.5V12.5C14 12.7031 13.9609 12.8958 13.8828 13.0781C13.8047 13.2604 13.6953 13.4219 13.5547 13.5625C13.4193 13.6979 13.2604 13.8047 13.0781 13.8828C12.8958 13.9609 12.7031 14 12.5 14H10.5ZM5.5 13C5.63542 13 5.7526 12.9505 5.85156 12.8516C5.95052 12.7526 6 12.6354 6 12.5V3.5C6 3.36458 5.95052 3.2474 5.85156 3.14844C5.7526 3.04948 5.63542 3 5.5 3H3.5C3.36458 3 3.2474 3.04948 3.14844 3.14844C3.04948 3.2474 3 3.36458 3 3.5V12.5C3 12.6354 3.04948 12.7526 3.14844 12.8516C3.2474 12.9505 3.36458 13 3.5 13H5.5ZM12.5 13C12.6354 13 12.7526 12.9505 12.8516 12.8516C12.9505 12.7526 13 12.6354 13 12.5V3.5C13 3.36458 12.9505 3.2474 12.8516 3.14844C12.7526 3.04948 12.6354 3 12.5 3H10.5C10.3646 3 10.2474 3.04948 10.1484 3.14844C10.0495 3.2474 10 3.36458 10 3.5V12.5C10 12.6354 10.0495 12.7526 10.1484 12.8516C10.2474 12.9505 10.3646 13 10.5 13H12.5Z + M3.5 14C3.29688 14 3.10417 13.9609 2.92188 13.8828C2.73958 13.8047 2.57812 13.6979 2.4375 13.5625C2.30208 13.4219 2.19531 13.2604 2.11719 13.0781C2.03906 12.8958 2 12.7031 2 12.5V3.5C2 3.29688 2.03906 3.10417 2.11719 2.92188C2.19531 2.73958 2.30208 2.58073 2.4375 2.44531C2.57812 2.30469 2.73958 2.19531 2.92188 2.11719C3.10417 2.03906 3.29688 2 3.5 2H5.5C5.70312 2 5.89583 2.03906 6.07812 2.11719C6.26042 2.19531 6.41927 2.30469 6.55469 2.44531C6.69531 2.58073 6.80469 2.73958 6.88281 2.92188C6.96094 3.10417 7 3.29688 7 3.5V12.5C7 12.7031 6.96094 12.8958 6.88281 13.0781C6.80469 13.2604 6.69531 13.4219 6.55469 13.5625C6.41927 13.6979 6.26042 13.8047 6.07812 13.8828C5.89583 13.9609 5.70312 14 5.5 14H3.5ZM10.5 14C10.2969 14 10.1042 13.9609 9.92188 13.8828C9.73958 13.8047 9.57812 13.6979 9.4375 13.5625C9.30208 13.4219 9.19531 13.2604 9.11719 13.0781C9.03906 12.8958 9 12.7031 9 12.5V3.5C9 3.29688 9.03906 3.10417 9.11719 2.92188C9.19531 2.73958 9.30208 2.58073 9.4375 2.44531C9.57812 2.30469 9.73958 2.19531 9.92188 2.11719C10.1042 2.03906 10.2969 2 10.5 2H12.5C12.7031 2 12.8958 2.03906 13.0781 2.11719C13.2604 2.19531 13.4193 2.30469 13.5547 2.44531C13.6953 2.58073 13.8047 2.73958 13.8828 2.92188C13.9609 3.10417 14 3.29688 14 3.5V12.5C14 12.7031 13.9609 12.8958 13.8828 13.0781C13.8047 13.2604 13.6953 13.4219 13.5547 13.5625C13.4193 13.6979 13.2604 13.8047 13.0781 13.8828C12.8958 13.9609 12.7031 14 12.5 14H10.5ZM5.5 13C5.63542 13 5.7526 12.9505 5.85156 12.8516C5.95052 12.7526 6 12.6354 6 12.5V3.5C6 3.36458 5.95052 3.2474 5.85156 3.14844C5.7526 3.04948 5.63542 3 5.5 3H3.5C3.36458 3 3.2474 3.04948 3.14844 3.14844C3.04948 3.2474 3 3.36458 3 3.5V12.5C3 12.6354 3.04948 12.7526 3.14844 12.8516C3.2474 12.9505 3.36458 13 3.5 13H5.5ZM12.5 13C12.6354 13 12.7526 12.9505 12.8516 12.8516C12.9505 12.7526 13 12.6354 13 12.5V3.5C13 3.36458 12.9505 3.2474 12.8516 3.14844C12.7526 3.04948 12.6354 3 12.5 3H10.5C10.3646 3 10.2474 3.04948 10.1484 3.14844C10.0495 3.2474 10 3.36458 10 3.5V12.5C10 12.6354 10.0495 12.7526 10.1484 12.8516C10.2474 12.9505 10.3646 13 10.5 13H12.5Z - M7 2.25C7 2.04688 7.03906 1.85417 7.11719 1.67188C7.19531 1.48958 7.30208 1.33073 7.4375 1.19531C7.57812 1.05469 7.73958 0.945312 7.92188 0.867188C8.10417 0.789062 8.29688 0.75 8.5 0.75C8.70312 0.75 8.89583 0.789062 9.07812 0.867188C9.26042 0.945312 9.41927 1.05469 9.55469 1.19531C9.69531 1.33073 9.80469 1.48958 9.88281 1.67188C9.96094 1.85417 10 2.04688 10 2.25C10 2.45312 9.96094 2.64583 9.88281 2.82812C9.80469 3.01042 9.69531 3.17188 9.55469 3.3125C9.41927 3.44792 9.26042 3.55469 9.07812 3.63281C8.89583 3.71094 8.70312 3.75 8.5 3.75C8.29688 3.75 8.10417 3.71094 7.92188 3.63281C7.73958 3.55469 7.57812 3.44792 7.4375 3.3125C7.30208 3.17188 7.19531 3.01042 7.11719 2.82812C7.03906 2.64583 7 2.45312 7 2.25ZM7.5 14.25V6.25C7.5 6.10938 7.52604 5.97917 7.57812 5.85938C7.63021 5.73958 7.70052 5.63542 7.78906 5.54688C7.88281 5.45312 7.98958 5.38021 8.10938 5.32812C8.22917 5.27604 8.35938 5.25 8.5 5.25C8.63542 5.25 8.76302 5.27604 8.88281 5.32812C9.00781 5.38021 9.11458 5.45312 9.20312 5.54688C9.29688 5.63542 9.36979 5.74219 9.42188 5.86719C9.47396 5.98698 9.5 6.11458 9.5 6.25V14.25C9.5 14.3906 9.47396 14.5208 9.42188 14.6406C9.36979 14.7604 9.29688 14.8672 9.20312 14.9609C9.11458 15.0495 9.01042 15.1198 8.89062 15.1719C8.77083 15.224 8.64062 15.25 8.5 15.25C8.35938 15.25 8.22656 15.224 8.10156 15.1719C7.98177 15.1198 7.8776 15.0495 7.78906 14.9609C7.70052 14.8724 7.63021 14.7682 7.57812 14.6484C7.52604 14.5234 7.5 14.3906 7.5 14.25Z + M7 2.25C7 2.04688 7.03906 1.85417 7.11719 1.67188C7.19531 1.48958 7.30208 1.33073 7.4375 1.19531C7.57812 1.05469 7.73958 0.945312 7.92188 0.867188C8.10417 0.789062 8.29688 0.75 8.5 0.75C8.70312 0.75 8.89583 0.789062 9.07812 0.867188C9.26042 0.945312 9.41927 1.05469 9.55469 1.19531C9.69531 1.33073 9.80469 1.48958 9.88281 1.67188C9.96094 1.85417 10 2.04688 10 2.25C10 2.45312 9.96094 2.64583 9.88281 2.82812C9.80469 3.01042 9.69531 3.17188 9.55469 3.3125C9.41927 3.44792 9.26042 3.55469 9.07812 3.63281C8.89583 3.71094 8.70312 3.75 8.5 3.75C8.29688 3.75 8.10417 3.71094 7.92188 3.63281C7.73958 3.55469 7.57812 3.44792 7.4375 3.3125C7.30208 3.17188 7.19531 3.01042 7.11719 2.82812C7.03906 2.64583 7 2.45312 7 2.25ZM7.5 14.25V6.25C7.5 6.10938 7.52604 5.97917 7.57812 5.85938C7.63021 5.73958 7.70052 5.63542 7.78906 5.54688C7.88281 5.45312 7.98958 5.38021 8.10938 5.32812C8.22917 5.27604 8.35938 5.25 8.5 5.25C8.63542 5.25 8.76302 5.27604 8.88281 5.32812C9.00781 5.38021 9.11458 5.45312 9.20312 5.54688C9.29688 5.63542 9.36979 5.74219 9.42188 5.86719C9.47396 5.98698 9.5 6.11458 9.5 6.25V14.25C9.5 14.3906 9.47396 14.5208 9.42188 14.6406C9.36979 14.7604 9.29688 14.8672 9.20312 14.9609C9.11458 15.0495 9.01042 15.1198 8.89062 15.1719C8.77083 15.224 8.64062 15.25 8.5 15.25C8.35938 15.25 8.22656 15.224 8.10156 15.1719C7.98177 15.1198 7.8776 15.0495 7.78906 14.9609C7.70052 14.8724 7.63021 14.7682 7.57812 14.6484C7.52604 14.5234 7.5 14.3906 7.5 14.25Z - M9.29895 5.75C8.7216 4.75 7.27823 4.75 6.70088 5.75L6.24929 6.53218C6.10532 6.78152 6.20117 7.10063 6.45869 7.22939C6.69528 7.34768 6.98306 7.26125 7.11531 7.03218L7.5669 6.25C7.75935 5.91667 8.24048 5.91667 8.43293 6.25L8.88452 7.03218C9.01677 7.26125 9.30455 7.34768 9.54113 7.22939C9.79866 7.10063 9.8945 6.78152 9.75054 6.53218L9.29895 5.75ZM10.165 9.25L10.0574 9.06375C9.91348 8.8144 10.0093 8.4953 10.2668 8.36653C10.5034 8.24824 10.7912 8.33468 10.9235 8.56375L11.031 8.75C11.6084 9.75 10.8867 11 9.73196 11H8.99991C8.72377 11 8.49991 10.7761 8.49991 10.5C8.49991 10.2239 8.72377 10 8.99991 10H9.73196C10.1169 10 10.3574 9.58333 10.165 9.25ZM6.99991 10C7.27606 10 7.49991 10.2239 7.49991 10.5C7.49991 10.7761 7.27606 11 6.99991 11H6.26786C5.11316 11 4.39147 9.75 4.96882 8.75L5.07636 8.56375C5.20861 8.33468 5.4964 8.24824 5.73298 8.36653C5.9905 8.4953 6.08635 8.8144 5.94238 9.06375L5.83485 9.25C5.6424 9.58333 5.88296 10 6.26786 10H6.99991ZM13.9142 0.585786C14.2893 0.960859 14.5 1.46957 14.5 2V2.56L13.17 14.23C13.1133 14.7196 12.8778 15.1711 12.5087 15.4978C12.1396 15.8244 11.6629 16.0033 11.17 16H4.85C4.35711 16.0033 3.88037 15.8244 3.51127 15.4978C3.14216 15.1711 2.90667 14.7196 2.85 14.23L1.5 2.56V2C1.5 1.46957 1.71071 0.960859 2.08579 0.585786C2.46086 0.210714 2.96957 0 3.5 0H12.5C13.0304 0 13.5391 0.210714 13.9142 0.585786ZM12.5 1H3.5C3.23478 1 2.98043 1.10536 2.79289 1.29289C2.60536 1.48043 2.5 1.73478 2.5 2H13.5C13.5 1.73478 13.3946 1.48043 13.2071 1.29289C13.0196 1.10536 12.7652 1 12.5 1ZM11.8309 14.747C12.0156 14.5827 12.1328 14.3557 12.16 14.11L13.44 3H2.56L3.84 14.11C3.86719 14.3557 3.98443 14.5827 4.16911 14.747C4.35378 14.9114 4.59279 15.0015 4.84 15H11.16C11.4072 15.0015 11.6462 14.9114 11.8309 14.747Z + M9.29895 5.75C8.7216 4.75 7.27823 4.75 6.70088 5.75L6.24929 6.53218C6.10532 6.78152 6.20117 7.10063 6.45869 7.22939C6.69528 7.34768 6.98306 7.26125 7.11531 7.03218L7.5669 6.25C7.75935 5.91667 8.24048 5.91667 8.43293 6.25L8.88452 7.03218C9.01677 7.26125 9.30455 7.34768 9.54113 7.22939C9.79866 7.10063 9.8945 6.78152 9.75054 6.53218L9.29895 5.75ZM10.165 9.25L10.0574 9.06375C9.91348 8.8144 10.0093 8.4953 10.2668 8.36653C10.5034 8.24824 10.7912 8.33468 10.9235 8.56375L11.031 8.75C11.6084 9.75 10.8867 11 9.73196 11H8.99991C8.72377 11 8.49991 10.7761 8.49991 10.5C8.49991 10.2239 8.72377 10 8.99991 10H9.73196C10.1169 10 10.3574 9.58333 10.165 9.25ZM6.99991 10C7.27606 10 7.49991 10.2239 7.49991 10.5C7.49991 10.7761 7.27606 11 6.99991 11H6.26786C5.11316 11 4.39147 9.75 4.96882 8.75L5.07636 8.56375C5.20861 8.33468 5.4964 8.24824 5.73298 8.36653C5.9905 8.4953 6.08635 8.8144 5.94238 9.06375L5.83485 9.25C5.6424 9.58333 5.88296 10 6.26786 10H6.99991ZM13.9142 0.585786C14.2893 0.960859 14.5 1.46957 14.5 2V2.56L13.17 14.23C13.1133 14.7196 12.8778 15.1711 12.5087 15.4978C12.1396 15.8244 11.6629 16.0033 11.17 16H4.85C4.35711 16.0033 3.88037 15.8244 3.51127 15.4978C3.14216 15.1711 2.90667 14.7196 2.85 14.23L1.5 2.56V2C1.5 1.46957 1.71071 0.960859 2.08579 0.585786C2.46086 0.210714 2.96957 0 3.5 0H12.5C13.0304 0 13.5391 0.210714 13.9142 0.585786ZM12.5 1H3.5C3.23478 1 2.98043 1.10536 2.79289 1.29289C2.60536 1.48043 2.5 1.73478 2.5 2H13.5C13.5 1.73478 13.3946 1.48043 13.2071 1.29289C13.0196 1.10536 12.7652 1 12.5 1ZM11.8309 14.747C12.0156 14.5827 12.1328 14.3557 12.16 14.11L13.44 3H2.56L3.84 14.11C3.86719 14.3557 3.98443 14.5827 4.16911 14.747C4.35378 14.9114 4.59279 15.0015 4.84 15H11.16C11.4072 15.0015 11.6462 14.9114 11.8309 14.747Z + + + ToolTipService.ToolTip="{x:Bind Header, Mode=OneWay}" /> @@ -197,7 +198,7 @@ - + - + - - - - + + - - - - - - - - + XAxes="{x:Bind SpeedGraphXAxes, Mode=OneWay}" + YAxes="{x:Bind SpeedGraphYAxes, Mode=OneWay}" /> - + + + + + + + + + + ? SpeedGraphValues { get; private set; } + public ObservableCollection? SpeedGraphBackgroundValues { get; private set; } + public ObservableCollection? SpeedGraphSeries { get; private set; } - public IList? SpeedGraphXAxes { get; private set; } + public ObservableCollection? SpeedGraphXAxes { get; private set; } - public IList? SpeedGraphYAxes { get; private set; } + public ObservableCollection? SpeedGraphYAxes { get; private set; } public double IconBackgroundCircleBorderOpacity { get; private set; } + public double? CurrentHighestPointValue { get; private set; } + public CancellationToken CancellationToken => _operationCancellationToken?.Token ?? default; @@ -207,18 +211,17 @@ public StatusCenterItem( IconBackgroundCircleBorderOpacity = 1; AnimatedIconState = "NormalOff"; SpeedGraphValues = new(); + SpeedGraphBackgroundValues = new(); CancelCommand = new RelayCommand(ExecuteCancelCommand); Message = "ProcessingItems".GetLocalizedResource(); + Source = source; + Destination = destination; - if (source is not null) - Source = source; - - if (destination is not null) - Destination = destination; - + // Get the graph color if (App.Current.Resources["AppThemeFillColorAttentionBrush"] is not SolidColorBrush accentBrush) return; + // Initialize graph series SpeedGraphSeries = new() { new LineSeries @@ -231,7 +234,7 @@ public StatusCenterItem( // Stroke Stroke = new SolidColorPaint( new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B), - 1), + 1f), // Fill under the stroke Fill = new LinearGradientPaint( @@ -239,13 +242,34 @@ public StatusCenterItem( new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B, 50), new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B, 10) }, - new(0.5f, 0f), - new(0.5f, 1.0f), - new[] { 0.2f, 1.3f }), + new(0f, 0f), + new(0f, 0f), + new[] { 0.1f, 1.0f }), + }, + new LineSeries + { + Values = SpeedGraphBackgroundValues, + GeometrySize = 0d, + DataPadding = new(0, 0), + IsHoverable = false, + + // Stroke + Stroke = new SolidColorPaint( + new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B, 40), + 0.1f), + + // Fill under the stroke + Fill = new LinearGradientPaint( + new SKColor[] { + new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B, 40) + }, + new(0f, 0f), + new(0f, 0f)), } }; - SpeedGraphXAxes = new ICartesianAxis[] + // Initialize X axes of the graph + SpeedGraphXAxes = new() { new Axis { @@ -256,7 +280,8 @@ public StatusCenterItem( } }; - SpeedGraphYAxes = new ICartesianAxis[] + // Initialize Y axes of the graph + SpeedGraphYAxes = new() { new Axis { @@ -341,6 +366,7 @@ private void ReportProgress(StatusCenterItemProgressModel value) if (value.Status is FileSystemStatusCode status) FileSystemOperationReturnResult = status.ToStatus(); + // Update the footer message, percentage, processing item name if (value.Percentage is double p) { if (ProgressPercentage != value.Percentage) @@ -428,9 +454,41 @@ private void ReportProgress(StatusCenterItemProgressModel value) break; } - // Remove the same point + bool isSamePoint = false; + + // Remove the point that has the same X position if (SpeedGraphValues?.FirstOrDefault(v => v.X == point.X) is ObservablePoint existingPoint) + { SpeedGraphValues.Remove(existingPoint); + isSamePoint = true; + } + + CurrentHighestPointValue ??= point.Y; + + if (!isSamePoint) + { + // NOTE: -0.4 is the value that is needs to set for the graph drawing + var maxHeight = CurrentHighestPointValue * 1.44d - 0.4; + + if (CurrentHighestPointValue < point.Y && + SpeedGraphYAxes is not null && + SpeedGraphYAxes.FirstOrDefault() is var item && + item is not null) + { + // Max height is updated + CurrentHighestPointValue = point.Y; + maxHeight = CurrentHighestPointValue * 1.44d; + item.MaxLimit = maxHeight; + + // NOTE: -0.1 is the value that is needs to set for the graph drawing + UpdateGraphBackgroundPoints(point.X, maxHeight - 0.1, true); + } + else + { + // Max height is not updated + UpdateGraphBackgroundPoints(point.X, maxHeight, false); + } + } // Add a new point SpeedGraphValues?.Add(point); @@ -443,6 +501,20 @@ private void ReportProgress(StatusCenterItemProgressModel value) _viewModel.NotifyChanges(); } + private void UpdateGraphBackgroundPoints(double? x, double? y, bool redraw) + { + if (SpeedGraphBackgroundValues is null) + return; + + ObservablePoint newPoint = new(x, y); + SpeedGraphBackgroundValues.Add(newPoint); + + if (redraw) + { + SpeedGraphBackgroundValues.ForEach(x => x.Y = CurrentHighestPointValue * 1.44d - 0.1); + } + } + public void ExecuteCancelCommand() { if (IsCancelable) From 425d1eade6549dc7291a08d85a262e8834d951d3 Mon Sep 17 00:00:00 2001 From: Marco Gavelli Date: Sun, 22 Oct 2023 16:06:46 +0200 Subject: [PATCH 97/97] Fix cancellation of compression & extraction (#14) --- src/Files.App/Files.App.csproj | 2 +- .../Utils/Archives/CompressArchiveModel.cs | 10 +- .../Utils/Archives/DecompressHelper.cs | 107 +++++------------- .../nupkgs/SevenZipSharp.1.0.1.nupkg | Bin 71618 -> 0 bytes .../nupkgs/SevenZipSharp.1.0.2.nupkg | Bin 0 -> 71614 bytes 5 files changed, 36 insertions(+), 83 deletions(-) delete mode 100644 src/Files.App/nupkgs/SevenZipSharp.1.0.1.nupkg create mode 100644 src/Files.App/nupkgs/SevenZipSharp.1.0.2.nupkg diff --git a/src/Files.App/Files.App.csproj b/src/Files.App/Files.App.csproj index 85e81ede0cda..28f7b3083804 100644 --- a/src/Files.App/Files.App.csproj +++ b/src/Files.App/Files.App.csproj @@ -83,7 +83,7 @@ - + diff --git a/src/Files.App/Utils/Archives/CompressArchiveModel.cs b/src/Files.App/Utils/Archives/CompressArchiveModel.cs index 77b4c37c5dd5..347a2e2fda72 100644 --- a/src/Files.App/Utils/Archives/CompressArchiveModel.cs +++ b/src/Files.App/Utils/Archives/CompressArchiveModel.cs @@ -17,6 +17,8 @@ public class CompressArchiveModel : ICompressArchiveModel private FileSizeCalculator _sizeCalculator; + private IThreadingService _threadingService = Ioc.Default.GetRequiredService(); + private string ArchiveExtension => FileFormat switch { ArchiveFormats.Zip => ".zip", @@ -147,7 +149,6 @@ public async Task RunCreationAsync() IncludeEmptyDirectories = true, EncryptHeaders = true, PreserveDirectoryRoot = sources.Length > 1, - EventSynchronization = EventSynchronizationStrategy.AlwaysAsynchronous, }; compressor.Compressing += Compressor_Compressing; @@ -207,8 +208,11 @@ private void Compressor_FileCompressionStarted(object? sender, FileNameEventArgs e.Cancel = true; else _sizeCalculator.ForceComputeFileSize(e.FilePath); - _fileSystemProgress.FileName = e.FileName; - _fileSystemProgress.Report(); + _threadingService.ExecuteOnUiThreadAsync(() => + { + _fileSystemProgress.FileName = e.FileName; + _fileSystemProgress.Report(); + }); } private void Compressor_FileCompressionFinished(object? sender, EventArgs e) diff --git a/src/Files.App/Utils/Archives/DecompressHelper.cs b/src/Files.App/Utils/Archives/DecompressHelper.cs index 5acbe3cc95fd..569a0e1420d3 100644 --- a/src/Files.App/Utils/Archives/DecompressHelper.cs +++ b/src/Files.App/Utils/Archives/DecompressHelper.cs @@ -16,6 +16,8 @@ public static class DecompressHelper { private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); + private static IThreadingService _threadingService = Ioc.Default.GetRequiredService(); + private static async Task GetZipFile(BaseStorageFile archive, string password = "") { return await FilesystemTasks.Wrap(async () => @@ -40,57 +42,13 @@ public static async Task ExtractArchiveAsync(BaseStorageFile archive, BaseStorag if (zipFile is null) return; - var directoryEntries = new List(); - var fileEntries = new List(); - foreach (ArchiveFileInfo entry in zipFile.ArchiveFileData) - { - if (!entry.IsDirectory) - fileEntries.Add(entry); - else - directoryEntries.Add(entry); - } - - if (cancellationToken.IsCancellationRequested) // Check if cancelled - return; - - var directories = new List(); - try - { - directories.AddRange(directoryEntries.Select((entry) => entry.FileName)); - directories.AddRange(fileEntries.Select((entry) => Path.GetDirectoryName(entry.FileName))); - } - catch (Exception ex) - { - App.Logger.LogWarning(ex, $"Error transforming zip names into: {destinationFolder.Path}\n" + - $"Directories: {string.Join(", ", directoryEntries.Select(x => x.FileName))}\n" + - $"Files: {string.Join(", ", fileEntries.Select(x => x.FileName))}"); - return; - } - - foreach (var dir in directories.Distinct().OrderBy(x => x.Length)) - { - if (!NativeFileOperationsHelper.CreateDirectoryFromApp(dir, IntPtr.Zero)) - { - var dirName = destinationFolder.Path; - foreach (var component in dir.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries)) - { - dirName = Path.Combine(dirName, component); - NativeFileOperationsHelper.CreateDirectoryFromApp(dirName, IntPtr.Zero); - } - } - - if (cancellationToken.IsCancellationRequested) // Check if canceled - return; - } - if (cancellationToken.IsCancellationRequested) // Check if canceled return; // Fill files byte[] buffer = new byte[4096]; - int entriesAmount = fileEntries.Count; - var minimumTime = new DateTime(1); + int entriesAmount = zipFile.ArchiveFileData.Where(x => !x.IsDirectory).Count(); StatusCenterItemProgressModel fsProgress = new( progress, @@ -104,47 +62,38 @@ public static async Task ExtractArchiveAsync(BaseStorageFile archive, BaseStorag zipFile.Extracting += (s, e) => { if (fsProgress.TotalSize > 0) - fsProgress.Report((fsProgress.ProcessedSize + e.PercentDelta / 100.0 * e.BytesCount) / fsProgress.TotalSize * 100); + fsProgress.Report(e.BytesProcessed / (double)fsProgress.TotalSize * 100); }; - - foreach (var entry in fileEntries) + zipFile.FileExtractionStarted += (s, e) => { - if (cancellationToken.IsCancellationRequested) // Check if canceled - return; - - var filePath = destinationFolder.Path; - foreach (var component in entry.FileName.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) - filePath = Path.Combine(filePath, component); - - var hFile = NativeFileOperationsHelper.CreateFileForWrite(filePath); - if (hFile.IsInvalid) - return; // TODO: handle error - - fsProgress.FileName = entry.FileName; - fsProgress.Report(); - - // We don't close hFile because FileStream.Dispose() already does that - using (FileStream destinationStream = new FileStream(hFile, FileAccess.Write)) + if (cancellationToken.IsCancellationRequested) + e.Cancel = true; + if (!e.FileInfo.IsDirectory) { - try + _threadingService.ExecuteOnUiThreadAsync(() => { - await zipFile.ExtractFileAsync(entry.Index, destinationStream); - } - catch (Exception ex) - { - App.Logger.LogWarning(ex, $"Error extracting file: {filePath}"); - return; // TODO: handle error - } + fsProgress.FileName = e.FileInfo.FileName; + fsProgress.Report(); + }); } - - _ = new FileInfo(filePath) + }; + zipFile.FileExtractionFinished += (s, e) => + { + if (!e.FileInfo.IsDirectory) { - CreationTime = entry.CreationTime > minimumTime && entry.CreationTime < entry.LastWriteTime ? entry.CreationTime : entry.LastWriteTime, - LastWriteTime = entry.LastWriteTime, - }; + fsProgress.AddProcessedItemsCount(1); + fsProgress.Report(); + } + }; - fsProgress.AddProcessedItemsCount(1); - fsProgress.Report(); + try + { + await zipFile.ExtractArchiveAsync(destinationFolder.Path); + } + catch (Exception ex) + { + App.Logger.LogWarning(ex, $"Error extracting file: {archive.Name}"); + return; // TODO: handle error } } diff --git a/src/Files.App/nupkgs/SevenZipSharp.1.0.1.nupkg b/src/Files.App/nupkgs/SevenZipSharp.1.0.1.nupkg deleted file mode 100644 index 2cbf00b6f3a9e3fdc31529b3b74d5402ac1da261..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71618 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 diff --git a/src/Files.App/nupkgs/SevenZipSharp.1.0.2.nupkg b/src/Files.App/nupkgs/SevenZipSharp.1.0.2.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..246c5fdf3f1c568beb6749750618cae978306954 GIT binary patch literal 71614 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 literal 0 HcmV?d00001