Skip to content

Commit fde1e7e

Browse files
committed
Init
1 parent 99c390e commit fde1e7e

30 files changed

+514
-397
lines changed
+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
{
22
"$schema": "https://aka.ms/CsWin32.schema.json",
33
"allowMarshaling": false,
4-
"public": true
4+
"public": true,
5+
"comInterop": {
6+
"preserveSigMethods": [
7+
"IEnumShellItems.Next"
8+
]
9+
}
510
}

src/Files.App.CsWin32/NativeMethods.txt

+11
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,14 @@ WindowsDeleteString
121121
IPreviewHandler
122122
AssocQueryString
123123
GetModuleHandle
124+
SHEmptyRecycleBin
125+
SHFileOperation
126+
SHGetFolderPath
127+
SHGFP_TYPE
128+
SHGetKnownFolderItem
129+
SHQUERYRBINFO
130+
SHQueryRecycleBin
131+
FileOperation
132+
IFileOperation
133+
IShellItem2
134+
PSGetPropertyKeyFromName
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2024 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
using Windows.Win32.Foundation;
7+
8+
namespace Windows.Win32
9+
{
10+
namespace Graphics.Gdi
11+
{
12+
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
13+
public unsafe delegate BOOL MONITORENUMPROC([In] HMONITOR param0, [In] HDC param1, [In][Out] RECT* param2, [In] LPARAM param3);
14+
}
15+
16+
namespace UI.WindowsAndMessaging
17+
{
18+
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
19+
public delegate LRESULT WNDPROC(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam);
20+
}
21+
22+
namespace UI.Shell
23+
{
24+
public static partial class FOLDERID
25+
{
26+
public readonly static Guid FOLDERID_RecycleBinFolder = new(0xB7534046, 0x3ECB, 0x4C18, 0xBE, 0x4E, 0x64, 0xCD, 0x4C, 0xB7, 0xD6, 0xAC);
27+
}
28+
29+
public static partial class BHID
30+
{
31+
public readonly static Guid BHID_EnumItems = new(0x94f60519, 0x2850, 0x4924, 0xaa, 0x5a, 0xd1, 0x5e, 0x84, 0x86, 0x80, 0x39);
32+
}
33+
}
34+
}

src/Files.App.CsWin32/Windows.Win32.MONITORENUMPROC.cs

-10
This file was deleted.

src/Files.App.CsWin32/Windows.Win32.WNDPROC.cs

-10
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright (c) 2024 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using System.Security.Principal;
5+
6+
namespace Files.App.Storage.Watchers
7+
{
8+
public class RecycleBinWatcher : ITrashWatcher
9+
{
10+
private readonly List<SystemIO.FileSystemWatcher> _watchers = [];
11+
12+
/// <inheritdoc/>
13+
public event EventHandler<SystemIO.FileSystemEventArgs>? ItemAdded;
14+
15+
/// <inheritdoc/>
16+
public event EventHandler<SystemIO.FileSystemEventArgs>? ItemDeleted;
17+
18+
/// <inheritdoc/>
19+
public event EventHandler<SystemIO.FileSystemEventArgs>? ItemChanged;
20+
21+
/// <inheritdoc/>
22+
public event EventHandler<SystemIO.FileSystemEventArgs>? ItemRenamed;
23+
24+
/// <inheritdoc/>
25+
public event EventHandler<SystemIO.FileSystemEventArgs>? RefreshRequested;
26+
27+
/// <summary>
28+
/// Initializes an instance of <see cref="RecycleBinWatcher"/> class.
29+
/// </summary>
30+
public RecycleBinWatcher()
31+
{
32+
StartWatcher();
33+
}
34+
35+
/// <inheritdoc/>
36+
public void StartWatcher()
37+
{
38+
// NOTE:
39+
// SHChangeNotifyRegister only works if recycle bin is open in File Explorer.
40+
// Create file system watcher to monitor recycle bin folder(s) instead.
41+
42+
// Listen changes only on the Recycle Bin that the current logon user has
43+
var sid = WindowsIdentity.GetCurrent().User?.ToString() ?? string.Empty;
44+
if (string.IsNullOrEmpty(sid))
45+
return;
46+
47+
// TODO: Use IStorageDevicesService to enumerate drives
48+
foreach (var drive in SystemIO.DriveInfo.GetDrives())
49+
{
50+
var recyclePath = SystemIO.Path.Combine(drive.Name, "$RECYCLE.BIN", sid);
51+
52+
if (drive.DriveType is SystemIO.DriveType.Network ||
53+
!SystemIO.Directory.Exists(recyclePath))
54+
continue;
55+
56+
SystemIO.FileSystemWatcher watcher = new()
57+
{
58+
Path = recyclePath,
59+
Filter = "*.*",
60+
NotifyFilter = SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName | SystemIO.NotifyFilters.DirectoryName
61+
};
62+
63+
watcher.Created += Watcher_Changed;
64+
watcher.Deleted += Watcher_Changed;
65+
watcher.EnableRaisingEvents = true;
66+
67+
_watchers.Add(watcher);
68+
}
69+
}
70+
71+
/// <inheritdoc/>
72+
public void StopWatcher()
73+
{
74+
foreach (var watcher in _watchers)
75+
watcher.Dispose();
76+
}
77+
78+
private void Watcher_Changed(object sender, SystemIO.FileSystemEventArgs e)
79+
{
80+
// Don't listen changes on files starting with '$I'
81+
if (string.IsNullOrEmpty(e.Name) ||
82+
e.Name.StartsWith("$I", StringComparison.Ordinal))
83+
return;
84+
85+
switch (e.ChangeType)
86+
{
87+
case SystemIO.WatcherChangeTypes.Created:
88+
ItemAdded?.Invoke(this, e);
89+
break;
90+
case SystemIO.WatcherChangeTypes.Deleted:
91+
ItemDeleted?.Invoke(this, e);
92+
break;
93+
case SystemIO.WatcherChangeTypes.Renamed:
94+
ItemRenamed?.Invoke(this, e);
95+
break;
96+
default:
97+
RefreshRequested?.Invoke(this, e);
98+
break;
99+
}
100+
}
101+
102+
/// <inheritdoc/>
103+
public void Dispose()
104+
{
105+
StopWatcher();
106+
}
107+
}
108+
}

