Skip to content

Feature: Added support for extracting multiple archives at same time. #9957

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/Files.App/Helpers/ContextFlyoutItemHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -947,14 +947,15 @@ public static List<ContextMenuFlyoutItemViewModel> GetBaseItemMenuItems(BaseLayo
{
Text = "BaseLayoutItemContextFlyoutExtractionOptions".GetLocalizedResource(),
Glyph = "\xF11A",
ShowItem = selectedItems.Count == 1 && (selectedItems.First().IsZipItem || (selectedItems.First().PrimaryItemAttribute == StorageItemTypes.File && FileExtensionHelpers.IsZipFile(selectedItems.First().FileExtension))),
ShowItem = selectedItems.Any() && selectedItems.All(x => x.IsZipItem) || selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.File && FileExtensionHelpers.IsZipFile(x.FileExtension)),
ShowInSearchPage = true,
GlyphFontFamilyName = "CustomGlyph",
Items = new List<ContextMenuFlyoutItemViewModel>()
{
new ContextMenuFlyoutItemViewModel()
{
Text = "BaseLayoutItemContextFlyoutExtractFilesOption".GetLocalizedResource(),
ShowItem = selectedItems.Count == 1,
Command = commandsViewModel.DecompressArchiveCommand,
Glyph = "\xF11A",
GlyphFontFamilyName = "CustomGlyph",
Expand All @@ -970,7 +971,9 @@ public static List<ContextMenuFlyoutItemViewModel> GetBaseItemMenuItems(BaseLayo
},
new ContextMenuFlyoutItemViewModel()
{
Text = string.Format("BaseLayoutItemContextFlyoutExtractToChildFolder".GetLocalizedResource(), Path.GetFileNameWithoutExtension(selectedItems.First().ItemName)),
Text = selectedItems.Count > 1
? string.Format("BaseLayoutItemContextFlyoutExtractToChildFolder".GetLocalizedResource(), "*")
: string.Format("BaseLayoutItemContextFlyoutExtractToChildFolder".GetLocalizedResource(), Path.GetFileNameWithoutExtension(selectedItems.First().ItemName)),
Command = commandsViewModel.DecompressArchiveToChildFolderCommand,
Glyph = "\xF11A",
GlyphFontFamilyName = "CustomGlyph",
Expand Down
178 changes: 61 additions & 117 deletions src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#nullable disable warnings

using Files.Shared;
using Files.App.Dialogs;
using Files.Shared.Enums;
Expand Down Expand Up @@ -623,151 +625,93 @@ public async Task DecompressArchive()
{
BaseStorageFile archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(associatedInstance.SlimContentPage.SelectedItem.ItemPath);

if (archive != null)
{
DecompressArchiveDialog decompressArchiveDialog = new();
DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive);
decompressArchiveDialog.ViewModel = decompressArchiveViewModel;

ContentDialogResult option = await decompressArchiveDialog.ShowAsync();

if (option == ContentDialogResult.Primary)
{
// Check if archive still exists
if (!StorageHelpers.Exists(archive.Path))
{
return;
}
if (archive == null)
return;

CancellationTokenSource extractCancellation = new();
PostedStatusBanner banner = App.OngoingTasksViewModel.PostOperationBanner(
archive.Name.Length >= 30 ? archive.Name + "\n" : archive.Name,
"ExtractingArchiveText".GetLocalizedResource(),
0,
ReturnResult.InProgress,
FileOperationType.Extract,
extractCancellation);
DecompressArchiveDialog decompressArchiveDialog = new();
DecompressArchiveDialogViewModel decompressArchiveViewModel = new(archive);
decompressArchiveDialog.ViewModel = decompressArchiveViewModel;

BaseStorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder;
string destinationFolderPath = decompressArchiveViewModel.DestinationFolderPath;
ContentDialogResult option = await decompressArchiveDialog.ShowAsync();

if (destinationFolder == null)
{
BaseStorageFolder parentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(Path.GetDirectoryName(archive.Path));
destinationFolder = await FilesystemTasks.Wrap(() => parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName).AsTask());
}
if (destinationFolder == null)
{
return; // Could not create dest folder
}
if (option != ContentDialogResult.Primary)
return;

Stopwatch sw = new();
sw.Start();
// Check if archive still exists
if (!StorageHelpers.Exists(archive.Path))
return;

await ZipHelpers.ExtractArchive(archive, destinationFolder, banner.Progress, extractCancellation.Token);
BaseStorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder;
string destinationFolderPath = decompressArchiveViewModel.DestinationFolderPath;

sw.Stop();
banner.Remove();
if (destinationFolder == null)
{
BaseStorageFolder parentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(Path.GetDirectoryName(archive.Path));
destinationFolder = await FilesystemTasks.Wrap(() => parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName).AsTask());
}

if (sw.Elapsed.TotalSeconds >= 6)
{
App.OngoingTasksViewModel.PostBanner(
"ExtractingCompleteText".GetLocalizedResource(),
"ArchiveExtractionCompletedSuccessfullyText".GetLocalizedResource(),
0,
ReturnResult.Success,
FileOperationType.Extract);
}
await ExtractArchive(archive, destinationFolder);

if (decompressArchiveViewModel.OpenDestinationFolderOnCompletion)
{
await NavigationHelpers.OpenPath(destinationFolderPath, associatedInstance, FilesystemItemType.Directory);
}
}
}
if (decompressArchiveViewModel.OpenDestinationFolderOnCompletion)
await NavigationHelpers.OpenPath(destinationFolderPath, associatedInstance, FilesystemItemType.Directory);
}

public async Task DecompressArchiveHere()
{
BaseStorageFile archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(associatedInstance.SlimContentPage.SelectedItem.ItemPath);
BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath);

if (archive != null && currentFolder != null)
foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems)
{
CancellationTokenSource extractCancellation = new();
PostedStatusBanner banner = App.OngoingTasksViewModel.PostOperationBanner(
archive.Name.Length >= 30 ? archive.Name + "\n" : archive.Name,
"ExtractingArchiveText".GetLocalizedResource(),
0,
ReturnResult.InProgress,
FileOperationType.Extract,
extractCancellation);

Stopwatch sw = new();
sw.Start();

await ZipHelpers.ExtractArchive(archive, currentFolder, banner.Progress, extractCancellation.Token);

sw.Stop();
banner.Remove();
BaseStorageFile archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(selectedItem.ItemPath);
BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath);

