Skip to content

Commit 77a5b8a

Browse files
committed
Req
1 parent e7b8c44 commit 77a5b8a

File tree

16 files changed

+73
-81
lines changed

16 files changed

+73
-81
lines changed

src/Files.App.Storage/Watchers/RecycleBinWatcher.cs

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

4+
using Files.Shared.Extensions;
45
using System.Security.Principal;
56

67
namespace Files.App.Storage.Watchers
@@ -15,9 +16,6 @@ public class RecycleBinWatcher : ITrashWatcher
1516
/// <inheritdoc/>
1617
public event EventHandler<SystemIO.FileSystemEventArgs>? ItemDeleted;
1718

18-
/// <inheritdoc/>
19-
public event EventHandler<SystemIO.FileSystemEventArgs>? ItemChanged;
20-
2119
/// <inheritdoc/>
2220
public event EventHandler<SystemIO.FileSystemEventArgs>? ItemRenamed;
2321

@@ -35,16 +33,13 @@ public RecycleBinWatcher()
3533
/// <inheritdoc/>
3634
public void StartWatcher()
3735
{
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.
36+
// NOTE: SHChangeNotifyRegister only works if recycle bin is open in File Explorer.
4137

4238
// Listen changes only on the Recycle Bin that the current logon user has
4339
var sid = WindowsIdentity.GetCurrent().User?.ToString() ?? string.Empty;
4440
if (string.IsNullOrEmpty(sid))
4541
return;
4642

47-
// TODO: Use IStorageDevicesService to enumerate drives
4843
foreach (var drive in SystemIO.DriveInfo.GetDrives())
4944
{
5045
var recyclePath = SystemIO.Path.Combine(drive.Name, "$RECYCLE.BIN", sid);
@@ -53,18 +48,22 @@ public void StartWatcher()
5348
!SystemIO.Directory.Exists(recyclePath))
5449
continue;
5550

56-
SystemIO.FileSystemWatcher watcher = new()
51+
// NOTE: Suppressed NullReferenceException caused by EnableRaisingEvents in #15808
52+
SafetyExtensions.IgnoreExceptions(() =>
5753
{
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);
54+
SystemIO.FileSystemWatcher watcher = new()
55+
{
56+
Path = recyclePath,
57+
Filter = "*.*",
58+
NotifyFilter = SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName | SystemIO.NotifyFilters.DirectoryName
59+
};
60+
61+
watcher.Created += Watcher_Changed;
62+
watcher.Deleted += Watcher_Changed;
63+
watcher.EnableRaisingEvents = true;
64+
65+
_watchers.Add(watcher);
66+
});
6867
}
6968
}
7069

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Files.App.Actions
88
{
99
internal sealed class EmptyRecycleBinAction : BaseUIAction, IAction
1010
{
11-
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
11+
private readonly IStorageTrashBinService StorageTrashBinService = Ioc.Default.GetRequiredService<IStorageTrashBinService>();
1212
private readonly StatusCenterViewModel StatusCenterViewModel = Ioc.Default.GetRequiredService<StatusCenterViewModel>();
1313
private readonly IUserSettingsService UserSettingsService = Ioc.Default.GetRequiredService<IUserSettingsService>();
1414
private readonly IContentPageContext context;
@@ -25,7 +25,7 @@ public RichGlyph Glyph
2525
public override bool IsExecutable =>
2626
UIHelpers.CanShowDialog &&
2727
((context.PageType == ContentPageTypes.RecycleBin && context.HasItem) ||
28-
WindowsRecycleBinService.HasItems());
28+
StorageTrashBinService.HasItems());
2929

3030
public EmptyRecycleBinAction()
3131
{
@@ -54,7 +54,7 @@ await confirmationDialog.TryShowAsync() is ContentDialogResult.Primary)
5454
{
5555
var banner = StatusCenterHelper.AddCard_EmptyRecycleBin(ReturnResult.InProgress);
5656

57-
bool result = await Task.Run(WindowsRecycleBinService.DeleteAllAsync);
57+
bool result = await Task.Run(StorageTrashBinService.EmptyTrashBin);
5858

5959
StatusCenterViewModel.RemoveItem(banner);
6060

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Files.App.Actions
88
{
99
internal sealed class RestoreAllRecycleBinAction : BaseUIAction, IAction
1010
{
11-
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
11+
private readonly IStorageTrashBinService StorageTrashBinService = Ioc.Default.GetRequiredService<IStorageTrashBinService>();
1212

1313
public string Label
1414
=> "RestoreAllItems".GetLocalizedResource();
@@ -21,7 +21,7 @@ public RichGlyph Glyph
2121

2222
public override bool IsExecutable =>
2323
UIHelpers.CanShowDialog &&
24-
WindowsRecycleBinService.HasItems();
24+
StorageTrashBinService.HasItems();
2525

2626
public async Task ExecuteAsync(object? parameter = null)
2727
{
@@ -41,7 +41,7 @@ public async Task ExecuteAsync(object? parameter = null)
4141
if (await confirmationDialog.TryShowAsync() is not ContentDialogResult.Primary)
4242
return;
4343

44-
bool result = await Task.Run(WindowsRecycleBinService.RestoreAllAsync);
44+
bool result = await Task.Run(StorageTrashBinService.RestoreAllTrashes);
4545

4646
// Show error dialog when failed
4747
if (!result)

src/Files.App/Data/Contracts/IWindowsRecycleBinService.cs renamed to src/Files.App/Data/Contracts/IStorageTrashBinService.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Files.App.Data.Contracts
66
/// <summary>
77
/// Provides service for Recycle Bin on Windows.
88
/// </summary>
9-
public interface IWindowsRecycleBinService
9+
public interface IStorageTrashBinService
1010
{
1111
/// <summary>
1212
/// Gets the watcher of Recycle Bin folder.
@@ -43,25 +43,25 @@ public interface IWindowsRecycleBinService
4343
/// </summary>
4444
/// <param name="path">The path that indicates to a file or folder.</param>
4545
/// <returns>True if the file or path is recycled; otherwise, false.</returns>
46-
bool IsRecycled(string? path);
46+
bool IsUnderTrashBin(string? path);
4747

4848
/// <summary>
4949
/// Gets the file or folder specified can be moved to Recycle Bin.
5050
/// </summary>
5151
/// <param name="path"></param>
5252
/// <returns></returns>
53-
Task<bool> IsRecyclableAsync(string? path);
53+
Task<bool> CanGoTrashBin(string? path);
5454

5555
/// <summary>
5656
/// Deletes files and folders in Recycle Bin permanently.
5757
/// </summary>
5858
/// <returns>True if succeeded; otherwise, false</returns>
59-
bool DeleteAllAsync();
59+
bool EmptyTrashBin();
6060

6161
/// <summary>
6262
/// Restores files and folders in Recycle Bin to original paths.
6363
/// </summary>
6464
/// <returns>True if succeeded; otherwise, false</returns>
65-
bool RestoreAllAsync();
65+
bool RestoreAllTrashes();
6666
}
6767
}

src/Files.App/Data/Items/LocationItem.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,13 @@ public int CompareTo(INavigationControlItem other)
124124

125125
public sealed class RecycleBinLocationItem : LocationItem
126126
{
127-
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
127+
private readonly IStorageTrashBinService StorageTrashBinService = Ioc.Default.GetRequiredService<IStorageTrashBinService>();
128128

129129
public async void RefreshSpaceUsed(object? sender, FileSystemEventArgs e)
130130
{
131131
await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() =>
132132
{
133-
SpaceUsed = WindowsRecycleBinService.GetSize();
133+
SpaceUsed = StorageTrashBinService.GetSize();
134134
});
135135
}
136136

@@ -152,10 +152,10 @@ public override object ToolTip
152152

153153
public RecycleBinLocationItem()
154154
{
155-
SpaceUsed = WindowsRecycleBinService.GetSize();
155+
SpaceUsed = StorageTrashBinService.GetSize();
156156

157-
WindowsRecycleBinService.Watcher.ItemAdded += RefreshSpaceUsed;
158-
WindowsRecycleBinService.Watcher.ItemDeleted += RefreshSpaceUsed;
157+
StorageTrashBinService.Watcher.ItemAdded += RefreshSpaceUsed;
158+
StorageTrashBinService.Watcher.ItemDeleted += RefreshSpaceUsed;
159159
}
160160
}
161161
}

src/Files.App/Helpers/Application/AppLifecycleHelper.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public static IHost ConfigureHost()
196196
.AddSingleton<IQuickAccessService, QuickAccessService>()
197197
.AddSingleton<IResourcesService, ResourcesService>()
198198
.AddSingleton<IWindowsJumpListService, WindowsJumpListService>()
199-
.AddSingleton<IWindowsRecycleBinService, WindowsRecycleBinService>()
199+
.AddSingleton<IStorageTrashBinService, StorageTrashBinService>()
200200
.AddSingleton<IRemovableDrivesService, RemovableDrivesService>()
201201
.AddSingleton<INetworkService, NetworkService>()
202202
.AddSingleton<IStartMenuService, StartMenuService>()

src/Files.App/Services/App/FileTagsService.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Files.App.Services
1111
/// <inheritdoc cref="IFileTagsService"/>
1212
internal sealed class FileTagsService : IFileTagsService
1313
{
14-
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
14+
private readonly IStorageTrashBinService StorageTrashBinService = Ioc.Default.GetRequiredService<IStorageTrashBinService>();
1515
private readonly IFileTagsSettingsService FileTagsSettingsService = Ioc.Default.GetRequiredService<IFileTagsSettingsService>();
1616
private readonly IStorageService StorageService = Ioc.Default.GetRequiredService<IStorageService>();
1717

@@ -42,7 +42,7 @@ public async IAsyncEnumerable<TaggedItemModel> GetItemsForTagAsync(string tagUid
4242
{
4343
foreach (var item in FileTagsHelper.GetDbInstance().GetAll())
4444
{
45-
if (!item.Tags.Contains(tagUid) || WindowsRecycleBinService.IsRecycled(item.FilePath))
45+
if (!item.Tags.Contains(tagUid) || StorageTrashBinService.IsUnderTrashBin(item.FilePath))
4646
continue;
4747

4848
var storable = await StorageService.TryGetStorableAsync(item.FilePath, cancellationToken);

src/Files.App/Services/Windows/WindowsRecycleBinService.cs renamed to src/Files.App/Services/Storage/StorageTrashBinService.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
namespace Files.App.Services
1111
{
12-
/// <inheritdoc cref="IWindowsRecycleBinService"/>
13-
public class WindowsRecycleBinService : IWindowsRecycleBinService
12+
/// <inheritdoc cref="IStorageTrashBinService"/>
13+
public class StorageTrashBinService : IStorageTrashBinService
1414
{
1515
/// <inheritdoc/>
1616
public RecycleBinWatcher Watcher { get; private set; } = new();
@@ -46,15 +46,15 @@ public bool HasItems()
4646
}
4747

4848
/// <inheritdoc/>
49-
public bool IsRecycled(string? path)
49+
public bool IsUnderTrashBin(string? path)
5050
{
5151
return
5252
!string.IsNullOrWhiteSpace(path) &&
5353
RegexHelpers.RecycleBinPath().IsMatch(path);
5454
}
5555

5656
/// <inheritdoc/>
57-
public async Task<bool> IsRecyclableAsync(string? path)
57+
public async Task<bool> CanGoTrashBin(string? path)
5858
{
5959
if (string.IsNullOrEmpty(path) ||
6060
path.StartsWith(@"\\?\", StringComparison.Ordinal))
@@ -68,7 +68,7 @@ public async Task<bool> IsRecyclableAsync(string? path)
6868
}
6969

7070
/// <inheritdoc/>
71-
public bool DeleteAllAsync()
71+
public bool EmptyTrashBin()
7272
{
7373
var fRes = PInvoke.SHEmptyRecycleBin(
7474
new(),
@@ -80,7 +80,7 @@ public bool DeleteAllAsync()
8080
}
8181

8282
/// <inheritdoc/>
83-
public unsafe bool RestoreAllAsync()
83+
public unsafe bool RestoreAllTrashes()
8484
{
8585
IShellItem* recycleBinFolderShellItem = default;
8686
IEnumShellItems* enumShellItems = default;

src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace Files.App.Utils.Storage
2020
{
2121
public sealed class FilesystemHelpers : IFilesystemHelpers
2222
{
23-
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
23+
private readonly IStorageTrashBinService StorageTrashBinService = Ioc.Default.GetRequiredService<IStorageTrashBinService>();
2424
private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService<StatusCenterViewModel>();
2525

2626
private IShellPage associatedInstance;
@@ -91,8 +91,8 @@ public async Task<ReturnResult> DeleteItemsAsync(IEnumerable<IStorageItemWithPat
9191

9292
var returnStatus = ReturnResult.InProgress;
9393

94-
var deleteFromRecycleBin = source.Select(item => item.Path).Any(WindowsRecycleBinService.IsRecycled);
95-
var canBeSentToBin = !deleteFromRecycleBin && await WindowsRecycleBinService.IsRecyclableAsync(source.FirstOrDefault()?.Path);
94+
var deleteFromRecycleBin = source.Select(item => item.Path).Any(StorageTrashBinService.IsUnderTrashBin);
95+
var canBeSentToBin = !deleteFromRecycleBin && await StorageTrashBinService.CanGoTrashBin(source.FirstOrDefault()?.Path);
9696

9797
if (showDialog is DeleteConfirmationPolicies.Always ||
9898
showDialog is DeleteConfirmationPolicies.PermanentOnly &&
@@ -103,9 +103,9 @@ showDialog is DeleteConfirmationPolicies.PermanentOnly &&
103103

104104
foreach (var src in source)
105105
{
106-
if (WindowsRecycleBinService.IsRecycled(src.Path))
106+
if (StorageTrashBinService.IsUnderTrashBin(src.Path))
107107
{
108-
binItems ??= await WindowsRecycleBinService.GetAllRecycleBinFoldersAsync();
108+
binItems ??= await StorageTrashBinService.GetAllRecycleBinFoldersAsync();
109109

110110
// Might still be null because we're deserializing the list from Json
111111
if (!binItems.IsEmpty())
@@ -364,9 +364,9 @@ public async Task<ReturnResult> CopyItemsFromClipboard(DataPackageView packageVi
364364
List<ShellFileItem>? binItems = null;
365365
foreach (var item in source)
366366
{
367-
if (WindowsRecycleBinService.IsRecycled(item.Path))
367+
if (StorageTrashBinService.IsUnderTrashBin(item.Path))
368368
{
369-
binItems ??= await WindowsRecycleBinService.GetAllRecycleBinFoldersAsync();
369+
binItems ??= await StorageTrashBinService.GetAllRecycleBinFoldersAsync();
370370
if (!binItems.IsEmpty()) // Might still be null because we're deserializing the list from Json
371371
{
372372
var matchingItem = binItems.FirstOrDefault(x => x.RecyclePath == item.Path); // Get original file name
@@ -512,9 +512,9 @@ public async Task<ReturnResult> MoveItemsFromClipboard(DataPackageView packageVi
512512
List<ShellFileItem>? binItems = null;
513513
foreach (var item in source)
514514
{
515-
if (WindowsRecycleBinService.IsRecycled(item.Path))
515+
if (StorageTrashBinService.IsUnderTrashBin(item.Path))
516516
{
517-
binItems ??= await WindowsRecycleBinService.GetAllRecycleBinFoldersAsync();
517+
binItems ??= await StorageTrashBinService.GetAllRecycleBinFoldersAsync();
518518
if (!binItems.IsEmpty()) // Might still be null because we're deserializing the list from Json
519519
{
520520
var matchingItem = binItems.FirstOrDefault(x => x.RecyclePath == item.Path); // Get original file name
@@ -637,7 +637,7 @@ public async Task<ReturnResult> RecycleItemsFromClipboard(DataPackageView packag
637637
var source = await GetDraggedStorageItems(packageView);
638638
ReturnResult returnStatus = ReturnResult.InProgress;
639639

640-
source = source.Where(x => !WindowsRecycleBinService.IsRecycled(x.Path)); // Can't recycle items already in recyclebin
640+
source = source.Where(x => !StorageTrashBinService.IsUnderTrashBin(x.Path)); // Can't recycle items already in recyclebin
641641
returnStatus = await DeleteItemsAsync(source, showDialog, false, registerHistory);
642642

643643
return returnStatus;

src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace Files.App.Utils.Storage
1616
/// </summary>
1717
public sealed class FilesystemOperations : IFilesystemOperations
1818
{
19-
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
19+
private readonly IStorageTrashBinService StorageTrashBinService = Ioc.Default.GetRequiredService<IStorageTrashBinService>();
2020

2121
private IShellPage _associatedInstance;
2222

@@ -500,7 +500,7 @@ public async Task<IStorageHistory> DeleteAsync(IStorageItemWithPath source, IPro
500500

501501
fsProgress.Report();
502502

503-
bool deleteFromRecycleBin = WindowsRecycleBinService.IsRecycled(source.Path);
503+
bool deleteFromRecycleBin = StorageTrashBinService.IsUnderTrashBin(source.Path);
504504

505505
FilesystemResult fsResult = FileSystemStatusCode.InProgress;
506506

@@ -551,7 +551,7 @@ await _associatedInstance.ShellViewModel.GetFileFromPathAsync(iFilePath)
551551
if (!permanently)
552552
{
553553
// Enumerate Recycle Bin
554-
IEnumerable<ShellFileItem> nameMatchItems, items = await WindowsRecycleBinService.GetAllRecycleBinFoldersAsync();
554+
IEnumerable<ShellFileItem> nameMatchItems, items = await StorageTrashBinService.GetAllRecycleBinFoldersAsync();
555555

556556
// Get name matching files
557557
if (FileExtensionHelpers.IsShortcutOrUrlFile(source.Path)) // We need to check if it is a shortcut file
@@ -936,7 +936,7 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
936936
if (token.IsCancellationRequested)
937937
break;
938938

939-
permanently = WindowsRecycleBinService.IsRecycled(source[i].Path) || originalPermanently;
939+
permanently = StorageTrashBinService.IsUnderTrashBin(source[i].Path) || originalPermanently;
940940

941941
rawStorageHistory.Add(await DeleteAsync(source[i], null, permanently, token));
942942
fsProgress.AddProcessedItemsCount(1);

src/Files.App/Utils/Storage/Operations/ShellFilesystemOperations.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Files.App.Utils.Storage
1111
/// </summary>
1212
public sealed class ShellFilesystemOperations : IFilesystemOperations
1313
{
14-
private readonly IWindowsRecycleBinService WindowsRecycleBinService = Ioc.Default.GetRequiredService<IWindowsRecycleBinService>();
14+
private readonly IStorageTrashBinService StorageTrashBinService = Ioc.Default.GetRequiredService<IStorageTrashBinService>();
1515

1616
private IShellPage _associatedInstance;
1717

@@ -360,7 +360,7 @@ public async Task<IStorageHistory> DeleteItemsAsync(IList<IStorageItemWithPath>
360360
fsProgress.Report();
361361

362362
var deleteFilePaths = source.Select(s => s.Path).Distinct();
363-
var deleteFromRecycleBin = source.Any() && WindowsRecycleBinService.IsRecycled(source.ElementAt(0).Path);
363+
var deleteFromRecycleBin = source.Any() && StorageTrashBinService.IsUnderTrashBin(source.ElementAt(0).Path);
364364

365365
permanently |= deleteFromRecycleBin;
366366

@@ -847,9 +847,9 @@ private async Task<DialogResult> GetFileListDialog(IEnumerable<string> source, s
847847
List<ShellFileItem> binItems = null;
848848
foreach (var src in source)
849849
{
850-
if (WindowsRecycleBinService.IsRecycled(src))
850+
if (StorageTrashBinService.IsUnderTrashBin(src))
851851
{
852-
binItems ??= await WindowsRecycleBinService.GetAllRecycleBinFoldersAsync();
852+
binItems ??= await StorageTrashBinService.GetAllRecycleBinFoldersAsync();
853853

854854
// Might still be null because we're deserializing the list from Json
855855
if (!binItems.IsEmpty())

0 commit comments

Comments
 (0)