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..780cddf96788 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.Zip);
- 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 69d517e18803..329af11425f5 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.DecompressArchiveAsync(context.ShellPage);
+ return DecompressHelper.DecompressArchiveAsync(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 51fb41fd8b57..5487e515a31c 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.DecompressArchiveHereAsync(context.ShellPage);
+ return DecompressHelper.DecompressArchiveHereAsync(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 ad8db6db6d84..06b07c4dcbcf 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.DecompressArchiveToChildFolderAsync(context.ShellPage);
+ return DecompressHelper.DecompressArchiveToChildFolderAsync(context.ShellPage);
}
protected override void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
diff --git a/src/Files.App/App.xaml b/src/Files.App/App.xaml
index 51f0b13ff276..b4bd7eff717d 100644
--- a/src/Files.App/App.xaml
+++ b/src/Files.App/App.xaml
@@ -30,8 +30,8 @@
-
-
+
+
@@ -45,6 +45,9 @@
+
+ #0070CB
+
@@ -56,6 +59,9 @@
+
+ #50C0FF
+
@@ -67,6 +73,9 @@
+
+ #50C0FF
+
diff --git a/src/Files.App/Converters/StatusCenterStateToBrushConverter.cs b/src/Files.App/Converters/StatusCenterStateToBrushConverter.cs
new file mode 100644
index 000000000000..73e35e83039c
--- /dev/null
+++ b/src/Files.App/Converters/StatusCenterStateToBrushConverter.cs
@@ -0,0 +1,120 @@
+// 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 static readonly DependencyProperty CanceledBackgroundBrushProperty =
+ DependencyProperty.Register(nameof(CanceledBackgroundBrush), typeof(SolidColorBrush), typeof(StatusCenterStateToBrushConverter), new PropertyMetadata(null));
+
+ public static readonly DependencyProperty CanceledForegroundBrushProperty =
+ DependencyProperty.Register(nameof(CanceledForegroundBrush), 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 SolidColorBrush CanceledBackgroundBrush
+ {
+ get => (SolidColorBrush)GetValue(CanceledBackgroundBrushProperty);
+ set => SetValue(CanceledBackgroundBrushProperty, value);
+ }
+
+ public SolidColorBrush CanceledForegroundBrush
+ {
+ get => (SolidColorBrush)GetValue(CanceledForegroundBrushProperty);
+ set => SetValue(CanceledForegroundBrushProperty, 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,
+ StatusCenterItemKind.Canceled => CanceledBackgroundBrush,
+ _ => CanceledBackgroundBrush
+ };
+ }
+ else
+ {
+ return state switch
+ {
+ StatusCenterItemKind.InProgress => InProgressForegroundBrush,
+ StatusCenterItemKind.Successful => SuccessfulForegroundBrush,
+ StatusCenterItemKind.Error => ErrorForegroundBrush,
+ StatusCenterItemKind.Canceled => CanceledForegroundBrush,
+ _ => CanceledForegroundBrush
+ };
+ }
+ }
+
+ 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..0afd41e43b78
--- /dev/null
+++ b/src/Files.App/Converters/StatusCenterStateToStateIconConverter.cs
@@ -0,0 +1,53 @@
+// 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.ActionExtract"] as string,
+ StatusCenterItemIconKind.Compress => Application.Current.Resources["App.Theme.PathIcon.ActionExtract"] 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/Data/Items/ListedItem.cs b/src/Files.App/Data/Items/ListedItem.cs
index 189a158155f1..a48eb374f518 100644
--- a/src/Files.App/Data/Items/ListedItem.cs
+++ b/src/Files.App/Data/Items/ListedItem.cs
@@ -38,7 +38,7 @@ public string ItemTooltipText
get
{
var tooltipBuilder = new StringBuilder();
- tooltipBuilder.AppendLine($"{"ToolTipDescriptionName".GetLocalizedResource()} {Name}");
+ tooltipBuilder.AppendLine($"{"NameWithColon".GetLocalizedResource()} {Name}");
tooltipBuilder.AppendLine($"{"ItemType".GetLocalizedResource()} {itemType}");
tooltipBuilder.Append($"{"ToolTipDescriptionDate".GetLocalizedResource()} {ItemDateModified}");
if(!string.IsNullOrWhiteSpace(FileSize))
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/Files.App.csproj b/src/Files.App/Files.App.csproj
index 10f844751ba9..28f7b3083804 100644
--- a/src/Files.App/Files.App.csproj
+++ b/src/Files.App/Files.App.csproj
@@ -1,4 +1,4 @@
-
+
@@ -75,6 +75,7 @@
+
@@ -82,7 +83,7 @@
-
+
diff --git a/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs b/src/Files.App/Helpers/MenuFlyout/ContextFlyoutItemHelper.cs
index b74d5ccd443b..686ce3180db0 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/Helpers/UI/UIFilesystemHelpers.cs b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs
index 1e140fcf7722..e6a17b942ab7 100644
--- a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs
+++ b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs
@@ -33,12 +33,8 @@ public static async Task CutItemAsync(IShellPage associatedInstance)
associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
var itemsCount = associatedInstance.SlimContentPage.SelectedItems!.Count;
- var banner = itemsCount > 50 ? _statusCenterViewModel.AddItem(
- string.Empty,
- string.Format("StatusPreparingItemsDetails_Plural".GetLocalizedResource(), itemsCount),
- 0,
- ReturnResult.InProgress,
- FileOperationType.Prepare, new CancellationTokenSource()) : null;
+
+ var banner = itemsCount > 50 ? StatusCenterHelper.AddCard_Prepare() : null;
try
{
@@ -54,7 +50,7 @@ await associatedInstance.SlimContentPage.SelectedItems.ToList().ParallelForEachA
{
if (banner is not null)
{
- banner.Progress.ProcessedItemsCount = itemsCount;
+ banner.Progress.AddProcessedItemsCount(1);
banner.Progress.Report();
}
@@ -102,6 +98,7 @@ await associatedInstance.SlimContentPage.SelectedItems.ToList().ParallelForEachA
return;
}
+
associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
_statusCenterViewModel.RemoveItem(banner);
@@ -144,12 +141,8 @@ public static async Task CopyItemAsync(IShellPage associatedInstance)
associatedInstance.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
var itemsCount = associatedInstance.SlimContentPage.SelectedItems!.Count;
- var banner = itemsCount > 50 ? _statusCenterViewModel.AddItem(
- string.Empty,
- string.Format("StatusPreparingItemsDetails_Plural".GetLocalizedResource(), itemsCount),
- 0,
- ReturnResult.InProgress,
- FileOperationType.Prepare, new CancellationTokenSource()) : null;
+
+ var banner = itemsCount > 50 ? StatusCenterHelper.AddCard_Prepare() : null;
try
{
@@ -163,7 +156,7 @@ await associatedInstance.SlimContentPage.SelectedItems.ToList().ParallelForEachA
{
if (banner is not null)
{
- banner.Progress.ProcessedItemsCount = itemsCount;
+ banner.Progress.AddProcessedItemsCount(1);
banner.Progress.Report();
}
diff --git a/src/Files.App/ResourceDictionaries/PathIcons.xaml b/src/Files.App/ResourceDictionaries/PathIcons.xaml
index 204ff2d321f4..70592e9b296e 100644
--- a/src/Files.App/ResourceDictionaries/PathIcons.xaml
+++ b/src/Files.App/ResourceDictionaries/PathIcons.xaml
@@ -117,6 +117,60 @@
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
+
+
+
+
diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw
index b124d3d8112f..822e13999496 100644
--- a/src/Files.App/Strings/en-US/Resources.resw
+++ b/src/Files.App/Strings/en-US/Resources.resw
@@ -426,12 +426,6 @@
items
-
- Deleting files
-
-
- Moving files to the Recycle Bin
-
Yes
@@ -1173,24 +1167,6 @@
Item count
-
- Deletion Failed
-
-
- Deletion complete
-
-
- Deletion cancelled
-
-
- Recycle complete
-
-
- Move complete
-
-
- Copy complete
-
This action requires administrator rights
@@ -1305,9 +1281,6 @@
Create folder with selection
-
- Name:
-
Type:
@@ -1413,104 +1386,11 @@
Privacy
-
- Move canceled
-
-
- Copy canceled
-
-
- Moving {0} item from {1} to {2} was canceled
-
-
- Moving {0} items from {1} to {2} was canceled after moving {3} items
-
-
- Moving {0} items from {1} to {2} was canceled after moving {3} item
-
-
- Moving {0} item from {1} to {2}
-
-
- Moving {0} items from {1} to {2}
-
-
- Successfully moved {0} item from {1} to {2}
-
-
- Successfully moved {0} items from {1} to {2}
-
-
- Failed to move {0} item from {1} to {2}
-
-
- Failed to move {0} items from {1} to {2}
-
-
- Copying {0} item to {1} was canceled
-
-
- Copying {0} items to {1} was canceled after copying {2} items
-
-
- 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}
-
-
- Successfully copied {0} items to {1}
-
-
- Deleting {0} item from {1} was canceled
-
-
- Deleting {0} items from {1} was canceled after deleting {3} items
-
-
- Deleting {0} items from {1} was canceled after deleting {3} item
-
-
- Deleting {0} item from {1}
-
-
- Deleting {0} items from {1}
-
-
- Successfully deleted {0} item from {1}
-
-
- Successfully deleted {0} items from {1}
-
-
- Failed to delete {0} item from {1}
-
-
- Failed to delete {0} items from {1}
-
the Recycle Bin
-
- Moving items
-
-
- Copying items
-
-
- Recycle failed
-
-
- Recycle cancelled
-
-
- canceling
+
+ Canceling
Clear
@@ -2094,24 +1974,6 @@
Update Files
-
- Preparing {0} items
-
-
- Preparing items
-
-
- Copy failed
-
-
- Failed to copy {0} items from {1} to {2}
-
-
- Failed to copy {0} item from {1} to {2}
-
-
- Move failed
-
Tags
@@ -2202,18 +2064,6 @@
Opening items
-
- Compression completed
-
-
- {0} has been compressed
-
-
- {0} couldn't be compressed
-
-
- Compressing archive
-
Compress
@@ -2247,15 +2097,6 @@
You don't have permission to access this folder.
-
- Emptying recycle bin
-
-
- Successfully emptied recycle bin
-
-
- Failed to empty recycle bin
-
Archive password
@@ -3523,6 +3364,16 @@
Clear completed
+
+ Name:
+
+
+ {0} of {1} processed
+ Shown in a StatusCenter card. Used as "64 MB of 1 GB processed"
+
+
+ items
+
Files can't initialize this directory as a Git repository.
@@ -3532,6 +3383,204 @@
Add page
+
+ Processing items...
+
+
+ Speed:
+
+
+ Canceled compressing {0} items to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Canceled compressing {0} items from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Compressed {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Compressed {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Error compressing {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Failed to compress {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Compressing {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Compressing {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Canceled copying {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Canceled copying {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Copied {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Copied {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Error copying {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Failed to copy {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Copying {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Copying {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Canceled extracting "{0}" to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Canceled extracting "{0}" from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Extracted "{0}" to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Extracted "{0}" from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Error extracting "{0}" to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Failed to extract "{0}" from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Extracting "{0}" to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Extracting "{0}" from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Canceled permanently deleting {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Permanently deleted {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Error permanently deleting {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Failed to permanently delete {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Permanently deleting {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Canceled deleting {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Deleted {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Error deleting {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Deleting {0} item(s) from "{1}"
+ Shown in a StatusCenter card.
+
+
+ Canceled moving {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Canceled moving {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Moved {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Moved {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Moving {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Moving {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Error moving {0} item(s) to "{1}"
+ Shown in a StatusCenter card.
+
+
+ Failed to move {0} item(s) from "{1}" to "{2}"
+ Shown in a StatusCenter card.
+
+
+ Canceled emptying Recycle Bin
+ Shown in a StatusCenter card.
+
+
+ Emptied Recycle Bin
+ Shown in a StatusCenter card.
+
+
+ Emptying Recycle Bin
+ Shown in a StatusCenter card.
+
+
+ Error emptying Recycle Bin
+ Shown in a StatusCenter card.
+
+
+ Failed to empty Recycle Bin
+ Shown in a StatusCenter card.
+
+
+ Preparing the operation...
+ Shown in a StatusCenter card.
+
+
+ {0}/{1} item(s) processed
+ Shown in a StatusCenter card. Used as "8/20 items processed"
+
Unblock downloaded file
diff --git a/src/Files.App/UserControls/AddressToolbar.xaml b/src/Files.App/UserControls/AddressToolbar.xaml
index 3dc53a580048..e12eb4d3c92f 100644
--- a/src/Files.App/UserControls/AddressToolbar.xaml
+++ b/src/Files.App/UserControls/AddressToolbar.xaml
@@ -411,11 +411,12 @@
-
+ Style="{StaticResource ColorIconStatusCenter}" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -698,10 +26,11 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/// Provides an archive creation support.
///
- public class ArchiveCreator : IArchiveCreator
+ 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 StatusCenterItemProgressModel _fileSystemProgress;
- private int _processedItems = 0;
+ private FileSizeCalculator _sizeCalculator;
- private StatusCenterItemProgressModel _fileSystemProgress;
+ private IThreadingService _threadingService = Ioc.Default.GetRequiredService();
private string ArchiveExtension => FileFormat switch
{
@@ -77,7 +71,12 @@ public IProgress Progress
set
{
_Progress = value;
- _fileSystemProgress = new(Progress, true, FileSystemStatusCode.InProgress);
+
+ _fileSystemProgress = new(
+ Progress,
+ false,
+ FileSystemStatusCode.InProgress);
+
_fileSystemProgress.Report(0);
}
}
@@ -106,18 +105,28 @@ public IProgress Progress
///
public ArchiveSplittingSizes SplittingSize { get; init; }
- public ArchiveCreator()
+ ///
+ public CancellationToken CancellationToken { get; set; }
+
+ 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);
_Progress = new Progress();
- ArchivePath = string.Empty;
- Sources = Enumerable.Empty();
- FileFormat = ArchiveFormats.Zip;
- CompressionLevel = ArchiveCompressionLevels.Normal;
- SplittingSize = ArchiveSplittingSizes.None;
- _fileSystemProgress.Report(0);
+ Sources = source;
+ Directory = directory;
+ FileName = fileName;
+ Password = password ?? string.Empty;
+ ArchivePath = string.Empty;
+ FileFormat = fileFormat;
+ CompressionLevel = compressionLevel;
+ SplittingSize = splittingSize;
}
///
@@ -131,7 +140,7 @@ public async Task RunCreationAsync()
{
string[] sources = Sources.ToArray();
- var compressor = new SevenZipCompressor
+ var compressor = new SevenZipCompressor()
{
ArchiveFormat = SevenZipArchiveFormat,
CompressionLevel = SevenZipCompressionLevel,
@@ -140,17 +149,28 @@ public async Task RunCreationAsync()
IncludeEmptyDirectories = true,
EncryptHeaders = true,
PreserveDirectoryRoot = sources.Length > 1,
- EventSynchronization = EventSynchronizationStrategy.AlwaysAsynchronous,
};
+
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)
{
@@ -167,6 +187,8 @@ public async Task RunCreationAsync()
await compressor.CompressFilesEncryptedAsync(ArchivePath, Password, files);
}
+ cts.Cancel();
+
return true;
}
catch (Exception ex)
@@ -174,25 +196,35 @@ public async Task RunCreationAsync()
var logger = Ioc.Default.GetRequiredService>();
logger?.LogWarning(ex, $"Error compressing folder: {ArchivePath}");
+ cts.Cancel();
+
return false;
}
}
- private void Compressor_CompressionFinished(object? sender, EventArgs e)
+ private void Compressor_FileCompressionStarted(object? sender, FileNameEventArgs e)
{
- if (++_processedItems == _itemsAmount)
- {
- _fileSystemProgress.ReportStatus(FileSystemStatusCode.Success);
- }
+ if (CancellationToken.IsCancellationRequested)
+ e.Cancel = true;
else
+ _sizeCalculator.ForceComputeFileSize(e.FilePath);
+ _threadingService.ExecuteOnUiThreadAsync(() =>
{
- _fileSystemProgress.Report(_processedItems * 100.0 / _itemsAmount);
- }
+ _fileSystemProgress.FileName = e.FileName;
+ _fileSystemProgress.Report();
+ });
+ }
+
+ private void Compressor_FileCompressionFinished(object? sender, EventArgs e)
+ {
+ _fileSystemProgress.AddProcessedItemsCount(1);
+ _fileSystemProgress.Report();
}
private void Compressor_Compressing(object? _, ProgressEventArgs e)
{
- _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/Archives/CompressHelper.cs b/src/Files.App/Utils/Archives/CompressHelper.cs
new file mode 100644
index 000000000000..2c6bb752a0cc
--- /dev/null
+++ b/src/Files.App/Utils/Archives/CompressHelper.cs
@@ -0,0 +1,105 @@
+// Copyright (c) 2023 Files Community
+// Licensed under the MIT License. See the LICENSE.
+
+using Files.Shared.Helpers;
+using System.IO;
+using Windows.Storage;
+
+namespace Files.App.Utils.Archives
+{
+ ///
+ /// Provides static helper for compressing archive.
+ ///
+ 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;
+ }
+
+ public static string DetermineArchiveNameFromSelection(IReadOnlyList selectedItems)
+ {
+ if (!selectedItems.Any())
+ return string.Empty;
+
+ return Path.GetFileName(
+ selectedItems.Count is 1
+ ? selectedItems[0].ItemPath
+ : Path.GetDirectoryName(selectedItems[0].ItemPath
+ )) ?? string.Empty;
+ }
+
+ public static (string[] Sources, string directory, string fileName) GetCompressDestination(IShellPage associatedInstance)
+ {
+ string[] sources = associatedInstance.SlimContentPage.SelectedItems
+ .Select(item => item.ItemPath)
+ .ToArray();
+
+ if (sources.Length is 0)
+ return (sources, string.Empty, string.Empty);
+
+ string directory = associatedInstance.FilesystemViewModel.WorkingDirectory.Normalize();
+
+
+ if (App.LibraryManager.TryGetLibrary(directory, out var library) && !library.IsEmpty)
+ directory = library.DefaultSaveFolder;
+
+ string fileName = Path.GetFileName(sources.Length is 1 ? sources[0] : directory);
+
+ return (sources, directory, fileName);
+ }
+
+ public static async Task CompressArchiveAsync(ICompressArchiveModel creator)
+ {
+ var archivePath = creator.GetArchivePath();
+
+ int index = 1;
+
+ while (File.Exists(archivePath) || Directory.Exists(archivePath))
+ archivePath = creator.GetArchivePath($" ({++index})");
+
+ creator.ArchivePath = archivePath;
+
+ var banner = StatusCenterHelper.AddCard_Compress(
+ creator.Sources,
+ archivePath.CreateEnumerable(),
+ ReturnResult.InProgress,
+ creator.Sources.Count());
+
+ creator.Progress = banner.ProgressEventSource;
+ creator.CancellationToken = banner.CancellationToken;
+
+ bool isSuccess = await creator.RunCreationAsync();
+
+ _statusCenterViewModel.RemoveItem(banner);
+
+ if (isSuccess)
+ {
+ StatusCenterHelper.AddCard_Compress(
+ creator.Sources,
+ archivePath.CreateEnumerable(),
+ ReturnResult.Success,
+ creator.Sources.Count());
+ }
+ else
+ {
+ NativeFileOperationsHelper.DeleteFileFromApp(archivePath);
+
+ StatusCenterHelper.AddCard_Compress(
+ creator.Sources,
+ archivePath.CreateEnumerable(),
+ ReturnResult.Failed,
+ creator.Sources.Count());
+ }
+ }
+ }
+}
diff --git a/src/Files.App/Utils/Archives/ArchiveHelpers.cs b/src/Files.App/Utils/Archives/DecompressHelper.cs
similarity index 57%
rename from src/Files.App/Utils/Archives/ArchiveHelpers.cs
rename to src/Files.App/Utils/Archives/DecompressHelper.cs
index 68f90eb00c20..569a0e1420d3 100644
--- a/src/Files.App/Utils/Archives/ArchiveHelpers.cs
+++ b/src/Files.App/Utils/Archives/DecompressHelper.cs
@@ -1,112 +1,99 @@
-// Copyright (c) 2023 Files Community
+// 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 ArchiveHelpers
+ 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 IThreadingService _threadingService = Ioc.Default.GetRequiredService();
- public static bool CanCompress(IReadOnlyList selectedItems)
+ private static async Task GetZipFile(BaseStorageFile archive, string password = "")
{
- return !CanDecompress(selectedItems) || selectedItems.Count > 1;
+ return await FilesystemTasks.Wrap(async () =>
+ {
+ var arch = new SevenZipExtractor(await archive.OpenStreamForReadAsync(), password);
+ return arch?.ArchiveFileData is null ? null : arch; // Force load archive (1665013614u)
+ });
}
- public static string DetermineArchiveNameFromSelection(IReadOnlyList selectedItems)
+ public static async Task IsArchiveEncrypted(BaseStorageFile archive)
{
- if (!selectedItems.Any())
- return string.Empty;
-
- return Path.GetFileName(
- selectedItems.Count is 1
- ? selectedItems[0].ItemPath
- : Path.GetDirectoryName(selectedItems[0].ItemPath
- )) ?? string.Empty;
+ using SevenZipExtractor? zipFile = await GetZipFile(archive);
+ if (zipFile is null)
+ return true;
+
+ return zipFile.ArchiveFileData.Any(file => file.Encrypted || file.Method.Contains("Crypto") || file.Method.Contains("AES"));
}
- public static (string[] Sources, string directory, string fileName) GetCompressDestination(IShellPage associatedInstance)
+ public static async Task ExtractArchiveAsync(BaseStorageFile archive, BaseStorageFolder destinationFolder, string password, IProgress progress, CancellationToken cancellationToken)
{
- string[] sources = associatedInstance.SlimContentPage.SelectedItems
- .Select(item => item.ItemPath)
- .ToArray();
-
- if (sources.Length is 0)
- return (sources, string.Empty, string.Empty);
-
- string directory = associatedInstance.FilesystemViewModel.WorkingDirectory.Normalize();
+ using SevenZipExtractor? zipFile = await GetZipFile(archive, password);
+ if (zipFile is null)
+ return;
+ if (cancellationToken.IsCancellationRequested) // Check if canceled
+ return;
- if (App.LibraryManager.TryGetLibrary(directory, out var library) && !library.IsEmpty)
- directory = library.DefaultSaveFolder;
+ // Fill files
- string fileName = Path.GetFileName(sources.Length is 1 ? sources[0] : directory);
+ byte[] buffer = new byte[4096];
+ int entriesAmount = zipFile.ArchiveFileData.Where(x => !x.IsDirectory).Count();
- return (sources, directory, fileName);
- }
+ StatusCenterItemProgressModel fsProgress = new(
+ progress,
+ enumerationCompleted: true,
+ FileSystemStatusCode.InProgress,
+ entriesAmount);
- public static async Task CompressArchiveAsync(IArchiveCreator creator)
- {
- var archivePath = creator.GetArchivePath();
-
- int index = 1;
- while (File.Exists(archivePath) || System.IO.Directory.Exists(archivePath))
- archivePath = creator.GetArchivePath($" ({++index})");
- creator.ArchivePath = archivePath;
-
- CancellationTokenSource compressionToken = new();
- StatusCenterItem banner = _statusCenterViewModel.AddItem
- (
- "CompressionInProgress".GetLocalizedResource(),
- archivePath,
- 0,
- ReturnResult.InProgress,
- FileOperationType.Compressed,
- compressionToken
- );
-
- creator.Progress = banner.ProgressEventSource;
- bool isSuccess = await creator.RunCreationAsync();
+ fsProgress.TotalSize = zipFile.ArchiveFileData.Select(x => (long)x.Size).Sum();
+ fsProgress.Report();
- _statusCenterViewModel.RemoveItem(banner);
+ zipFile.Extracting += (s, e) =>
+ {
+ if (fsProgress.TotalSize > 0)
+ fsProgress.Report(e.BytesProcessed / (double)fsProgress.TotalSize * 100);
+ };
+ zipFile.FileExtractionStarted += (s, e) =>
+ {
+ if (cancellationToken.IsCancellationRequested)
+ e.Cancel = true;
+ if (!e.FileInfo.IsDirectory)
+ {
+ _threadingService.ExecuteOnUiThreadAsync(() =>
+ {
+ fsProgress.FileName = e.FileInfo.FileName;
+ fsProgress.Report();
+ });
+ }
+ };
+ zipFile.FileExtractionFinished += (s, e) =>
+ {
+ if (!e.FileInfo.IsDirectory)
+ {
+ fsProgress.AddProcessedItemsCount(1);
+ fsProgress.Report();
+ }
+ };
- if (isSuccess)
+ try
{
- _statusCenterViewModel.AddItem
- (
- "CompressionCompleted".GetLocalizedResource(),
- string.Format("CompressionSucceded".GetLocalizedResource(), archivePath),
- 0,
- ReturnResult.Success,
- FileOperationType.Compressed
- );
+ await zipFile.ExtractArchiveAsync(destinationFolder.Path);
}
- else
+ catch (Exception ex)
{
- NativeFileOperationsHelper.DeleteFileFromApp(archivePath);
-
- _statusCenterViewModel.AddItem
- (
- "CompressionCompleted".GetLocalizedResource(),
- string.Format("CompressionFailed".GetLocalizedResource(), archivePath),
- 0,
- ReturnResult.Failed,
- FileOperationType.Compressed
- );
+ App.Logger.LogWarning(ex, $"Error extracting file: {archive.Name}");
+ return; // TODO: handle error
}
}
@@ -115,26 +102,20 @@ private static async Task ExtractArchiveAsync(BaseStorageFile archive, BaseStora
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);
+ var banner = StatusCenterHelper.AddCard_Decompress(
+ archive.Path.CreateEnumerable(),
+ destinationFolder.Path.CreateEnumerable(),
+ ReturnResult.InProgress);
- await FilesystemTasks.Wrap(() => ZipHelpers.ExtractArchiveAsync(archive, destinationFolder, password, banner.ProgressEventSource, extractCancellation.Token));
+ await FilesystemTasks.Wrap(() =>
+ ExtractArchiveAsync(archive, destinationFolder, password, banner.ProgressEventSource, banner.CancellationToken));
_statusCenterViewModel.RemoveItem(banner);
- _statusCenterViewModel.AddItem(
- "ExtractingCompleteText".GetLocalizedResource(),
- "ArchiveExtractionCompletedSuccessfullyText".GetLocalizedResource(),
- 0,
- ReturnResult.Success,
- FileOperationType.Extract);
+ StatusCenterHelper.AddCard_Decompress(
+ archive.Path.CreateEnumerable(),
+ destinationFolder.Path.CreateEnumerable(),
+ ReturnResult.Success);
}
public static async Task DecompressArchiveAsync(IShellPage associatedInstance)
@@ -149,7 +130,7 @@ public static async Task DecompressArchiveAsync(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 +178,7 @@ public static async Task DecompressArchiveHereAsync(IShellPage associatedInstanc
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(() => IsArchiveEncrypted(archive)))
{
DecompressArchiveDialog decompressArchiveDialog = new();
DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive)
@@ -232,7 +213,7 @@ public static async Task DecompressArchiveToChildFolderAsync(IShellPage associat
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/IArchiveCreator.cs b/src/Files.App/Utils/Archives/ICompressArchiveModel.cs
similarity index 90%
rename from src/Files.App/Utils/Archives/IArchiveCreator.cs
rename to src/Files.App/Utils/Archives/ICompressArchiveModel.cs
index b7af5a9134b1..b324ec1cc82b 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.
@@ -53,6 +53,11 @@ public interface IArchiveCreator
///
IProgress Progress { get; set; }
+ ///
+ /// Cancellation request.
+ ///
+ CancellationToken CancellationToken { get; set; }
+
///
/// Get path which target will be archived to.
///
diff --git a/src/Files.App/Utils/Archives/ZipHelpers.cs b/src/Files.App/Utils/Archives/ZipHelpers.cs
deleted file mode 100644
index b50e84d1149a..000000000000
--- a/src/Files.App/Utils/Archives/ZipHelpers.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2023 Files Community
-// Licensed under the MIT License. See the LICENSE.
-
-using Microsoft.Extensions.Logging;
-using SevenZip;
-using System.IO;
-
-namespace Files.App.Utils.Archives
-{
- public static class ZipHelpers
- {
- private static async Task GetZipFile(BaseStorageFile archive, string password = "")
- {
- return await FilesystemTasks.Wrap(async () =>
- {
- var arch = new SevenZipExtractor(await archive.OpenStreamForReadAsync(), password);
- return arch?.ArchiveFileData is null ? null : arch; // Force load archive (1665013614u)
- });
- }
-
- public static async Task IsArchiveEncrypted(BaseStorageFile archive)
- {
- using SevenZipExtractor? zipFile = await GetZipFile(archive);
- if (zipFile is null)
- return true;
-
- return zipFile.ArchiveFileData.Any(file => file.Encrypted || file.Method.Contains("Crypto") || file.Method.Contains("AES"));
- }
-
- public static async Task ExtractArchiveAsync(BaseStorageFile archive, BaseStorageFolder destinationFolder, string password, IProgress progress, CancellationToken cancellationToken)
- {
- using SevenZipExtractor? zipFile = await GetZipFile(archive, password);
- 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;
- int entriesFinished = 0;
- var minimumTime = new DateTime(1);
-
- StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress, entriesAmount);
- fsProgress.Report();
-
- foreach (var entry in fileEntries)
- {
- 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
-
- // We don't close hFile because FileStream.Dispose() already does that
- using (FileStream destinationStream = new FileStream(hFile, FileAccess.Write))
- {
- try
- {
- await zipFile.ExtractFileAsync(entry.Index, destinationStream);
- }
- catch (Exception ex)
- {
- App.Logger.LogWarning(ex, $"Error extracting file: {filePath}");
- return; // TODO: handle error
- }
- }
-
- _ = new FileInfo(filePath)
- {
- CreationTime = entry.CreationTime > minimumTime && entry.CreationTime < entry.LastWriteTime ? entry.CreationTime : entry.LastWriteTime,
- LastWriteTime = entry.LastWriteTime,
- };
-
- entriesFinished++;
- fsProgress.ProcessedItemsCount = entriesFinished;
- fsProgress.Report();
- }
- }
- }
-}
diff --git a/src/Files.App/Utils/RecycleBin/RecycleBinHelpers.cs b/src/Files.App/Utils/RecycleBin/RecycleBinHelpers.cs
index 62156d1f869b..d47ffc0d0a7c 100644
--- a/src/Files.App/Utils/RecycleBin/RecycleBinHelpers.cs
+++ b/src/Files.App/Utils/RecycleBin/RecycleBinHelpers.cs
@@ -45,6 +45,7 @@ public static bool IsPathUnderRecycleBin(string path)
public static async Task EmptyRecycleBinAsync()
{
+ // Display confirmation dialog
var ConfirmEmptyBinDialog = new ContentDialog()
{
Title = "ConfirmEmptyBinDialogTitle".GetLocalizedResource(),
@@ -54,35 +55,21 @@ public static async Task EmptyRecycleBinAsync()
DefaultButton = ContentDialogButton.Primary
};
- if (userSettingsService.FoldersSettingsService.DeleteConfirmationPolicy is DeleteConfirmationPolicies.Never
- || await ConfirmEmptyBinDialog.TryShowAsync() == ContentDialogResult.Primary)
+ // If the operation is approved by the user
+ if (userSettingsService.FoldersSettingsService.DeleteConfirmationPolicy is DeleteConfirmationPolicies.Never ||
+ await ConfirmEmptyBinDialog.TryShowAsync() == ContentDialogResult.Primary)
{
- string bannerTitle = "EmptyRecycleBin".GetLocalizedResource();
- var banner = _statusCenterViewModel.AddItem(
- bannerTitle,
- "EmptyingRecycleBin".GetLocalizedResource(),
- 0,
- ReturnResult.InProgress,
- FileOperationType.Delete);
- bool opSucceded = await Task.Run(() => Shell32.SHEmptyRecycleBin(IntPtr.Zero, null, Shell32.SHERB.SHERB_NOCONFIRMATION | Shell32.SHERB.SHERB_NOPROGRESSUI).Succeeded);
+ var banner = StatusCenterHelper.AddCard_EmptyRecycleBin(ReturnResult.InProgress);
+
+ bool bResult = await Task.Run(() => Shell32.SHEmptyRecycleBin(IntPtr.Zero, null, Shell32.SHERB.SHERB_NOCONFIRMATION | Shell32.SHERB.SHERB_NOPROGRESSUI).Succeeded);
_statusCenterViewModel.RemoveItem(banner);
- if (opSucceded)
- _statusCenterViewModel.AddItem(
- bannerTitle,
- "BinEmptyingSucceded".GetLocalizedResource(),
- 100,
- ReturnResult.Success,
- FileOperationType.Delete);
+ if (bResult)
+ StatusCenterHelper.AddCard_EmptyRecycleBin(ReturnResult.Success);
else
- _statusCenterViewModel.AddItem(
- bannerTitle,
- "BinEmptyingFailed".GetLocalizedResource(),
- 100,
- ReturnResult.Failed,
- FileOperationType.Delete);
+ StatusCenterHelper.AddCard_EmptyRecycleBin(ReturnResult.Failed);
}
}
diff --git a/src/Files.App/Utils/Shell/ShellFileOperations2.cs b/src/Files.App/Utils/Shell/ShellFileOperations2.cs
new file mode 100644
index 000000000000..ef36ddafe494
--- /dev/null
+++ b/src/Files.App/Utils/Shell/ShellFileOperations2.cs
@@ -0,0 +1,742 @@
+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 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();
+
+ /// 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}";
+ }
+
+ 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/StatusCenterHelper.cs b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs
index 7090ccb675ac..d631e8c1be89 100644
--- a/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs
+++ b/src/Files.App/Utils/StatusCenter/StatusCenterHelper.cs
@@ -3,193 +3,674 @@
namespace Files.App.Utils.StatusCenter
{
+ ///
+ /// Provide static helper for the 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)
+ public static StatusCenterItem AddCard_Copy(
+ ReturnResult returnStatus,
+ IEnumerable source,
+ IEnumerable destination,
+ long itemsCount = 0,
+ long totalSize = 0)
{
- var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault()?.Path);
+ string? sourceDir = string.Empty;
+ string? destinationDir = string.Empty;
+
+ if (source is not null && source.Any())
+ sourceDir = PathNormalization.GetParentDir(source.First().Path);
- if (canceled)
- {
- if (permanently)
- {
- 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
- {
- 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);
- }
+ if (destination is not null && destination.Any())
+ destinationDir = PathNormalization.GetParentDir(destination.First());
+
+ if (returnStatus == ReturnResult.Cancelled)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_CopyCanceled_Header",
+ "StatusCenter_CopyCanceled_SubHeader",
+ ReturnResult.Cancelled,
+ FileOperationType.Copy,
+ source?.Select(x => x.Path),
+ destination,
+ true,
+ itemsCount,
+ totalSize);
}
else if (returnStatus == ReturnResult.InProgress)
{
- if (permanently)
- {
- // 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,
- new CancellationTokenSource());
- }
- else
- {
- // "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,
- new CancellationTokenSource());
- }
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_CopyInProgress_Header",
+ "StatusCenter_CopyInProgress_SubHeader",
+ ReturnResult.InProgress,
+ FileOperationType.Copy,
+ source?.Select(x => x.Path),
+ destination,
+ true,
+ itemsCount,
+ totalSize,
+ new CancellationTokenSource());
}
else if (returnStatus == ReturnResult.Success)
{
- if (permanently)
- {
- 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
- {
- 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);
- }
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_CopyComplete_Header",
+ "StatusCenter_CopyComplete_SubHeader",
+ ReturnResult.Success,
+ FileOperationType.Copy,
+ source?.Select(x => x.Path),
+ destination,
+ true,
+ itemsCount,
+ totalSize);
}
else
{
- if (permanently)
- {
- 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
- {
- 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);
- }
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_CopyFailed_Header",
+ "StatusCenter_CopyFailed_SubHeader",
+ ReturnResult.Failed,
+ FileOperationType.Copy,
+ source?.Select(x => x.Path),
+ destination,
+ true,
+ itemsCount,
+ totalSize);
}
}
- public static StatusCenterItem PostBanner_Copy(IEnumerable source, IEnumerable destination, ReturnResult returnStatus, bool canceled, int itemsCopied)
+ 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());
- if (canceled)
+ if (returnStatus == ReturnResult.Cancelled)
{
- 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,
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_MoveCanceled_Header",
+ "StatusCenter_MoveCanceled_SubHeader",
+ ReturnResult.Cancelled,
+ FileOperationType.Move,
+ source.Select(x => x.Path),
+ destination,
+ true,
+ itemsCount,
+ totalSize);
+ }
+ else if (returnStatus == ReturnResult.InProgress)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_MoveInProgress_Header",
+ "StatusCenter_MoveInProgress_SubHeader",
+ ReturnResult.InProgress,
+ FileOperationType.Move,
+ source.Select(x => x.Path),
+ destination,
+ true,
+ itemsCount,
+ totalSize,
+ new CancellationTokenSource());
+ }
+ else if (returnStatus == ReturnResult.Success)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_MoveComplete_Header",
+ "StatusCenter_MoveComplete_SubHeader",
+ ReturnResult.Success,
+ FileOperationType.Move,
+ source.Select(x => x.Path),
+ destination,
+ true,
+ itemsCount,
+ totalSize);
+ }
+ else
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_MoveFailed_Header",
+ "StatusCenter_MoveFailed_SubHeader",
+ ReturnResult.Failed,
+ FileOperationType.Move,
+ source.Select(x => x.Path),
+ destination,
+ true,
+ itemsCount,
+ totalSize);
+ }
+ }
+
+ public static StatusCenterItem AddCard_Recycle(
+ ReturnResult returnStatus,
+ IEnumerable? source,
+ long itemsCount = 0,
+ long totalSize = 0)
+ {
+ string? sourceDir = string.Empty;
+
+ if (source is not null && source.Any())
+ sourceDir = PathNormalization.GetParentDir(source.First().Path);
+
+ if (returnStatus == ReturnResult.Cancelled)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_RecycleCanceled_Header",
+ string.Empty,
ReturnResult.Cancelled,
- FileOperationType.Copy);
+ FileOperationType.Recycle,
+ source?.Select(x => x.Path),
+ null,
+ true,
+ itemsCount,
+ totalSize);
}
else if (returnStatus == ReturnResult.InProgress)
{
- return StatusCenterViewModel.AddItem(
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_RecycleInProgress_Header",
string.Empty,
- string.Format(source.Count() > 1 ? "StatusCopyingItemsDetails_Plural".GetLocalizedResource() : "StatusCopyingItemsDetails_Singular".GetLocalizedResource(), source.Count(), destinationDir),
- 0,
ReturnResult.InProgress,
- FileOperationType.Copy, new CancellationTokenSource());
+ FileOperationType.Recycle,
+ source?.Select(x => x.Path),
+ null,
+ true,
+ itemsCount,
+ totalSize,
+ 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),
- 0,
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_RecycleComplete_Header",
+ string.Empty,
ReturnResult.Success,
- FileOperationType.Copy);
+ FileOperationType.Recycle,
+ source?.Select(x => x.Path),
+ null,
+ true,
+ itemsCount,
+ totalSize);
}
else
{
- return StatusCenterViewModel.AddItem(
- "StatusCopyFailed".GetLocalizedResource(),
- string.Format(source.Count() > 1 ? "StatusCopyFailedDetails_Plural".GetLocalizedResource() : "StatusCopyFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir),
- 0,
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_RecycleFailed_Header",
+ string.Empty,
ReturnResult.Failed,
- FileOperationType.Copy);
+ FileOperationType.Recycle,
+ source?.Select(x => x.Path),
+ null,
+ true,
+ itemsCount,
+ totalSize);
}
}
- public static StatusCenterItem PostBanner_Move(IEnumerable source, IEnumerable destination, ReturnResult returnStatus, bool canceled, int itemsMoved)
+ public static StatusCenterItem AddCard_Delete(
+ ReturnResult returnStatus,
+ IEnumerable? source,
+ long itemsCount = 0,
+ long totalSize = 0)
{
- var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault()?.Path);
+ string? sourceDir = string.Empty;
+
+ if (source is not null && source.Any())
+ sourceDir = PathNormalization.GetParentDir(source.First().Path);
+
+ if (returnStatus == ReturnResult.Cancelled)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_DeleteCanceled_Header",
+ string.Empty,
+ ReturnResult.Cancelled,
+ FileOperationType.Delete,
+ source?.Select(x => x.Path) ?? string.Empty.CreateEnumerable(),
+ null,
+ true,
+ itemsCount,
+ totalSize);
+ }
+ else if (returnStatus == ReturnResult.InProgress)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_DeleteInProgress_Header",
+ string.Empty,
+ ReturnResult.InProgress,
+ FileOperationType.Delete,
+ source?.Select(x => x.Path),
+ null,
+ true,
+ itemsCount,
+ totalSize,
+ new CancellationTokenSource());
+ }
+ else if (returnStatus == ReturnResult.Success)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_DeleteComplete_Header",
+ string.Empty,
+ ReturnResult.Success,
+ FileOperationType.Delete,
+ source?.Select(x => x.Path),
+ null,
+ true,
+ itemsCount,
+ totalSize);
+ }
+ else
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_DeleteFailed_Header",
+ "StatusCenter_DeleteFailed_SubHeader",
+ ReturnResult.Failed,
+ FileOperationType.Delete,
+ source?.Select(x => x.Path),
+ null,
+ true,
+ itemsCount,
+ totalSize);
+ }
+ }
+
+ 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
+
+ var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault());
+ var destinationDir = PathNormalization.GetParentDir(destination.FirstOrDefault());
+
+ if (returnStatus == ReturnResult.Cancelled)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_CompressCanceled_Header",
+ "StatusCenter_CompressCanceled_SubHeader",
+ ReturnResult.Cancelled,
+ FileOperationType.Compressed,
+ source,
+ destination,
+ false,
+ itemsCount,
+ totalSize);
+ }
+ else if (returnStatus == ReturnResult.InProgress)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_CompressInProgress_Header",
+ "StatusCenter_CompressInProgress_SubHeader",
+ ReturnResult.InProgress,
+ FileOperationType.Compressed,
+ source,
+ destination,
+ true,
+ itemsCount,
+ totalSize,
+ new CancellationTokenSource());
+ }
+ else if (returnStatus == ReturnResult.Success)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_CompressComplete_Header",
+ "StatusCenter_CompressComplete_SubHeader",
+ ReturnResult.Success,
+ FileOperationType.Compressed,
+ source,
+ destination,
+ false,
+ itemsCount,
+ totalSize);
+ }
+ else
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_CompressFailed_Header",
+ "StatusCenter_CompressFailed_SubHeader",
+ ReturnResult.Failed,
+ FileOperationType.Compressed,
+ source,
+ destination,
+ false,
+ itemsCount,
+ totalSize);
+ }
+ }
+
+ 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
+
+ var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault());
var destinationDir = PathNormalization.GetParentDir(destination.FirstOrDefault());
- if (canceled)
+ if (returnStatus == ReturnResult.Cancelled)
{
- 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,
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_DecompressCanceled_Header",
+ "StatusCenter_DecompressCanceled_SubHeader",
ReturnResult.Cancelled,
- FileOperationType.Move);
+ FileOperationType.Extract,
+ source,
+ destination,
+ false,
+ itemsCount,
+ totalSize);
}
else if (returnStatus == ReturnResult.InProgress)
{
- return StatusCenterViewModel.AddItem(
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_DecompressInProgress_Header",
+ "StatusCenter_DecompressInProgress_SubHeader",
+ ReturnResult.InProgress,
+ FileOperationType.Extract,
+ source,
+ destination,
+ true,
+ itemsCount,
+ totalSize,
+ new CancellationTokenSource());
+ }
+ else if (returnStatus == ReturnResult.Success)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_DecompressComplete_Header",
+ "StatusCenter_DecompressComplete_SubHeader",
+ ReturnResult.Success,
+ FileOperationType.Extract,
+ source,
+ destination,
+ false,
+ itemsCount,
+ totalSize);
+ }
+ else
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_DecompressFailed_Header",
+ "StatusCenter_DecompressFailed_SubHeader",
+ ReturnResult.Failed,
+ FileOperationType.Extract,
+ source,
+ destination,
+ false,
+ itemsCount,
+ totalSize);
+ }
+ }
+
+ public static StatusCenterItem AddCard_EmptyRecycleBin(
+ ReturnResult returnStatus,
+ long itemsCount = 0,
+ long totalSize = 0)
+ {
+ // Currently not supported accurate progress report for emptying the recycle bin
+
+ if (returnStatus == ReturnResult.Cancelled)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_EmptyRecycleBinCancel_Header",
+ string.Empty,
+ ReturnResult.Cancelled,
+ FileOperationType.Delete,
+ null,
+ null,
+ false,
+ itemsCount,
+ totalSize);
+ }
+ else if (returnStatus == ReturnResult.InProgress)
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_EmptyRecycleBinInProgress_Header",
string.Empty,
- string.Format(source.Count() > 1 ? "StatusMovingItemsDetails_Plural".GetLocalizedResource() : "StatusMovingItemsDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir),
- 0,
ReturnResult.InProgress,
- FileOperationType.Move, new CancellationTokenSource());
+ FileOperationType.Delete,
+ null,
+ null,
+ false,
+ itemsCount,
+ totalSize,
+ 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),
- 0,
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_EmptyRecycleBinComplete_Header",
+ string.Empty,
ReturnResult.Success,
- FileOperationType.Move);
+ FileOperationType.Delete,
+ null,
+ null,
+ false,
+ itemsCount,
+ totalSize);
}
else
{
- return StatusCenterViewModel.AddItem(
- "StatusMoveFailed".GetLocalizedResource(),
- string.Format(source.Count() > 1 ? "StatusMoveFailedDetails_Plural".GetLocalizedResource() : "StatusMoveFailedDetails_Singular".GetLocalizedResource(), source.Count(), sourceDir, destinationDir),
- 0,
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_EmptyRecycleBinFailed_Header",
+ "StatusCenter_EmptyRecycleBinFailed_SubHeader",
ReturnResult.Failed,
- FileOperationType.Move);
+ FileOperationType.Delete,
+ null,
+ null,
+ false,
+ itemsCount,
+ totalSize);
+ }
+ }
+
+ public static StatusCenterItem AddCard_Prepare()
+ {
+ return _statusCenterViewModel.AddItem(
+ "StatusCenter_Prepare_Header",
+ string.Empty,
+ ReturnResult.InProgress,
+ FileOperationType.Prepare,
+ null,
+ null,
+ false);
+ }
+
+ public static void UpdateCardStrings(StatusCenterItem card)
+ {
+ // Aren't used for now
+ string sourcePath = string.Empty;
+ string destinationPath = string.Empty;
+
+ string sourceFileName = string.Empty;
+ string sourceDirName = string.Empty;
+ string destinationDirName = string.Empty;
+
+ if (card.Source is not null && card.Source.Any())
+ {
+ sourcePath = PathNormalization.GetParentDir(card.Source.First());
+ sourceDirName = sourcePath.Split('\\').Last();
+ sourceFileName = card.Source.First().Split('\\').Last();
+ }
+
+ if (card.Destination is not null && card.Destination.Any())
+ {
+ destinationPath = PathNormalization.GetParentDir(card.Destination.First());
+ destinationDirName = card.Destination.First().Split('\\').Last();
+ }
+
+ string headerString = string.IsNullOrWhiteSpace(card.HeaderStringResource) ? string.Empty : card.HeaderStringResource.GetLocalizedResource();
+ string subHeaderString = string.IsNullOrWhiteSpace(card.SubHeaderStringResource) ? string.Empty : card.SubHeaderStringResource.GetLocalizedResource();
+
+ // Update string resources
+ switch (card.Operation)
+ {
+ case FileOperationType.Copy:
+ {
+ if (headerString is not null)
+ {
+ card.Header = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.Success => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.Failed => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.InProgress => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ _ => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ };
+ }
+ if (subHeaderString is not null)
+ {
+ card.SubHeader = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.Success => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.Failed => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.InProgress => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ _ => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ };
+ }
+ break;
+ }
+ case FileOperationType.Move:
+ {
+ if (headerString is not null)
+ {
+ card.Header = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.Success => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.Failed => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.InProgress => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ _ => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ };
+ }
+ if (subHeaderString is not null)
+ {
+ card.SubHeader = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.Success => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.Failed => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.InProgress => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ _ => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ };
+ }
+ break;
+ }
+ case FileOperationType.Delete:
+ {
+ if (headerString is not null)
+ {
+ card.Header = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ ReturnResult.Success => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ ReturnResult.Failed => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ ReturnResult.InProgress => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ _ => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ };
+ }
+ if (subHeaderString is not null)
+ {
+ card.SubHeader = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ ReturnResult.Success => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ ReturnResult.Failed => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ ReturnResult.InProgress => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ _ => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ };
+ }
+ break;
+ }
+ case FileOperationType.Recycle:
+ {
+ if (headerString is not null)
+ {
+ card.Header = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ ReturnResult.Success => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ ReturnResult.Failed => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ ReturnResult.InProgress => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ _ => string.Format(headerString, card.TotalItemsCount, sourceDirName),
+ };
+ }
+ if (subHeaderString is not null)
+ {
+ card.SubHeader = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ ReturnResult.Success => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ ReturnResult.Failed => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ ReturnResult.InProgress => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ _ => string.Format(subHeaderString, card.TotalItemsCount, sourcePath),
+ };
+ }
+ break;
+ }
+ case FileOperationType.Extract:
+ {
+ if (headerString is not null)
+ {
+ card.Header = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(headerString, sourceFileName, destinationDirName),
+ ReturnResult.Success => string.Format(headerString, sourceFileName, destinationDirName),
+ ReturnResult.Failed => string.Format(headerString, sourceFileName, destinationDirName),
+ ReturnResult.InProgress => string.Format(headerString, sourceFileName, destinationDirName),
+ _ => string.Format(headerString, sourceFileName, destinationDirName),
+ };
+ }
+ if (subHeaderString is not null)
+ {
+ card.SubHeader = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(subHeaderString, sourceFileName, sourcePath, destinationPath),
+ ReturnResult.Success => string.Format(subHeaderString, sourceFileName, sourcePath, destinationPath),
+ ReturnResult.Failed => string.Format(subHeaderString, sourceFileName, sourcePath, destinationPath),
+ ReturnResult.InProgress => string.Format(subHeaderString, sourceFileName, sourcePath, destinationPath),
+ _ => string.Format(subHeaderString, sourceFileName, sourcePath, destinationPath),
+ };
+ }
+ break;
+ }
+ case FileOperationType.Compressed:
+ {
+ if (headerString is not null)
+ {
+ card.Header = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.Success => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.Failed => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ ReturnResult.InProgress => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ _ => string.Format(headerString, card.TotalItemsCount, destinationDirName),
+ };
+ }
+ if (subHeaderString is not null)
+ {
+ card.SubHeader = card.FileSystemOperationReturnResult switch
+ {
+ ReturnResult.Cancelled => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.Success => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.Failed => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ ReturnResult.InProgress => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ _ => string.Format(subHeaderString, card.TotalItemsCount, sourcePath, destinationPath),
+ };
+ }
+ break;
+ }
}
}
}
diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs
index d59602459062..2125b82adf2d 100644
--- a/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs
+++ b/src/Files.App/Utils/StatusCenter/StatusCenterItem.cs
@@ -2,16 +2,37 @@
// 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;
+using LiveChartsCore.Defaults;
+using Microsoft.UI.Xaml.Media;
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 int _ProgressPercentage;
+ public int ProgressPercentage
+ {
+ get => _ProgressPercentage;
+ set
+ {
+ ProgressPercentageText = $"{value}%";
+ SetProperty(ref _ProgressPercentage, value);
+ }
+ }
+
private string? _Header;
public string? Header
{
@@ -19,6 +40,7 @@ public string? Header
set => SetProperty(ref _Header, value);
}
+ // Currently, shown on the tooltip
private string? _SubHeader;
public string? SubHeader
{
@@ -26,13 +48,44 @@ public string? SubHeader
set => SetProperty(ref _SubHeader, value);
}
- private int _ProgressPercentage;
- public int ProgressPercentage
+ private string? _Message;
+ public string? Message
{
- get => _ProgressPercentage;
- set => SetProperty(ref _ProgressPercentage, value);
+ 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,40 +98,48 @@ public bool IsExpanded
}
}
- private string _AnimatedIconState = "NormalOff";
- public string AnimatedIconState
+ // This property is used for AnimatedIcon state
+ private string? _AnimatedIconState;
+ public string? AnimatedIconState
{
get => _AnimatedIconState;
set => SetProperty(ref _AnimatedIconState, value);
}
- private bool _IsInProgress; // Item type is InProgress && is the operation in progress
- public bool IsInProgress
+ // 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 => _IsInProgress;
- set
- {
- if (SetProperty(ref _IsInProgress, value))
- OnPropertyChanged(nameof(SubHeader));
- }
+ get => _IsSpeedAndProgressAvailable;
+ set => SetProperty(ref _IsSpeedAndProgressAvailable, value);
}
- private bool _IsCancelled;
- public bool IsCancelled
+ // 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
{
- get => _IsCancelled;
- set => SetProperty(ref _IsCancelled, value);
+ get => _IsIndeterminateProgress;
+ set => SetProperty(ref _IsIndeterminateProgress, value);
}
- public CancellationToken CancellationToken
- => _operationCancellationToken?.Token ?? default;
-
+ // 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
- => _operationCancellationToken is not null;
+ {
+ get => _IsCancelable;
+ set => SetProperty(ref _IsCancelable, value);
+ }
- public string HeaderBody { get; set; }
+ // This property is not updated for now. Should be removed.
+ private StatusCenterItemProgressModel _Progress = null!;
+ public StatusCenterItemProgressModel Progress
+ {
+ get => _Progress;
+ set => SetProperty(ref _Progress, value);
+ }
- public ReturnResult FileSystemOperationReturnResult { get; set; }
+ public ReturnResult FileSystemOperationReturnResult { get; private set; }
public FileOperationType Operation { get; private set; }
@@ -86,7 +147,36 @@ public bool IsCancelable
public StatusCenterItemIconKind ItemIconKind { get; private set; }
- public readonly StatusCenterItemProgressModel Progress;
+ public long TotalSize { get; private set; }
+
+ public long TotalItemsCount { get; private set; }
+
+ public bool IsInProgress { get; private set; }
+
+ public IEnumerable? Source { get; private set; }
+
+ public IEnumerable? Destination { get; private set; }
+
+ public string? HeaderStringResource { get; private set; }
+
+ public string? SubHeaderStringResource { get; private set; }
+
+ public ObservableCollection? SpeedGraphValues { get; private set; }
+
+ public ObservableCollection? SpeedGraphBackgroundValues { get; private set; }
+
+ public ObservableCollection? SpeedGraphSeries { get; private set; }
+
+ public ObservableCollection? SpeedGraphXAxes { 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;
public readonly Progress ProgressEventSource;
@@ -94,39 +184,127 @@ 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 headerResource,
+ string subHeaderResource,
+ ReturnResult status,
+ FileOperationType operation,
+ IEnumerable? source,
+ IEnumerable? destination,
+ bool canProvideProgress = false,
+ long itemsCount = 0,
+ long totalSize = 0,
+ CancellationTokenSource? operationCancellationToken = default)
{
_operationCancellationToken = operationCancellationToken;
- SubHeader = message;
- HeaderBody = title;
- Header = title;
+ Header = headerResource == string.Empty ? headerResource : headerResource.GetLocalizedResource();
+ HeaderStringResource = headerResource;
+ SubHeader = subHeaderResource == string.Empty ? subHeaderResource : subHeaderResource.GetLocalizedResource();
+ SubHeaderStringResource = subHeaderResource;
FileSystemOperationReturnResult = status;
Operation = operation;
ProgressEventSource = new Progress(ReportProgress);
Progress = new(ProgressEventSource, status: FileSystemStatusCode.InProgress);
-
+ IsCancelable = _operationCancellationToken is not null;
+ TotalItemsCount = itemsCount;
+ TotalSize = totalSize;
+ IconBackgroundCircleBorderOpacity = 1;
+ AnimatedIconState = "NormalOff";
+ SpeedGraphValues = new();
+ SpeedGraphBackgroundValues = new();
CancelCommand = new RelayCommand(ExecuteCancelCommand);
+ Message = "ProcessingItems".GetLocalizedResource();
+ Source = source;
+ Destination = destination;
+
+ // Get the graph color
+ if (App.Current.Resources["App.Theme.FillColorAttentionBrush"] is not SolidColorBrush accentBrush)
+ return;
+ // Initialize graph series
+ SpeedGraphSeries = new()
+ {
+ new LineSeries
+ {
+ Values = SpeedGraphValues,
+ GeometrySize = 0d,
+ DataPadding = new(0, 0),
+ IsHoverable = false,
+
+ // Stroke
+ Stroke = new SolidColorPaint(
+ new(accentBrush.Color.R, accentBrush.Color.G, accentBrush.Color.B),
+ 1f),
+
+ // 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(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)),
+ }
+ };
+
+ // Initialize X axes of the graph
+ SpeedGraphXAxes = new()
+ {
+ new Axis
+ {
+ Padding = new Padding(0, 0),
+ Labels = new List(),
+ MaxLimit = 100,
+ ShowSeparatorLines = false,
+ }
+ };
+
+ // Initialize Y axes of the graph
+ SpeedGraphYAxes = new()
+ {
+ new Axis
+ {
+ Padding = new Padding(0, 0),
+ Labels = new List(),
+ ShowSeparatorLines = false,
+ }
+ };
+
+ // Set icon and initialize string resources
switch (FileSystemOperationReturnResult)
{
case ReturnResult.InProgress:
{
+ IsSpeedAndProgressAvailable = canProvideProgress;
IsInProgress = true;
+ IsIndeterminateProgress = !canProvideProgress;
+ IconBackgroundCircleBorderOpacity = 0.1d;
- 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(),
- };
+ if (Operation is FileOperationType.Prepare)
+ Header = "StatusCenter_PrepareInProgress".GetLocalizedResource();
- Header = $"{HeaderBody} ({progress}%)";
ItemKind = StatusCenterItemKind.InProgress;
-
ItemIconKind = Operation switch
{
FileOperationType.Extract => StatusCenterItemIconKind.Extract,
@@ -134,6 +312,7 @@ public StatusCenterItem(string message, string title, float progress, ReturnResu
FileOperationType.Move => StatusCenterItemIconKind.Move,
FileOperationType.Delete => StatusCenterItemIconKind.Delete,
FileOperationType.Recycle => StatusCenterItemIconKind.Recycle,
+ FileOperationType.Compressed => StatusCenterItemIconKind.Compress,
_ => StatusCenterItemIconKind.Delete,
};
@@ -141,33 +320,45 @@ public StatusCenterItem(string message, string title, float progress, ReturnResu
}
case ReturnResult.Success:
{
- if (string.IsNullOrWhiteSpace(HeaderBody) || string.IsNullOrWhiteSpace(SubHeader))
- throw new NotImplementedException();
-
- Header = HeaderBody;
ItemKind = StatusCenterItemKind.Successful;
ItemIconKind = StatusCenterItemIconKind.Successful;
break;
}
case ReturnResult.Failed:
- case ReturnResult.Cancelled:
{
- if (string.IsNullOrWhiteSpace(HeaderBody) || string.IsNullOrWhiteSpace(SubHeader))
- throw new NotImplementedException();
-
- Header = HeaderBody;
ItemKind = StatusCenterItemKind.Error;
ItemIconKind = StatusCenterItemIconKind.Error;
+ break;
+ }
+ case ReturnResult.Cancelled:
+ {
+ IconBackgroundCircleBorderOpacity = 0.1d;
+
+ ItemKind = StatusCenterItemKind.Canceled;
+ 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.Compress,
+ _ => StatusCenterItemIconKind.Delete,
+ };
+
break;
}
}
+
+ StatusCenterHelper.UpdateCardStrings(this);
}
private void ReportProgress(StatusCenterItemProgressModel value)
{
- // The Operation has been cancelled. Do update neither progress value nor text.
+ // The operation has been canceled.
+ // Do update neither progress value nor text.
if (CancellationToken.IsCancellationRequested)
return;
@@ -175,54 +366,153 @@ private void ReportProgress(StatusCenterItemProgressModel value)
if (value.Status is FileSystemStatusCode status)
FileSystemOperationReturnResult = status.ToStatus();
- // Get if the operation is in progress
- IsInProgress = (value.Status & FileSystemStatusCode.InProgress) != 0;
-
+ // Update the footer message, percentage, processing item name
if (value.Percentage is double p)
{
if (ProgressPercentage != value.Percentage)
{
- Header = $"{HeaderBody} ({ProgressPercentage:0}%)";
ProgressPercentage = (int)p;
+
+ if (Operation == FileOperationType.Recycle ||
+ Operation == FileOperationType.Delete ||
+ Operation == FileOperationType.Compressed)
+ {
+ Message =
+ $"{string.Format(
+ "StatusCenter_ProcessedItems_Header".GetLocalizedResource(),
+ value.ProcessedItemsCount,
+ value.ItemsCount)}";
+ }
+ else
+ {
+ Message =
+ $"{string.Format(
+ "StatusCenter_ProcessedSize_Header".GetLocalizedResource(),
+ value.ProcessedSize.ToSizeString(),
+ value.TotalSize.ToSizeString())}";
+ }
}
+
+ if (CurrentProcessingItemName != value.FileName)
+ CurrentProcessingItemName = value.FileName;
}
- else if (value.EnumerationCompleted)
+
+ // Set total count
+ if (TotalItemsCount < value.ItemsCount)
+ TotalItemsCount = value.ItemsCount;
+
+ // Set total size
+ if (TotalSize < value.TotalSize)
+ TotalSize = value.TotalSize;
+
+ // Update UI for strings
+ StatusCenterHelper.UpdateCardStrings(this);
+
+ // Graph item point
+ ObservablePoint point;
+
+ // Set speed text and percentage
+ switch (value.TotalSize, value.ItemsCount)
{
- switch (value.TotalSize, value.ItemsCount)
- {
- // 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}%)";
- 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}%)";
- break;
- // In progress, displaying items count
- case (_, not 0):
- ProgressPercentage = (int)(value.ProcessedItemsCount * 100.0 / value.ItemsCount);
- Header = $"{HeaderBody} ({value.ProcessedItemsCount} / {value.ItemsCount}: {ProgressPercentage}%)";
- break;
- default:
- Header = $"{HeaderBody}";
- break;
- }
+ // In progress, displaying items count & processed size
+ case (not 0, not 0):
+ ProgressPercentage = Math.Clamp((int)(value.ProcessedSize * 100.0 / value.TotalSize), 0, 100);
+
+ SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s";
+
+ point = new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed);
+
+ break;
+ // In progress, displaying processed size
+ case (not 0, _):
+ ProgressPercentage = Math.Clamp((int)(value.ProcessedSize * 100.0 / value.TotalSize), 0, 100);
+
+ SpeedText = $"{value.ProcessingSizeSpeed.ToSizeString()}/s";
+
+ point = new(value.ProcessedSize * 100.0 / value.TotalSize, value.ProcessingSizeSpeed);
+
+ break;
+ // In progress, displaying items count
+ case (_, not 0):
+ ProgressPercentage = Math.Clamp((int)(value.ProcessedItemsCount * 100.0 / value.ItemsCount), 0, 100);
+
+ SpeedText = $"{value.ProcessingItemsCountSpeed:0} items/s";
+
+ point = new(value.ProcessedItemsCount * 100.0 / value.ItemsCount, value.ProcessingItemsCountSpeed);
+
+ break;
+ default:
+ point = new(ProgressPercentage, value.ProcessingItemsCountSpeed);
+
+ SpeedText = (value.ProcessedSize, value.ProcessedItemsCount) switch
+ {
+ (not 0, not 0) => $"{value.ProcessingSizeSpeed.ToSizeString()}/s",
+ (not 0, _) => $"{value.ProcessingSizeSpeed.ToSizeString()}/s",
+ (_, not 0) => $"{value.ProcessingItemsCountSpeed:0} items/s",
+ _ => "N/A",
+ };
+ break;
+ }
+
+ 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;
}
- else
+
+ CurrentHighestPointValue ??= point.Y;
+
+ if (!isSamePoint)
{
- Header = (value.ProcessedSize, value.ProcessedItemsCount) switch
+ // 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)
{
- (not 0, not 0) => $"{HeaderBody} ({value.ProcessedItemsCount} ({value.ProcessedSize.ToSizeString()}) / ...)",
- (not 0, _) => $"{HeaderBody} ({value.ProcessedSize.ToSizeString()} / ...)",
- (_, not 0) => $"{HeaderBody} ({value.ProcessedItemsCount} / ...)",
- _ => $"{HeaderBody}",
- };
+ // 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);
+
+ // Add percentage to the header
+ if (!IsIndeterminateProgress)
+ Header = $"{Header} ({ProgressPercentage}%)";
+
+ // Update UI of the address bar
_viewModel.NotifyChanges();
- _viewModel.UpdateAverageProgressValue();
+ }
+
+ 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()
@@ -230,8 +520,11 @@ public void ExecuteCancelCommand()
if (IsCancelable)
{
_operationCancellationToken?.Cancel();
- IsCancelled = true;
- Header = $"{HeaderBody} ({"canceling".GetLocalizedResource()})";
+ IsIndeterminateProgress = true;
+ IsCancelable = false;
+ IsExpanded = false;
+ IsSpeedAndProgressAvailable = false;
+ Header = $"{"Canceling".GetLocalizedResource()} - {Header}";
}
}
}
diff --git a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs
index f88a36830fbe..fa1d76d23192 100644
--- a/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs
+++ b/src/Files.App/Utils/StatusCenter/StatusCenterItemProgressModel.cs
@@ -9,13 +9,18 @@ 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;
private readonly ConcurrentDictionary _dirtyTracker;
- private readonly IntervalSampler _sampler;
+ private readonly IntervalSampler _sampler, _sampler2;
private bool _criticalReport;
@@ -69,7 +74,6 @@ public long TotalSize
public long ProcessedSize
{
get => _ProcessedSize;
- set => SetProperty(ref _ProcessedSize, value);
}
private long _ItemsCount;
@@ -83,10 +87,10 @@ public long ItemsCount
public long ProcessedItemsCount
{
get => _ProcessedItemsCount;
- set => SetProperty(ref _ProcessedItemsCount, value);
}
public double ProcessingSizeSpeed { get; private set; }
+
public double ProcessingItemsCountSpeed { get; private set; }
private DateTimeOffset _StartTime;
@@ -112,20 +116,32 @@ public DateTimeOffset CompletedTime
public event PropertyChangedEventHandler? PropertyChanged;
- public StatusCenterItemProgressModel(IProgress? progress, bool enumerationCompleted = false, FileSystemStatusCode? status = null, long itemsCount = 0, long totalSize = 0, int samplerInterval = 100)
+ public StatusCenterItemProgressModel(IProgress? progress, bool enumerationCompleted = false, FileSystemStatusCode? status = null, long itemsCount = 0, int samplerInterval = 100)
{
// Initialize
_progress = progress;
_sampler = new(samplerInterval);
+ _sampler2 = new(samplerInterval);
_dirtyTracker = new();
EnumerationCompleted = enumerationCompleted;
Status = status;
ItemsCount = itemsCount;
- TotalSize = totalSize;
StartTime = DateTimeOffset.Now;
_previousReportTime = StartTime - TimeSpan.FromSeconds(1);
}
+ public void AddProcessedItemsCount(long value)
+ {
+ Interlocked.Add(ref _ProcessedItemsCount, value);
+ _dirtyTracker[nameof(ProcessedItemsCount)] = true;
+ }
+
+ public void SetProcessedSize(long value)
+ {
+ Interlocked.Exchange(ref _ProcessedSize, value);
+ _dirtyTracker[nameof(ProcessedSize)] = true;
+ }
+
private void SetProperty(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
field = value;
@@ -138,8 +154,7 @@ 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 &&
ProcessedSize == TotalSize &&
@@ -150,9 +165,30 @@ TotalSize is not 0 ||
_Status = FileSystemStatusCode.Success;
}
+ // Set time at completed when succeed
if (_Status is FileSystemStatusCode.Success)
CompletedTime = DateTimeOffset.Now;
+ if (percentage is not null && Percentage != percentage)
+ {
+ SetProcessedSize((long)(TotalSize * percentage / 100));
+
+ if (_sampler2.CheckNow())
+ {
+ ProcessingSizeSpeed = (ProcessedSize - _previousProcessedSize) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds;
+ ProcessingItemsCountSpeed = (ProcessedItemsCount - _previousProcessedItemsCount) / (DateTimeOffset.Now - _previousReportTime).TotalSeconds;
+
+ _dirtyTracker[nameof(ProcessingSizeSpeed)] = true;
+ _dirtyTracker[nameof(ProcessingItemsCountSpeed)] = true;
+
+ _previousReportTime = DateTimeOffset.Now;
+ _previousProcessedSize = ProcessedSize;
+ _previousProcessedItemsCount = ProcessedItemsCount;
+ }
+
+ Percentage = percentage;
+ }
+
if (_criticalReport || _sampler.CheckNow())
{
_criticalReport = false;
@@ -164,14 +200,8 @@ TotalSize is not 0 ||
PropertyChanged?.Invoke(this, new(propertyName));
}
}
- 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)));
+
_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/Operations/FileOperationsHelpers.cs
similarity index 81%
rename from src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs
rename to src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.cs
index cb67b489a45d..81768464c294 100644
--- a/src/Files.App/Utils/Storage/Helpers/FileOperationsHelpers.cs
+++ b/src/Files.App/Utils/Storage/Operations/FileOperationsHelpers.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 Files.Shared.Helpers;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
@@ -42,7 +43,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 +112,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
@@ -199,26 +200,48 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
{
operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID;
- StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress);
+ StatusCenterItemProgressModel fsProgress = new(
+ progress,
+ false,
+ FileSystemStatusCode.InProgress);
+
+ var cts = new CancellationTokenSource();
+ var sizeCalculator = new FileSizeCalculator(fileToDeletePath);
+ var sizeTask = sizeCalculator.ComputeSizeAsync(cts.Token);
+ sizeTask.ContinueWith(_ =>
+ {
+ fsProgress.TotalSize = 0;
+ fsProgress.ItemsCount = sizeCalculator.ItemsCount;
+ fsProgress.EnumerationCompleted = true;
+ fsProgress.Report();
+ });
+
fsProgress.Report();
progressHandler ??= new();
return Win32API.StartSTATask(async () =>
{
- using var op = new ShellFileOperations();
- op.Options = ShellFileOperations.OperationFlags.Silent
- | ShellFileOperations.OperationFlags.NoConfirmation
- | ShellFileOperations.OperationFlags.NoErrorUI;
+ using var op = new ShellFileOperations2();
+
+ 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();
@@ -228,6 +251,7 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
if (!SafetyExtensions.IgnoreExceptions(() =>
{
using var shi = new ShellItem(fileToDeletePath[i]);
+
op.QueueDeleteOperation(shi);
}))
{
@@ -244,15 +268,28 @@ 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);
+
+ sizeCalculator.ForceComputeFileSize(e.SourceItem.FileSystemPath);
+ fsProgress.FileName = e.SourceItem.Name;
+ fsProgress.Report();
};
+
+ // Right after deleted item
op.PostDeleteItem += (s, e) =>
{
+ if (!e.SourceItem.IsFolder)
+ {
+ if (sizeCalculator.TryGetComputedFileSize(e.SourceItem.FileSystemPath, out _))
+ fsProgress.AddProcessedItemsCount(1);
+ }
+
shellOperationResult.Items.Add(new ShellOperationItemResult()
{
Succeeded = e.Result.Succeeded,
@@ -260,15 +297,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);
};
@@ -284,6 +325,8 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
progressHandler.RemoveOperation(operationID);
+ cts.Cancel();
+
return (await deleteTcs.Task, shellOperationResult);
});
}
@@ -296,7 +339,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
@@ -359,33 +402,56 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
{
operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID;
- StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress);
+ StatusCenterItemProgressModel fsProgress = new(
+ progress,
+ false,
+ FileSystemStatusCode.InProgress);
+
+ var cts = new CancellationTokenSource();
+ var sizeCalculator = new FileSizeCalculator(fileToMovePath);
+ var sizeTask = sizeCalculator.ComputeSizeAsync(cts.Token);
+ sizeTask.ContinueWith(_ =>
+ {
+ fsProgress.TotalSize = sizeCalculator.Size;
+ fsProgress.ItemsCount = sizeCalculator.ItemsCount;
+ fsProgress.EnumerationCompleted = true;
+ fsProgress.Report();
+ });
+
fsProgress.Report();
progressHandler ??= new();
return Win32API.StartSTATask(async () =>
{
- using var op = new ShellFileOperations();
+ using var op = new ShellFileOperations2();
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]));
}))
{
@@ -403,8 +469,22 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
progressHandler.AddOperation(operationID);
var moveTcs = new TaskCompletionSource();
+
+ op.PreMoveItem += (s, e) =>
+ {
+ sizeCalculator.ForceComputeFileSize(e.SourceItem.FileSystemPath);
+ fsProgress.FileName = e.SourceItem.Name;
+ fsProgress.Report();
+ };
+
op.PostMoveItem += (s, e) =>
{
+ if (!e.SourceItem.IsFolder)
+ {
+ if (sizeCalculator.TryGetComputedFileSize(e.SourceItem.FileSystemPath, out _))
+ fsProgress.AddProcessedItemsCount(1);
+ }
+
shellOperationResult.Items.Add(new ShellOperationItemResult()
{
Succeeded = e.Result.Succeeded,
@@ -412,15 +492,19 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
Destination = e.DestFolder.GetParsingPath() is not null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.GetParsingPath(), e.Name) : null,
HResult = (int)e.Result
});
+
+ UpdateFileTagsDb(e, "move");
};
- op.PostMoveItem += (_, e) => UpdateFileTagsDb(e, "move");
- op.FinishOperations += (s, e) => moveTcs.TrySetResult(e.Result.Succeeded);
+
+ 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);
};
@@ -436,6 +520,8 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
progressHandler.RemoveOperation(operationID);
+ cts.Cancel();
+
return (await moveTcs.Task, shellOperationResult);
});
}
@@ -444,34 +530,58 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
{
operationID = string.IsNullOrEmpty(operationID) ? Guid.NewGuid().ToString() : operationID;
- StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress);
+ StatusCenterItemProgressModel fsProgress = new(
+ progress,
+ false,
+ FileSystemStatusCode.InProgress);
+
+ var cts = new CancellationTokenSource();
+ var sizeCalculator = new FileSizeCalculator(fileToCopyPath);
+ var sizeTask = sizeCalculator.ComputeSizeAsync(cts.Token);
+ sizeTask.ContinueWith(_ =>
+ {
+ fsProgress.TotalSize = sizeCalculator.Size;
+ fsProgress.ItemsCount = sizeCalculator.ItemsCount;
+ fsProgress.EnumerationCompleted = true;
+ fsProgress.Report();
+ });
+
fsProgress.Report();
progressHandler ??= new();
return Win32API.StartSTATask(async () =>
{
- using var op = new ShellFileOperations();
+ using var op = new ShellFileOperations2();
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]));
}))
{
@@ -489,8 +599,22 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
progressHandler.AddOperation(operationID);
var copyTcs = new TaskCompletionSource();
+
+ op.PreCopyItem += (s, e) =>
+ {
+ sizeCalculator.ForceComputeFileSize(e.SourceItem.FileSystemPath);
+ fsProgress.FileName = e.SourceItem.Name;
+ fsProgress.Report();
+ };
+
op.PostCopyItem += (s, e) =>
{
+ if (!e.SourceItem.IsFolder)
+ {
+ if (sizeCalculator.TryGetComputedFileSize(e.SourceItem.FileSystemPath, out _))
+ fsProgress.AddProcessedItemsCount(1);
+ }
+
shellOperationResult.Items.Add(new ShellOperationItemResult()
{
Succeeded = e.Result.Succeeded,
@@ -498,15 +622,19 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
Destination = e.DestFolder.GetParsingPath() is not null && !string.IsNullOrEmpty(e.Name) ? Path.Combine(e.DestFolder.GetParsingPath(), e.Name) : null,
HResult = (int)e.Result
});
+
+ UpdateFileTagsDb(e, "copy");
};
- op.PostCopyItem += (_, e) => UpdateFileTagsDb(e, "copy");
- op.FinishOperations += (s, e) => copyTcs.TrySetResult(e.Result.Succeeded);
+
+ 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);
};
@@ -522,6 +650,8 @@ public static Task SetClipboard(string[] filesToCopy, DataPackageOperation opera
progressHandler.RemoveOperation(operationID);
+ cts.Cancel();
+
return (await copyTcs.Task, shellOperationResult);
});
}
@@ -576,7 +706,7 @@ public static void TryCancelOperation(string operationId)
ipf.GetUrl(out var retVal);
return retVal;
});
- return string.IsNullOrEmpty(targetPath) ?
+ return string.IsNullOrEmpty(targetPath) ?
new ShellLinkItem
{
TargetPath = string.Empty,
@@ -678,7 +808,7 @@ public static bool SetLinkIcon(string filePath, string iconFile, int iconIndex)
if (attribs.Any() && attribs[0] is byte[] objectSid)
return new SecurityIdentifier(objectSid, 0).Value;
}
- catch {}
+ catch { }
}
}
@@ -729,7 +859,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)
@@ -811,9 +941,9 @@ private class ProgressHandler : Disposable
{
private readonly ManualResetEvent operationsCompletedEvent;
- private class OperationWithProgress
+ public class OperationWithProgress
{
- public int Progress { get; set; }
+ public double Progress { get; set; }
public bool Canceled { get; set; }
}
@@ -855,7 +985,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))
{
diff --git a/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs b/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs
new file mode 100644
index 000000000000..bf8144d2c28c
--- /dev/null
+++ b/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs
@@ -0,0 +1,115 @@
+using System.Collections.Concurrent;
+using System.IO;
+using Vanara.PInvoke;
+using static Vanara.PInvoke.Kernel32;
+
+namespace Files.App.Utils.Storage.Operations
+{
+ internal class FileSizeCalculator
+ {
+ private readonly string[] _paths;
+ private readonly ConcurrentDictionary _computedFiles = new();
+ private long _size;
+
+ public long Size => _size;
+ public int ItemsCount => _computedFiles.Count;
+ public bool Completed { get; private set; }
+
+ public FileSizeCalculator(params string[] paths)
+ {
+ _paths = paths;
+ }
+
+ public async Task ComputeSizeAsync(CancellationToken cancellationToken = default)
+ {
+ await Parallel.ForEachAsync(_paths, cancellationToken, async (path, token) => await Task.Factory.StartNew(() =>
+ {
+ var queue = new Queue();
+ if (!NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory))
+ {
+ ComputeFileSize(path);
+ }
+ else
+ {
+ queue.Enqueue(path);
+
+ while (queue.TryDequeue(out var directory))
+ {
+ using var hFile = FindFirstFileEx(
+ directory + "\\*.*",
+ FINDEX_INFO_LEVELS.FindExInfoBasic,
+ out WIN32_FIND_DATA findData,
+ FINDEX_SEARCH_OPS.FindExSearchNameMatch,
+ IntPtr.Zero,
+ FIND_FIRST.FIND_FIRST_EX_LARGE_FETCH);
+
+ if (!hFile.IsInvalid)
+ {
+ do
+ {
+ if ((findData.dwFileAttributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
+ // Skip symbolic links and junctions
+ continue;
+
+ var itemPath = Path.Combine(directory, findData.cFileName);
+
+ if ((findData.dwFileAttributes & FileAttributes.Directory) != FileAttributes.Directory)
+ {
+ ComputeFileSize(itemPath);
+ }
+ else if (findData.cFileName != "." && findData.cFileName != "..")
+ {
+ queue.Enqueue(itemPath);
+ }
+
+ if (token.IsCancellationRequested)
+ break;
+ }
+ while (FindNextFile(hFile, out findData));
+ }
+ }
+ }
+ }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default));
+ }
+
+ private long ComputeFileSize(string path)
+ {
+ if (_computedFiles.TryGetValue(path, out var size))
+ {
+ return size;
+ }
+
+ using var hFile = CreateFile(
+ path,
+ Kernel32.FileAccess.FILE_READ_ATTRIBUTES,
+ FileShare.Read,
+ null,
+ FileMode.Open,
+ 0,
+ null);
+
+ if (!hFile.IsInvalid)
+ {
+ if (GetFileSizeEx(hFile, out size) && _computedFiles.TryAdd(path, size))
+ {
+ Interlocked.Add(ref _size, size);
+ }
+ }
+
+ return size;
+ }
+
+ public void ForceComputeFileSize(string path)
+ {
+ if (!NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory))
+ {
+ ComputeFileSize(path);
+ }
+ }
+
+ public bool TryGetComputedFileSize(string path, out long size)
+ {
+ return _computedFiles.TryGetValue(path, out size);
+ }
+ }
+}
diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs
index 2eafeb906de5..3a7b0f232ff0 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();
@@ -119,19 +92,24 @@ public async Task DeleteItemsAsync(IEnumerable item.Path).Any(path => RecycleBinHelpers.IsPathUnderRecycleBin(path));
var canBeSentToBin = !deleteFromRecycleBin && await RecycleBinHelpers.HasRecycleBin(source.FirstOrDefault()?.Path);
- if (showDialog is DeleteConfirmationPolicies.Always
- || showDialog is DeleteConfirmationPolicies.PermanentOnly && (permanently || !canBeSentToBin))
+ if (showDialog is DeleteConfirmationPolicies.Always ||
+ showDialog is DeleteConfirmationPolicies.PermanentOnly &&
+ (permanently || !canBeSentToBin))
{
var incomingItems = new List();
List? binItems = null;
+
foreach (var src in source)
{
if (RecycleBinHelpers.IsPathUnderRecycleBin(src.Path))
{
binItems ??= await RecycleBinHelpers.EnumerateRecycleBin();
- if (!binItems.IsEmpty()) // Might still be null because we're deserializing the list from Json
+
+ // Might still be null because we're deserializing the list from Json
+ if (!binItems.IsEmpty())
{
- var matchingItem = binItems.FirstOrDefault(x => x.RecyclePath == src.Path); // Get original file name
+ // Get original file name
+ var matchingItem = binItems.FirstOrDefault(x => x.RecyclePath == src.Path);
incomingItems.Add(new FileSystemDialogDefaultItemViewModel() { SourcePath = src.Path, DisplayName = matchingItem?.FileName ?? src.Name });
}
}
@@ -150,20 +128,26 @@ public async Task DeleteItemsAsync(IEnumerable();
+ // Return if the result isn't delete
if (await dialogService.ShowDialogAsync(dialogViewModel) != DialogResult.Primary)
- return ReturnResult.Cancelled; // Return if the result isn't delete
+ return ReturnResult.Cancelled;
// Delete selected items if the result is Yes
permanently = dialogViewModel.DeletePermanently;
}
else
{
- permanently |= !canBeSentToBin; // delete permanently if recycle bin is not supported
+ // Delete permanently if recycle bin is not supported
+ permanently |= !canBeSentToBin;
}
- // post the status banner
- var banner = StatusCenterHelper.PostBanner_Delete(source, returnStatus, permanently, false, 0);
- banner.ProgressEventSource.ProgressChanged += (s, e) => returnStatus = returnStatus < ReturnResult.Failed ? e.Status!.Value.ToStatus() : returnStatus;
+ // Add an in-progress card in the StatusCenter
+ var banner = permanently
+ ? StatusCenterHelper.AddCard_Delete(returnStatus, source)
+ : StatusCenterHelper.AddCard_Recycle(returnStatus, source);
+
+ banner.ProgressEventSource.ProgressChanged += (s, e)
+ => returnStatus = returnStatus < ReturnResult.Failed ? e.Status!.Value.ToStatus() : returnStatus;
var token = banner.CancellationToken;
@@ -176,15 +160,23 @@ public async Task DeleteItemsAsync(IEnumerable await jumpListService.RemoveFolderAsync(x.Path)); // Remove items from jump list
+ // Remove items from jump list
+ source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path));
+
+ var itemsCount = banner.TotalItemsCount;
+ // Remove the in-progress card from the StatusCenter
_statusCenterViewModel.RemoveItem(banner);
sw.Stop();
- StatusCenterHelper.PostBanner_Delete(source, returnStatus, permanently, token.IsCancellationRequested, itemsDeleted);
+ // Add a complete card in the StatusCenter
+ _ = permanently
+ ? StatusCenterHelper.AddCard_Delete(token.IsCancellationRequested ? ReturnResult.Cancelled : returnStatus, source, itemsCount)
+ : StatusCenterHelper.AddCard_Recycle(token.IsCancellationRequested ? ReturnResult.Cancelled : returnStatus, source, itemsCount);
return returnStatus;
}
@@ -198,10 +190,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 +225,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 +280,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);
@@ -308,8 +293,13 @@ public async Task CopyItemsAsync(IEnumerable
var returnStatus = ReturnResult.InProgress;
- var banner = StatusCenterHelper.PostBanner_Copy(source, destination, returnStatus, false, 0);
- banner.ProgressEventSource.ProgressChanged += (s, e) => returnStatus = returnStatus < ReturnResult.Failed ? e.Status!.Value.ToStatus() : returnStatus;
+ var banner = StatusCenterHelper.AddCard_Copy(
+ returnStatus,
+ source,
+ destination);
+
+ banner.ProgressEventSource.ProgressChanged += (s, e)
+ => returnStatus = returnStatus < ReturnResult.Failed ? e.Status!.Value.ToStatus() : returnStatus;
var token = banner.CancellationToken;
@@ -318,14 +308,15 @@ public async Task CopyItemsAsync(IEnumerable
if (cancelOperation)
{
_statusCenterViewModel.RemoveItem(banner);
-
return ReturnResult.Cancelled;
}
itemManipulationModel?.ClearSelection();
IStorageHistory history = await filesystemOperations.CopyItemsAsync((IList)source, (IList)destination, collisions, banner.ProgressEventSource, token);
+
banner.Progress.ReportStatus(FileSystemStatusCode.Success);
+
await Task.Yield();
if (registerHistory && history is not null && source.Any((item) => !string.IsNullOrWhiteSpace(item.Path)))
@@ -343,11 +334,16 @@ public async Task CopyItemsAsync(IEnumerable
}
App.HistoryWrapper.AddHistory(history);
}
- var itemsCopied = history?.Source.Count ?? 0;
+
+ var itemsCount = banner.TotalItemsCount;
_statusCenterViewModel.RemoveItem(banner);
- StatusCenterHelper.PostBanner_Copy(source, destination, returnStatus, token.IsCancellationRequested, itemsCopied);
+ StatusCenterHelper.AddCard_Copy(
+ token.IsCancellationRequested ? ReturnResult.Cancelled : returnStatus,
+ source,
+ destination,
+ itemsCount);
return returnStatus;
}
@@ -418,10 +414,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);
@@ -435,11 +427,13 @@ public async Task MoveItemsAsync(IEnumerable
var returnStatus = ReturnResult.InProgress;
- var sourceDir = PathNormalization.GetParentDir(source.FirstOrDefault()?.Path);
- var destinationDir = PathNormalization.GetParentDir(destination.FirstOrDefault());
+ var banner = StatusCenterHelper.AddCard_Move(
+ returnStatus,
+ source,
+ destination);
- var banner = StatusCenterHelper.PostBanner_Move(source, destination, returnStatus, false, 0);
- banner.ProgressEventSource.ProgressChanged += (s, e) => returnStatus = returnStatus < ReturnResult.Failed ? e.Status!.Value.ToStatus() : returnStatus;
+ banner.ProgressEventSource.ProgressChanged += (s, e)
+ => returnStatus = returnStatus < ReturnResult.Failed ? e.Status!.Value.ToStatus() : returnStatus;
var token = banner.CancellationToken;
@@ -458,7 +452,9 @@ public async Task MoveItemsAsync(IEnumerable
itemManipulationModel?.ClearSelection();
IStorageHistory history = await filesystemOperations.MoveItemsAsync((IList)source, (IList)destination, collisions, banner.ProgressEventSource, token);
+
banner.Progress.ReportStatus(FileSystemStatusCode.Success);
+
await Task.Yield();
if (registerHistory && history is not null && source.Any((item) => !string.IsNullOrWhiteSpace(item.Path)))
@@ -474,17 +470,24 @@ public async Task MoveItemsAsync(IEnumerable
}
}
}
+
App.HistoryWrapper.AddHistory(history);
}
- int itemsMoved = history?.Source.Count ?? 0;
- source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); // Remove items from jump list
+ // Remove items from jump list
+ source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path));
+
+ var itemsCount = banner.TotalItemsCount;
_statusCenterViewModel.RemoveItem(banner);
sw.Stop();
- StatusCenterHelper.PostBanner_Move(source, destination, returnStatus, token.IsCancellationRequested, itemsMoved);
+ StatusCenterHelper.AddCard_Move(
+ token.IsCancellationRequested ? ReturnResult.Cancelled : returnStatus,
+ source,
+ destination,
+ itemsCount);
return returnStatus;
}
@@ -528,10 +531,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 +594,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 +642,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 +722,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"));
@@ -866,10 +858,6 @@ public static bool ContainsRestrictedFileName(string input)
return false;
}
- #endregion Public Helpers
-
- #region IDisposable
-
public void Dispose()
{
filesystemOperations?.Dispose();
@@ -877,7 +865,5 @@ public void Dispose()
associatedInstance = null;
filesystemOperations = null;
}
-
- #endregion IDisposable
}
}
diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs
index da25e51de966..0ff585aae922 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)
@@ -89,7 +89,7 @@ public FilesystemOperations(IShellPage associatedInstance)
break;
}
- fsProgress.ProcessedItemsCount = 1;
+ fsProgress.AddProcessedItemsCount(1);
fsProgress.ReportStatus(fsResult);
return item is not null
? (new StorageHistory(FileOperationType.CreateNew, item.CreateList(), null), item.Item)
@@ -109,7 +109,11 @@ 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);
+
fsProgress.Report();
if (destination.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal))
@@ -125,7 +129,6 @@ await DialogDisplayHelper.ShowDialogAsync(
}
IStorageItem copiedItem = null;
- //long itemSize = await FilesystemHelpers.GetItemSize(await source.ToStorageItem(associatedInstance));
if (source.ItemType == FilesystemItemType.Directory)
{
@@ -289,7 +292,11 @@ 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);
+
fsProgress.Report();
if (source.Path == destination)
@@ -321,8 +328,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
@@ -478,7 +483,11 @@ 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);
+
fsProgress.Report();
bool deleteFromRecycleBin = RecycleBinHelpers.IsPathUnderRecycleBin(source.Path);
@@ -560,12 +569,13 @@ 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);
@@ -665,11 +675,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();
@@ -683,7 +694,7 @@ public async Task RestoreItemsFromTrashAsync(IList CopyItemsAsync(IList so
token));
}
- fsProgress.ProcessedItemsCount++;
+ fsProgress.AddProcessedItemsCount(1);
fsProgress.Report();
}
@@ -877,7 +888,7 @@ public async Task MoveItemsAsync(IList so
token));
}
- fsProgress.ProcessedItemsCount++;
+ fsProgress.AddProcessedItemsCount(1);
fsProgress.Report();
}
@@ -912,7 +923,7 @@ public async Task DeleteItemsAsync(IList
permanently = RecycleBinHelpers.IsPathUnderRecycleBin(source[i].Path) || originalPermanently;
rawStorageHistory.Add(await DeleteAsync(source[i], null, permanently, token));
- fsProgress.ProcessedItemsCount++;
+ fsProgress.AddProcessedItemsCount(1);
fsProgress.Report();
}
diff --git a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs
index 2d88fd8f0b0c..93517117beeb 100644
--- a/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs
+++ b/src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs
@@ -44,7 +44,12 @@ public async Task CopyItemsAsync(IList so
return await _filesystemOperations.CopyItemsAsync(source, destination, collisions, progress, cancellationToken);
}
- StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress);
+ StatusCenterItemProgressModel fsProgress = new(
+ progress,
+ true,
+ FileSystemStatusCode.InProgress,
+ source.Count());
+
fsProgress.Report();
var sourceNoSkip = source.Zip(collisions, (src, coll) => new { src, coll }).Where(item => item.coll != FileNameConflictResolveOptionType.Skip).Select(item => item.src);
@@ -297,7 +302,7 @@ public async Task CreateShortcutItemsAsync(IList new { src, dest, index }).Where(x => !string.IsNullOrEmpty(x.src.Path) && !string.IsNullOrEmpty(x.dest));
+ var items = source.Zip(destination, (src, dest) => new { src, dest }).Where(x => !string.IsNullOrEmpty(x.src.Path) && !string.IsNullOrEmpty(x.dest));
foreach (var item in items)
{
var result = await FileOperationsHelpers.CreateOrUpdateLinkAsync(item.dest, item.src.Path);
@@ -311,7 +316,7 @@ public async Task CreateShortcutItemsAsync(IList DeleteItemsAsync(IList
return await _filesystemOperations.DeleteItemsAsync(source, progress, permanently, cancellationToken);
}
- StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress);
+ StatusCenterItemProgressModel fsProgress = new(
+ progress,
+ true,
+ FileSystemStatusCode.InProgress,
+ source.Count());
+
fsProgress.Report();
var deleteFilePaths = source.Select(s => s.Path).Distinct();
@@ -457,7 +467,12 @@ public async Task MoveItemsAsync(IList so
return await _filesystemOperations.MoveItemsAsync(source, destination, collisions, progress, cancellationToken);
}
- StatusCenterItemProgressModel fsProgress = new(progress, true, FileSystemStatusCode.InProgress);
+ StatusCenterItemProgressModel fsProgress = new(
+ progress,
+ true,
+ FileSystemStatusCode.InProgress,
+ source.Count());
+
fsProgress.Report();
var sourceNoSkip = source.Zip(collisions, (src, coll) => new { src, coll }).Where(item => item.coll != FileNameConflictResolveOptionType.Skip).Select(item => item.src);
diff --git a/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs b/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs
index 1056e83e4af2..fe760ec702ef 100644
--- a/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs
+++ b/src/Files.App/ViewModels/UserControls/StatusCenterViewModel.cs
@@ -64,9 +64,29 @@ public StatusCenterViewModel()
StatusCenterItems.CollectionChanged += (s, e) => OnPropertyChanged(nameof(HasAnyItem));
}
- public StatusCenterItem AddItem(string title, string message, int initialProgress, ReturnResult status, FileOperationType operation, CancellationTokenSource cancellationTokenSource = null)
+ public StatusCenterItem AddItem(
+ string headerResource,
+ string subHeaderResource,
+ ReturnResult status,
+ FileOperationType operation,
+ IEnumerable? source,
+ IEnumerable? destination,
+ bool canProvideProgress = true,
+ long itemsCount = 0,
+ long totalSize = 0,
+ CancellationTokenSource cancellationTokenSource = null)
{
- var banner = new StatusCenterItem(message, title, initialProgress, status, operation, cancellationTokenSource);
+ var banner = new StatusCenterItem(
+ headerResource,
+ subHeaderResource,
+ status,
+ operation,
+ source,
+ destination,
+ canProvideProgress,
+ itemsCount,
+ totalSize,
+ cancellationTokenSource);
StatusCenterItems.Insert(0, banner);
NewItemAdded?.Invoke(this, banner);
@@ -76,12 +96,12 @@ public StatusCenterItem AddItem(string title, string message, int initialProgres
return banner;
}
- public bool RemoveItem(StatusCenterItem banner)
+ public bool RemoveItem(StatusCenterItem card)
{
- if (!StatusCenterItems.Contains(banner))
+ if (!StatusCenterItems.Contains(card))
return false;
- StatusCenterItems.Remove(banner);
+ StatusCenterItems.Remove(card);
NotifyChanges();
@@ -106,6 +126,8 @@ public void NotifyChanges()
OnPropertyChanged(nameof(HasAnyItem));
OnPropertyChanged(nameof(InfoBadgeState));
OnPropertyChanged(nameof(InfoBadgeValue));
+
+ UpdateAverageProgressValue();
}
public void UpdateAverageProgressValue()
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 ced7d4de30f6..000000000000
Binary files a/src/Files.App/nupkgs/SevenZipSharp.1.0.0.nupkg and /dev/null differ
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 000000000000..246c5fdf3f1c
Binary files /dev/null and b/src/Files.App/nupkgs/SevenZipSharp.1.0.2.nupkg differ
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,
}
diff --git a/src/Files.Core/Data/Enums/StatusCenterItemKind.cs b/src/Files.Core/Data/Enums/StatusCenterItemKind.cs
index 7771a5a850b0..997811b64fb7 100644
--- a/src/Files.Core/Data/Enums/StatusCenterItemKind.cs
+++ b/src/Files.Core/Data/Enums/StatusCenterItemKind.cs
@@ -11,5 +11,6 @@ public enum StatusCenterItemKind
InProgress,
Successful,
Error,
+ Canceled,
}
}