Skip to content

Commit dd15612

Browse files
committed
Added bulk copy and cut actions
1 parent 2c7d8a8 commit dd15612

File tree

6 files changed

+249
-110
lines changed

6 files changed

+249
-110
lines changed

src/Files.App/Actions/FileSystem/Transfer/BaseTransferItemAction.cs

Lines changed: 1 addition & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
// Copyright (c) Files Community
22
// Licensed under the MIT License.
33

4-
using Microsoft.Extensions.Logging;
5-
using System.Collections.Concurrent;
6-
using System.IO;
74
using Windows.ApplicationModel.DataTransfer;
8-
using Windows.Storage;
9-
using Windows.System;
105

116
namespace Files.App.Actions
127
{
@@ -25,103 +20,7 @@ public BaseTransferItemAction()
2520

2621
public async Task ExecuteTransferAsync(DataPackageOperation type = DataPackageOperation.Copy)
2722
{
28-
if (ContentPageContext.ShellPage?.SlimContentPage is null ||
29-
ContentPageContext.ShellPage.SlimContentPage.IsItemSelected is false)
30-
return;
31-
32-
// Reset cut mode
33-
ContentPageContext.ShellPage.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
34-
35-
ConcurrentBag<IStorageItem> items = [];
36-
var itemsCount = ContentPageContext.SelectedItems.Count;
37-
var statusCenterItem = itemsCount > 50 ? StatusCenterHelper.AddCard_Prepare() : null;
38-
var dataPackage = new DataPackage() { RequestedOperation = type };
39-
40-
try
41-
{
42-
// Update the status to in-progress
43-
if (statusCenterItem is not null)
44-
{
45-
statusCenterItem.Progress.EnumerationCompleted = true;
46-
statusCenterItem.Progress.ItemsCount = items.Count;
47-
statusCenterItem.Progress.ReportStatus(FileSystemStatusCode.InProgress);
48-
}
49-
50-
await ContentPageContext.SelectedItems.ToList().ParallelForEachAsync(async listedItem =>
51-
{
52-
// Update the status to increase processed count by one
53-
if (statusCenterItem is not null)
54-
{
55-
statusCenterItem.Progress.AddProcessedItemsCount(1);
56-
statusCenterItem.Progress.Report();
57-
}
58-
59-
if (listedItem is FtpItem ftpItem)
60-
{
61-
// Don't dim selected items here since FTP doesn't support cut
62-
if (ftpItem.PrimaryItemAttribute is StorageItemTypes.File or StorageItemTypes.Folder)
63-
items.Add(await ftpItem.ToStorageItem());
64-
}
65-
else
66-
{
67-
if (type is DataPackageOperation.Move)
68-
{
69-
// Dim opacities accordingly
70-
await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() =>
71-
{
72-
listedItem.Opacity = Constants.UI.DimItemOpacity;
73-
});
74-
}
75-
76-
FilesystemResult? result =
77-
listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem
78-
? await ContentPageContext.ShellPage.ShellViewModel.GetFileFromPathAsync(listedItem.ItemPath).OnSuccess(t => items.Add(t))
79-
: await ContentPageContext.ShellPage.ShellViewModel.GetFolderFromPathAsync(listedItem.ItemPath).OnSuccess(t => items.Add(t));
80-
81-
if (!result)
82-
throw new IOException($"Failed to process {listedItem.ItemPath} in cutting/copying to the clipboard.", (int)result.ErrorCode);
83-
}
84-
},
85-
10,
86-
statusCenterItem?.CancellationToken ?? default);
87-
88-
var standardObjectsOnly = items.All(x => x is StorageFile or StorageFolder or SystemStorageFile or SystemStorageFolder);
89-
if (standardObjectsOnly)
90-
items = new ConcurrentBag<IStorageItem>(await items.ToStandardStorageItemsAsync());
91-
92-
if (items.IsEmpty)
93-
return;
94-
95-
dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName;
96-
dataPackage.SetStorageItems(items, false);
97-
98-
Clipboard.SetContent(dataPackage);
99-
}
100-
catch (Exception ex)
101-
{
102-
dataPackage = default;
103-
104-
if (ex is not IOException)
105-
App.Logger.LogWarning(ex, "Failed to process cutting/copying due to an unknown error.");
106-
107-
if ((FileSystemStatusCode)ex.HResult is FileSystemStatusCode.Unauthorized)
108-
{
109-
string[] filePaths = ContentPageContext.SelectedItems.Select(x => x.ItemPath).ToArray();
110-
await FileOperationsHelpers.SetClipboard(filePaths, type);
111-
112-
return;
113-
}
114-
115-
// Reset cut mode
116-
ContentPageContext.ShellPage.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
117-
118-
return;
119-
}
120-
finally
121-
{
122-
if (statusCenterItem is not null)
123-
StatusCenterViewModel.RemoveItem(statusCenterItem);
124-
}
23+
await TransferHelpers.ExecuteTransferAsync(ContentPageContext, StatusCenterViewModel, type);
12524
}
12625