src/Files.App/Actions/FileSystem/EmptyRecycleBinAction.cs

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
// Copyright (c) 2024 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4+
using Microsoft.UI.Xaml.Controls;
5+
using Windows.Foundation.Metadata;
6+
47
namespace Files.App.Actions
58
{
69
internal sealed class EmptyRecycleBinAction : BaseUIAction, IAction
710
{
11+
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
12+
private readonly StatusCenterViewModel StatusCenterViewModel = Ioc.Default.GetRequiredService<StatusCenterViewModel>();
13+
private readonly IUserSettingsService UserSettingsService = Ioc.Default.GetRequiredService<IUserSettingsService>();
814
private readonly IContentPageContext context;
915

1016
public string Label
@@ -19,7 +25,7 @@ public RichGlyph Glyph
1925
public override bool IsExecutable =>
2026
UIHelpers.CanShowDialog &&
2127
((context.PageType == ContentPageTypes.RecycleBin && context.HasItem) ||
22-
RecycleBinHelpers.RecycleBinHasItems());
28+
WindowsRecycleBinService.HasItems());
2329

2430
public EmptyRecycleBinAction()
2531
{
@@ -30,7 +36,31 @@ public EmptyRecycleBinAction()
3036

3137
public async Task ExecuteAsync(object? parameter = null)
3238
{
33-
await RecycleBinHelpers.EmptyRecycleBinAsync();
39+
// TODO: Use AppDialogService
40+
var confirmationDialog = new ContentDialog()
41+
{
42+
Title = "ConfirmEmptyBinDialogTitle".GetLocalizedResource(),
43+
Content = "ConfirmEmptyBinDialogContent".GetLocalizedResource(),
44+
PrimaryButtonText = "Yes".GetLocalizedResource(),
45+
SecondaryButtonText = "Cancel".GetLocalizedResource(),
46+
DefaultButton = ContentDialogButton.Primary
47+
};
48+
49+
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
50+
confirmationDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;
51+
52+
if (UserSettingsService.FoldersSettingsService.DeleteConfirmationPolicy is DeleteConfirmationPolicies.Never ||
53+
await confirmationDialog.TryShowAsync() is ContentDialogResult.Primary)
54+
{
55+
var banner = StatusCenterHelper.AddCard_EmptyRecycleBin(ReturnResult.InProgress);
56+
57+
bool result = await Task.Run(WindowsRecycleBinService.DeleteAllAsync);
58+
59+
StatusCenterViewModel.RemoveItem(banner);
60+
61+
// Post a status based on the result
62+
StatusCenterHelper.AddCard_EmptyRecycleBin(result ? ReturnResult.Success : ReturnResult.Failed);
63+
}
3464
}
3565

3666
private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)

src/Files.App/Actions/FileSystem/RestoreAllRecycleBinAction.cs

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
// Copyright (c) 2024 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4+
using Microsoft.UI.Xaml.Controls;
5+
using Windows.Foundation.Metadata;
6+
47
namespace Files.App.Actions
58
{
69
internal sealed class RestoreAllRecycleBinAction : BaseUIAction, IAction
710
{
11+
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
12+
813
public string Label
914
=> "RestoreAllItems".GetLocalizedResource();
1015

@@ -16,11 +21,42 @@ public RichGlyph Glyph
1621

1722
public override bool IsExecutable =>
1823
UIHelpers.CanShowDialog &&
19-
RecycleBinHelpers.RecycleBinHasItems();
24+
WindowsRecycleBinService.HasItems();
2025

2126
public async Task ExecuteAsync(object? parameter = null)
2227
{
23-
await RecycleBinHelpers.RestoreRecycleBinAsync();
28+
// TODO: Use AppDialogService
29+
var confirmationDialog = new ContentDialog()
30+
{
31+
Title = "ConfirmRestoreBinDialogTitle".GetLocalizedResource(),
32+
Content = "ConfirmRestoreBinDialogContent".GetLocalizedResource(),
33+
PrimaryButtonText = "Yes".GetLocalizedResource(),
34+
SecondaryButtonText = "Cancel".GetLocalizedResource(),
35+
DefaultButton = ContentDialogButton.Primary
36+
};
37+
38+
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
39+
confirmationDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;
40+
41+
if (await confirmationDialog.TryShowAsync() is not ContentDialogResult.Primary)
42+
return;
43+
44+
bool result = await Task.Run(WindowsRecycleBinService.RestoreAllAsync);
45+
46+
// Show error dialog when failed
47+
if (!result)
48+
{
49+
var errorDialog = new ContentDialog()
50+
{
51+
Title = "FailedToRestore".GetLocalizedResource(),
52+
PrimaryButtonText = "OK".GetLocalizedResource(),
53+
};
54+
55+
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
56+
errorDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;
57+
58+
await errorDialog.TryShowAsync();
59+
}
2460
}
2561
}
2662
}

src/Files.App/Actions/FileSystem/RestoreRecycleBinAction.cs

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Copyright (c) 2024 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4+
using Microsoft.UI.Xaml.Controls;
5+
using Windows.Foundation.Metadata;
6+
using Windows.Storage;
7+
48
namespace Files.App.Actions
59
{
610
internal sealed class RestoreRecycleBinAction : BaseUIAction, IAction
@@ -30,8 +34,32 @@ public RestoreRecycleBinAction()
3034

3135
public async Task ExecuteAsync(object? parameter = null)
3236
{
33-
if (context.ShellPage is not null)
34-
await RecycleBinHelpers.RestoreSelectionRecycleBinAsync(context.ShellPage);
37+
var confirmationDialog = new ContentDialog()
38+
{
39+
Title = "ConfirmRestoreSelectionBinDialogTitle".GetLocalizedResource(),
40+
Content = string.Format("ConfirmRestoreSelectionBinDialogContent".GetLocalizedResource(), context.SelectedItems.Count),
41+
PrimaryButtonText = "Yes".GetLocalizedResource(),
42+
SecondaryButtonText = "Cancel".GetLocalizedResource(),
43+
DefaultButton = ContentDialogButton.Primary
44+
};
45+
46+
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
47+
confirmationDialog.XamlRoot = MainWindow.Instance.Content.XamlRoot;
48+
49+
ContentDialogResult result = await confirmationDialog.TryShowAsync();
50+
51+
if (result is not ContentDialogResult.Primary)
52+
return;
53+
54+
var items = context.SelectedItems.ToList().Where(x => x is RecycleBinItem).Select((item) => new
55+
{
56+
Source = StorageHelpers.FromPathAndType(
57+
item.ItemPath,
58+
item.PrimaryItemAttribute == StorageItemTypes.File ? FilesystemItemType.File : FilesystemItemType.Directory),
59+
Dest = ((RecycleBinItem)item).ItemOriginalPath
60+
});
61+
62+
await context.ShellPage!.FilesystemHelpers.RestoreItemsFromTrashAsync(items.Select(x => x.Source), items.Select(x => x.Dest), true);
3563
}
3664

3765
private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)

0 commit comments

Comments
 (0)