if (sw.Elapsed.TotalSeconds >= 6)
{
App.OngoingTasksViewModel.PostBanner(
"ExtractingCompleteText".GetLocalizedResource(),
"ArchiveExtractionCompletedSuccessfullyText".GetLocalizedResource(),
0,
ReturnResult.Success,
FileOperationType.Extract);
}
await ExtractArchive(archive, currentFolder);
}
}

public async Task DecompressArchiveToChildFolder()
{
var selectedItem = associatedInstance?.SlimContentPage?.SelectedItem;
if (selectedItem == null)
foreach (var selectedItem in associatedInstance.SlimContentPage.SelectedItems)
{
return;
}
BaseStorageFile archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(selectedItem.ItemPath);
BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath);
BaseStorageFolder destinationFolder = null;

BaseStorageFile archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(selectedItem.ItemPath);
BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(associatedInstance.FilesystemViewModel.CurrentFolder.ItemPath);
BaseStorageFolder destinationFolder = null;
if (currentFolder != null)
destinationFolder = await FilesystemTasks.Wrap(() => currentFolder.CreateFolderAsync(Path.GetFileNameWithoutExtension(archive.Path), CreationCollisionOption.GenerateUniqueName).AsTask());

if (currentFolder != null)
{
destinationFolder = await FilesystemTasks.Wrap(() => currentFolder.CreateFolderAsync(Path.GetFileNameWithoutExtension(archive.Path), CreationCollisionOption.GenerateUniqueName).AsTask());
await ExtractArchive(archive, destinationFolder);
}
}