12726
private void ContentPageContext_PropertyChanged(object? sender, PropertyChangedEventArgs e)
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using Microsoft.Extensions.Logging;
2+
using System.Collections.Concurrent;
3+
using Windows.ApplicationModel.DataTransfer;
4+
using Windows.Storage;
5+
6+
namespace Files.App.Helpers
7+
{
8+
public static class TransferHelpers
9+
{
10+
public static async Task ExecuteTransferAsync(IReadOnlyList<IStorable> itemsToTransfer, ShellViewModel shellViewModel, StatusCenterViewModel statusViewModel, DataPackageOperation type = DataPackageOperation.Copy)
11+
{
12+
ConcurrentBag<IStorageItem> items = [];
13+
var itemsCount = itemsToTransfer.Count;
14+
var statusCenterItem = itemsCount > 50 ? StatusCenterHelper.AddCard_Prepare() : null;
15+
var dataPackage = new DataPackage() { RequestedOperation = type };
16+
17+
try
18+
{
19+
// Update the status to in-progress
20+
if (statusCenterItem is not null)
21+
{
22+
statusCenterItem.Progress.EnumerationCompleted = true;
23+
statusCenterItem.Progress.ItemsCount = items.Count;
24+
statusCenterItem.Progress.ReportStatus(FileSystemStatusCode.InProgress);
25+
}
26+
27+
await itemsToTransfer.ParallelForEachAsync(async storable =>
28+
{
29+
// Update the status to increase processed count by one
30+
if (statusCenterItem is not null)
31+
{
32+
statusCenterItem.Progress.AddProcessedItemsCount(1);
33+
statusCenterItem.Progress.Report();
34+
}
35+
36+
var result = storable switch
37+
{
38+
IFile => await shellViewModel.GetFileFromPathAsync(storable.Id).OnSuccess(x => items.Add(x)),
39+
IFolder => await shellViewModel.GetFolderFromPathAsync(storable.Id).OnSuccess(x => items.Add(x)),
40+
};
41+
42+
if (!result)
43+
throw new SystemIO.IOException($"Failed to process {storable.Id} in cutting/copying to the clipboard.", (int)result.ErrorCode);
44+
}, 10, statusCenterItem?.CancellationToken ?? CancellationToken.None);
45+
46+
var standardObjectsOnly = items.All(x => x is StorageFile or StorageFolder or SystemStorageFile or SystemStorageFolder);
47+
if (standardObjectsOnly)
48+
items = new(await items.ToStandardStorageItemsAsync());
49+
50+
if (items.IsEmpty)
51+
return;
52+
53+
dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName;
54+
dataPackage.SetStorageItems(items, false);
55+
56+
Clipboard.SetContent(dataPackage);
57+
}
58+
catch (Exception ex)
59+
{
60+
if (ex is not SystemIO.IOException)
61+
App.Logger.LogWarning(ex, "Failed to process cutting/copying due to an unknown error.");
62+
63+
if ((FileSystemStatusCode)ex.HResult is FileSystemStatusCode.Unauthorized)
64+
{
65+
var filePaths = itemsToTransfer.Select(x => x.Id).ToArray();
66+
await FileOperationsHelpers.SetClipboard(filePaths, type);
67+
}
68+
}
69+
finally
70+
{
71+
if (statusCenterItem is not null)
72+
statusViewModel.RemoveItem(statusCenterItem);
73+
}
74+
}
75+
76+
public static async Task ExecuteTransferAsync(IContentPageContext context, StatusCenterViewModel statusViewModel, DataPackageOperation type = DataPackageOperation.Copy)
77+
{
78+
if (context.ShellPage?.SlimContentPage is null ||
79+
context.ShellPage.SlimContentPage.IsItemSelected is false)
80+
return;
81+
82+
// Reset cut mode
83+
context.ShellPage.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
84+
85+
ConcurrentBag<IStorageItem> items = [];
86+
var itemsCount = context.SelectedItems.Count;
87+
var statusCenterItem = itemsCount > 50 ? StatusCenterHelper.AddCard_Prepare() : null;
88+
var dataPackage = new DataPackage() { RequestedOperation = type };
89+
90+
try
91+
{
92+
// Update the status to in-progress
93+
if (statusCenterItem is not null)
94+
{
95+
statusCenterItem.Progress.EnumerationCompleted = true;
96+
statusCenterItem.Progress.ItemsCount = items.Count;
97+
statusCenterItem.Progress.ReportStatus(FileSystemStatusCode.InProgress);
98+
}
99+
100+
await context.SelectedItems.ToList().ParallelForEachAsync(async listedItem =>
101+
{
102+
// Update the status to increase processed count by one
103+
if (statusCenterItem is not null)
104+
{
105+
statusCenterItem.Progress.AddProcessedItemsCount(1);
106+
statusCenterItem.Progress.Report();
107+
}
108+
109+
if (listedItem is FtpItem ftpItem)
110+
{
111+
// Don't dim selected items here since FTP doesn't support cut
112+
if (ftpItem.PrimaryItemAttribute is StorageItemTypes.File or StorageItemTypes.Folder)
113+
items.Add(await ftpItem.ToStorageItem());
114+
}
115+
else
116+
{
117+
if (type is DataPackageOperation.Move)
118+
{
119+
// Dim opacities accordingly
120+
await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() =>
121+
{
122+
listedItem.Opacity = Constants.UI.DimItemOpacity;
123+
});
124+
}
125+
126+
var result = listedItem.PrimaryItemAttribute == StorageItemTypes.File || listedItem is ZipItem
127+
? await context.ShellPage.ShellViewModel.GetFileFromPathAsync(listedItem.ItemPath).OnSuccess(t => items.Add(t))
128+
: await context.ShellPage.ShellViewModel.GetFolderFromPathAsync(listedItem.ItemPath).OnSuccess(t => items.Add(t));
129+
130+
if (!result)
131+
throw new SystemIO.IOException($"Failed to process {listedItem.ItemPath} in cutting/copying to the clipboard.", (int)result.ErrorCode);
132+
}
133+
}, 10, statusCenterItem?.CancellationToken ?? CancellationToken.None);
134+
135+
var standardObjectsOnly = items.All(x => x is StorageFile or StorageFolder or SystemStorageFile or SystemStorageFolder);
136+
if (standardObjectsOnly)
137+
items = new(await items.ToStandardStorageItemsAsync());
138+
139+
if (items.IsEmpty)
140+
return;
141+
142+
dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName;
143+
dataPackage.SetStorageItems(items, false);
144+
145+
Clipboard.SetContent(dataPackage);
146+
}
147+
catch (Exception ex)
148+
{
149+
if (ex is not SystemIO.IOException)
150+
App.Logger.LogWarning(ex, "Failed to process cutting/copying due to an unknown error.");
151+
152+
if ((FileSystemStatusCode)ex.HResult is FileSystemStatusCode.Unauthorized)
153+
{
154+
var filePaths = context.SelectedItems.Select(x => x.ItemPath).ToArray();
155+
await FileOperationsHelpers.SetClipboard(filePaths, type);
156+
157+
return;
158+
}
159+
160+
// Reset cut mode
161+
context.ShellPage.SlimContentPage.ItemManipulationModel.RefreshItemsOpacity();
162+
}
163+
finally
164+
{
165+
if (statusCenterItem is not null)
166+
statusViewModel.RemoveItem(statusCenterItem);
167+
}
168+
}
169+
}
170+
}