if (archive != null && destinationFolder != null)
{
CancellationTokenSource extractCancellation = new();
PostedStatusBanner banner = App.OngoingTasksViewModel.PostOperationBanner(
archive.Name.Length >= 30 ? archive.Name + "\n" : archive.Name,
"ExtractingArchiveText".GetLocalizedResource(),
0,
ReturnResult.InProgress,
FileOperationType.Extract,
extractCancellation);
private static async Task ExtractArchive(BaseStorageFile archive, BaseStorageFolder destinationFolder)
{
if (archive == null || destinationFolder == null)
return;

Stopwatch sw = new();
sw.Start();
CancellationTokenSource extractCancellation = new();
PostedStatusBanner banner = App.OngoingTasksViewModel.PostOperationBanner(
archive.Name.Length >= 30 ? archive.Name + "\n" : archive.Name,
"ExtractingArchiveText".GetLocalizedResource(),
0,
ReturnResult.InProgress,
FileOperationType.Extract,
extractCancellation);

await ZipHelpers.ExtractArchive(archive, destinationFolder, banner.Progress, extractCancellation.Token);
Stopwatch sw = new();
sw.Start();

sw.Stop();
banner.Remove();
await ZipHelpers.ExtractArchive(archive, destinationFolder, banner.Progress, extractCancellation.Token);

if (sw.Elapsed.TotalSeconds >= 6)
{
App.OngoingTasksViewModel.PostBanner(
"ExtractingCompleteText".GetLocalizedResource(),
"ArchiveExtractionCompletedSuccessfullyText".GetLocalizedResource(),
0,
ReturnResult.Success,
FileOperationType.Extract);
}
sw.Stop();
banner.Remove();

if (sw.Elapsed.TotalSeconds >= 6)
{
App.OngoingTasksViewModel.PostBanner(
"ExtractingCompleteText".GetLocalizedResource(),
"ArchiveExtractionCompletedSuccessfullyText".GetLocalizedResource(),
0,
ReturnResult.Success,
FileOperationType.Extract);
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/Files.App/UserControls/InnerNavigationToolbar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,16 @@
AccessKey="Z"
IsEnabled="{x:Bind ViewModel.CanExtract, Mode=OneWay, FallbackValue=False}"
Label="{helpers:ResourceString Name=Extract}"
LabelPosition="Default">
LabelPosition="Default"
Style="{StaticResource ToolBarAppBarButtonFlyoutStyle}">
<AppBarButton.Content>
<local:ColoredIcon BaseLayerGlyph="&#xF03F;" OverlayLayerGlyph="&#xF040;" />
</AppBarButton.Content>
<AppBarButton.Flyout>
<MenuFlyout helpers:MenuFlyoutHelper.IsVisible="{x:Bind ViewModel.CanExtract, Mode=OneWay}" Placement="Bottom">
<MenuFlyoutItem
x:Name="ExtractSingle"
x:Load="{x:Bind ViewModel.IsMultipleArchivesSelected, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
Command="{x:Bind ViewModel.ExtractCommand, Mode=OneWay}"
IsEnabled="{x:Bind ViewModel.CanExtract, Mode=OneWay, FallbackValue=False}"
KeyboardAcceleratorTextOverride="{helpers:ResourceString Name=ExtractKeyboardAcceleratorTextOverride}"
Expand Down
6 changes: 4 additions & 2 deletions src/Files.App/ViewModels/ToolbarViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,7 @@ public List<ListedItem> SelectedItems
OnPropertyChanged(nameof(CanViewProperties));
OnPropertyChanged(nameof(CanExtract));
OnPropertyChanged(nameof(ExtractToText));
OnPropertyChanged(nameof(IsMultipleArchivesSelected));
OnPropertyChanged(nameof(IsInfFile));
OnPropertyChanged(nameof(IsPowerShellScript));
OnPropertyChanged(nameof(IsImage));
Expand All @@ -1220,8 +1221,9 @@ public List<ListedItem> SelectedItems
public bool CanRename => SelectedItems is not null && SelectedItems.Count == 1;
public bool CanViewProperties => SelectedItems is not null && SelectedItems.Any();
public bool CanEmptyRecycleBin => InstanceViewModel.IsPageTypeRecycleBin && HasItem;
public bool CanExtract => SelectedItems is not null && SelectedItems.Count == 1 && FileExtensionHelpers.IsZipFile(SelectedItems.First().FileExtension) && !InstanceViewModel.IsPageTypeRecycleBin;
public string ExtractToText => SelectedItems is not null && SelectedItems.Count == 1 ? string.Format("ExtractToChildFolder".GetLocalizedResource() + "\\", Path.GetFileNameWithoutExtension(selectedItems.First().ItemName)) : "ExtractToChildFolder".GetLocalizedResource();
public bool CanExtract => SelectedItems is not null && SelectedItems.Any() && SelectedItems.All(x => FileExtensionHelpers.IsZipFile(x.FileExtension)) && !InstanceViewModel.IsPageTypeRecycleBin;
public string ExtractToText => CanExtract ? SelectedItems.Count > 1 ? string.Format("ExtractToChildFolder".GetLocalizedResource(), $"*{Path.DirectorySeparatorChar}") : string.Format("ExtractToChildFolder".GetLocalizedResource() + "\\", Path.GetFileNameWithoutExtension(selectedItems.First().ItemName)) : "ExtractToChildFolder".GetLocalizedResource();
public bool IsMultipleArchivesSelected => CanExtract && SelectedItems.Count > 1;
public bool IsPowerShellScript => SelectedItems is not null && SelectedItems.Count == 1 && FileExtensionHelpers.IsPowerShellFile(SelectedItems.First().FileExtension) && !InstanceViewModel.IsPageTypeRecycleBin;
public bool IsImage => SelectedItems is not null && SelectedItems.Any() && SelectedItems.All(x => FileExtensionHelpers.IsImageFile(x.FileExtension)) && !InstanceViewModel.IsPageTypeRecycleBin;
public bool IsMultipleImageSelected => SelectedItems is not null && SelectedItems.Count > 1 && SelectedItems.All(x => FileExtensionHelpers.IsImageFile(x.FileExtension)) && !InstanceViewModel.IsPageTypeRecycleBin;
Expand Down