src/Files.App/UserControls/Pane/ShelfPane.xaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,20 @@
164164
Command="{x:Bind BulkDeleteCommand, Mode=OneWay}">
165165
<controls:ThemedIcon Style="{StaticResource App.ThemedIcons.Delete}" />
166166
</Button>
167+
<Button
168+
Padding="8"
169+
Background="Transparent"
170+
BorderThickness="0"
171+
Command="{x:Bind BulkCopyCommand, Mode=OneWay}">
172+
<controls:ThemedIcon Style="{StaticResource App.ThemedIcons.Copy}" />
173+
</Button>
174+
<Button
175+
Padding="8"
176+
Background="Transparent"
177+
BorderThickness="0"
178+
Command="{x:Bind BulkCutCommand, Mode=OneWay}">
179+
<controls:ThemedIcon Style="{StaticResource App.ThemedIcons.Cut}" />
180+
</Button>
167181
</StackPanel>
168182

169183
<HyperlinkButton

src/Files.App/UserControls/Pane/ShelfPane.xaml.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,22 @@ public ICommand? BulkDeleteCommand
142142
public static readonly DependencyProperty BulkDeleteCommandProperty =
143143
DependencyProperty.Register(nameof(BulkDeleteCommand), typeof(ICommand), typeof(ShelfPane), new PropertyMetadata(null));
144144

145+
public ICommand? BulkCopyCommand
146+
{
147+
get => (ICommand?)GetValue(BulkCopyCommandProperty);
148+
set => SetValue(BulkCopyCommandProperty, value);
149+
}
150+
public static readonly DependencyProperty BulkCopyCommandProperty =
151+
DependencyProperty.Register(nameof(BulkCopyCommand), typeof(ICommand), typeof(ShelfPane), new PropertyMetadata(null));
152+
153+
public ICommand? BulkCutCommand
154+
{
155+
get => (ICommand?)GetValue(BulkCutCommandProperty);
156+
set => SetValue(BulkCutCommandProperty, value);
157+
}
158+
public static readonly DependencyProperty BulkCutCommandProperty =
159+
DependencyProperty.Register(nameof(BulkCutCommand), typeof(ICommand), typeof(ShelfPane), new PropertyMetadata(null));
160+
145161
public ICommand? ItemFocusedCommand
146162
{
147163
get => (ICommand?)GetValue(ItemFocusedCommandProperty);

0 commit comments

Comments
 (0)