Skip to content

Commit 153ec26

Browse files
committed
Merge branch '5bfa/CQ-IStorageDevicesService' of https://github.com/0x5bfa/Files into 5bfa/CQ-IStorageDevicesService
2 parents 2f60d47 + b4faf6c commit 153ec26

28 files changed

+353
-540
lines changed

Diff for: src/Files.App/Data/Items/DeviceEvent.cs renamed to src/Files.App.Storage/Data/DeviceEvent.cs

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

4-
namespace Files.App.Data.Items
4+
namespace Files.App.Storage
55
{
6-
public enum DeviceEvent
6+
internal enum DeviceEvent
77
{
88
Added,
99

Diff for: src/Files.App/Data/EventArguments/EventArrivedEventArgs.cs renamed to src/Files.App.Storage/Data/EventArrivedEventArgs.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
// Licensed under the MIT License. See the LICENSE.
33

44
using Microsoft.Management.Infrastructure;
5-
using System;
65

7-
namespace Files.App.Data.EventArguments
6+
namespace Files.App.Storage
87
{
98
/// <summary>
109
/// CimWatcher event args, which contains CimSubscriptionResult

Diff for: src/Files.App.Storage/Files.App.Storage.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
<ItemGroup>
1515
<PackageReference Include="FluentFTP" Version="43.0.1" />
16+
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
17+
<PackageReference Include="Microsoft.Management.Infrastructure.Runtime.Win" Version="3.0.0" />
1618
</ItemGroup>
1719

1820
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">

Diff for: src/Files.App.Storage/Watchers/DeviceWatcher.cs

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright (c) 2024 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using Microsoft.Management.Infrastructure;
5+
using Windows.Devices.Enumeration;
6+
using Windows.Devices.Portable;
7+
8+
namespace Files.App.Storage.Watchers
9+
{
10+
/// <inheritdoc cref="IDeviceWatcher"/>
11+
public class DeviceWatcher : IDeviceWatcher
12+
{
13+
private Windows.Devices.Enumeration.DeviceWatcher? _winDeviceWatcher;
14+
15+
private ManagementEventWatcher? _mmiInsertWatcher, _mmiRemoveWatcher, _mmiModifyWatcher;
16+
17+
/// <inheritdoc/>
18+
public bool CanBeStarted
19+
=> _winDeviceWatcher?.Status is DeviceWatcherStatus.Created or DeviceWatcherStatus.Stopped or DeviceWatcherStatus.Aborted;
20+
21+
/// <inheritdoc/>
22+
public event EventHandler<DeviceEventArgs>? DeviceAdded;
23+
24+
/// <inheritdoc/>
25+
public event EventHandler<DeviceEventArgs>? DeviceChanged;
26+
27+
/// <inheritdoc/>
28+
public event EventHandler<DeviceEventArgs>? DeviceDeleted;
29+
30+
/// <inheritdoc/>
31+
public event EventHandler<DeviceEventArgs>? DeviceInserted;
32+
33+
/// <inheritdoc/>
34+
public event EventHandler<DeviceEventArgs>? DeviceEjected;
35+
36+
/// <inheritdoc/>
37+
public event EventHandler? EnumerationCompleted;
38+
39+
/// <summary>
40+
/// Initializes an instance of <see cref="DeviceWatcher"/> class.
41+
/// </summary>
42+
public DeviceWatcher()
43+
{
44+
StartWatcher();
45+
}
46+
47+
/// <inheritdoc/>
48+
public void StartWatcher()
49+
{
50+
_winDeviceWatcher = DeviceInformation.CreateWatcher(StorageDevice.GetDeviceSelector());
51+
_winDeviceWatcher.Added += Watcher_Added;
52+
_winDeviceWatcher.Removed += Watcher_Removed;
53+
_winDeviceWatcher.EnumerationCompleted += Watcher_EnumerationCompleted;
54+
55+
var insertQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_LogicalDisk'";
56+
_mmiInsertWatcher = new(insertQuery);
57+
_mmiInsertWatcher.EventArrived += new EventArrivedEventHandler(Device_Inserted);
58+
_mmiInsertWatcher.Start();
59+
60+
var modifyQuery = "SELECT * FROM __InstanceModificationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_LogicalDisk' and TargetInstance.DriveType = 5";
61+
_mmiModifyWatcher = new(modifyQuery);
62+
_mmiModifyWatcher.EventArrived += new EventArrivedEventHandler(Device_Modified);
63+
_mmiModifyWatcher.Start();
64+
65+
var removeQuery = "SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_LogicalDisk'";
66+
_mmiRemoveWatcher = new(removeQuery);
67+
_mmiRemoveWatcher.EventArrived += new EventArrivedEventHandler(Device_Removed);
68+
_mmiRemoveWatcher.Start();
69+
}
70+
71+
/// <inheritdoc/>
72+
public void StopWatcher()
73+
{
74+
if (_winDeviceWatcher?.Status is DeviceWatcherStatus.Started or DeviceWatcherStatus.EnumerationCompleted)
75+
_winDeviceWatcher.Stop();
76+
77+
if (_winDeviceWatcher is not null)
78+
{
79+
_winDeviceWatcher.Added -= Watcher_Added;
80+
_winDeviceWatcher.Removed -= Watcher_Removed;
81+
_winDeviceWatcher.EnumerationCompleted -= Watcher_EnumerationCompleted;
82+
}
83+
84+
_mmiInsertWatcher?.Dispose();
85+
_mmiRemoveWatcher?.Dispose();
86+
_mmiModifyWatcher?.Dispose();
87+
_mmiInsertWatcher = null;
88+
_mmiRemoveWatcher = null;
89+
_mmiModifyWatcher = null;
90+
}
91+
92+
private void Watcher_Added(Windows.Devices.Enumeration.DeviceWatcher sender, DeviceInformation args)
93+
{
94+
DeviceAdded?.Invoke(this, new(args.Name, args.Id));
95+
}
96+
97+
private void Watcher_Removed(Windows.Devices.Enumeration.DeviceWatcher sender, DeviceInformationUpdate args)
98+
{
99+
DeviceDeleted?.Invoke(this, new(string.Empty, args.Id));
100+
}
101+
102+
private void Watcher_EnumerationCompleted(Windows.Devices.Enumeration.DeviceWatcher sender, object args)
103+
{
104+
EnumerationCompleted?.Invoke(this, EventArgs.Empty);
105+
}
106+
107+
private void Device_Modified(object sender, EventArrivedEventArgs e)
108+
{
109+
CimInstance obj = (CimInstance)e.NewEvent.Instance.CimInstanceProperties["TargetInstance"].Value;
110+
var deviceName = obj.CimInstanceProperties["Name"]?.Value.ToString();
111+
var deviceId = obj.CimInstanceProperties["DeviceID"]?.Value.ToString();
112+
var volumeName = obj.CimInstanceProperties["VolumeName"]?.Value.ToString();
113+
var eventType = volumeName is not null ? DeviceEvent.Inserted : DeviceEvent.Ejected;
114+
115+
Debug.WriteLine($"Drive modify event: {deviceName}, {deviceId}, {eventType}");
116+
117+
if (eventType is DeviceEvent.Inserted)
118+
DeviceInserted?.Invoke(sender, new DeviceEventArgs(deviceName ?? string.Empty, deviceId ?? string.Empty));
119+
else
120+
DeviceEjected?.Invoke(sender, new DeviceEventArgs(deviceName ?? string.Empty, deviceId ?? string.Empty));
121+
}
122+
123+
private void Device_Removed(object sender, EventArrivedEventArgs e)
124+
{
125+
CimInstance obj = (CimInstance)e.NewEvent.Instance.CimInstanceProperties["TargetInstance"].Value;
126+
var deviceName = (string)obj.CimInstanceProperties["Name"].Value;
127+
var deviceId = (string)obj.CimInstanceProperties["DeviceID"].Value;
128+
129+
Debug.WriteLine($"Drive removed event: {deviceName}, {deviceId}");
130+
131+
DeviceDeleted?.Invoke(sender, new DeviceEventArgs(deviceName, deviceId));
132+
}
133+
134+
private void Device_Inserted(object sender, EventArrivedEventArgs e)
135+
{
136+
CimInstance obj = (CimInstance)e.NewEvent.Instance.CimInstanceProperties["TargetInstance"].Value;
137+
var deviceName = (string)obj.CimInstanceProperties["Name"].Value;
138+
var deviceId = (string)obj.CimInstanceProperties["DeviceID"].Value;
139+
140+
Debug.WriteLine($"Drive added event: {deviceName}, {deviceId}");
141+
142+
DeviceAdded?.Invoke(sender, new DeviceEventArgs(deviceName, deviceId));
143+
}
144+
145+
/// <inheritdoc/>
146+
public void Dispose()
147+
{
148+
StopWatcher();
149+
}
150+
}
151+
}

Diff for: src/Files.App/Helpers/WMI/ManagementEventWatcher.cs renamed to src/Files.App.Storage/Watchers/ManagementEventWatcher.cs

+9-28
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,19 @@
33

44
using Microsoft.Management.Infrastructure;
55
using Microsoft.Management.Infrastructure.Generic;
6-
using System;
7-
using System.Threading;
86

9-
namespace Files.App.Helpers
7+
namespace Files.App.Storage.Watchers
108
{
119
public delegate void EventArrivedEventHandler(object sender, EventArrivedEventArgs e);
1210

1311
/// <summary>
14-
/// A public class used to start/stop the subscription to specific indication source,
15-
/// and listen to the incoming indications, event <see cref="EventArrived" />
16-
/// will be raised for each cimindication.
17-
/// Original Sourced from: https://codereview.stackexchange.com/questions/255055/trying-to-replace-managementeventwatcher-class-in-system-management-to-switch-to
18-
/// Adapted to newer versions of MMI
12+
/// Watches to start/stop the subscription to specific indication source, and listen to the incoming indications.
1913
/// </summary>
14+
/// <remarks>
15+
/// <see cref="EventArrived" /> will be raised for each CIM Indication.
16+
/// <br/>
17+
/// Credit: <a href="https://codereview.stackexchange.com/questions/255055/trying-to-replace-managementeventwatcher-class-in-system-management-to-switch-to">original source</a>.
18+
/// </remarks>
2019
public class ManagementEventWatcher : IDisposable, IObserver<CimSubscriptionResult>
2120
{
2221
internal enum CimWatcherStatus
@@ -46,15 +45,11 @@ internal enum CimWatcherStatus
4645
/// <summary>
4746
/// Initializes a new instance of the <see cref="ManagementEventWatcher" /> class.
4847
/// </summary>
49-
/// <param name="queryExpression"></param>
50-
public ManagementEventWatcher(WqlEventQuery query)
48+
/// <param name="query"></param>
49+
public ManagementEventWatcher(string queryExpression)
5150
{
52-
string queryExpression = query.QueryExpression;
53-
5451
if (string.IsNullOrWhiteSpace(queryExpression))
55-
{
5652
throw new ArgumentNullException(nameof(queryExpression));
57-
}
5853

5954
_nameSpace = DefaultNameSpace;
6055
_queryDialect = DefaultQueryDialect;
@@ -71,9 +66,7 @@ public ManagementEventWatcher(WqlEventQuery query)
7166
public ManagementEventWatcher(string queryDialect, string queryExpression)
7267
{
7368
if (string.IsNullOrWhiteSpace(queryExpression))
74-
{
7569
throw new ArgumentNullException(nameof(queryExpression));
76-
}
7770

7871
_nameSpace = DefaultNameSpace;
7972
_queryDialect = queryDialect ?? DefaultQueryDialect;
@@ -91,9 +84,7 @@ public ManagementEventWatcher(string queryDialect, string queryExpression)
9184
public ManagementEventWatcher(string nameSpace, string queryDialect, string queryExpression)
9285
{
9386
if (string.IsNullOrWhiteSpace(queryExpression))
94-
{
9587
throw new ArgumentNullException(nameof(queryExpression));
96-
}
9788

9889
_nameSpace = nameSpace ?? DefaultNameSpace;
9990
_queryDialect = queryDialect ?? DefaultQueryDialect;
@@ -112,9 +103,7 @@ public ManagementEventWatcher(string nameSpace, string queryDialect, string quer
112103
public ManagementEventWatcher(string computerName, string nameSpace, string queryDialect, string queryExpression)
113104
{
114105
if (string.IsNullOrWhiteSpace(queryExpression))
115-
{
116106
throw new ArgumentNullException(nameof(queryExpression));
117-
}
118107

119108
_computerName = computerName;
120109
_nameSpace = nameSpace ?? DefaultNameSpace;
@@ -161,14 +150,10 @@ public void Start()
161150
lock (_myLock)
162151
{
163152
if (_isDisposed)
164-
{
165153
throw new ObjectDisposedException(nameof(ManagementEventWatcher));
166-
}
167154

168155
if (_cimWatcherStatus != CimWatcherStatus.Default && _cimWatcherStatus != CimWatcherStatus.Stopped)
169-
{
170156
return;
171-
}
172157

173158
_subscription = _cimObservable.Subscribe(this);
174159

@@ -181,14 +166,10 @@ public void Stop()
181166
lock (_myLock)
182167
{
183168
if (_isDisposed)
184-
{
185169
throw new ObjectDisposedException(nameof(ManagementEventWatcher));
186-
}
187170

188171
if (_cimWatcherStatus != CimWatcherStatus.Started)
189-
{
190172
return;
191-
}
192173

193174
_subscription?.Dispose();
194175

Diff for: src/Files.App/Actions/FileSystem/FormatDriveAction.cs

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

4-
using Files.App.Utils.Shell;
5-
64
namespace Files.App.Actions
75
{
86
internal sealed class FormatDriveAction : ObservableObject, IAction
97
{
108
private readonly IContentPageContext context;
119

12-
private readonly DrivesViewModel drivesViewModel;
10+
private readonly IRemovableDrivesService StorageDevicesService = Ioc.Default.GetRequiredService<IRemovableDrivesService>();
1311

1412
public string Label
1513
=> "FormatDriveText".GetLocalizedResource();
@@ -20,13 +18,12 @@ public string Description
2018
public bool IsExecutable =>
2119
context.HasItem &&
2220
!context.HasSelection &&
23-
(drivesViewModel.Drives.Cast<DriveItem>().FirstOrDefault(x =>
21+
(StorageDevicesService.Drives.Cast<DriveItem>().FirstOrDefault(x =>
2422
string.Equals(x.Path, context.Folder?.ItemPath))?.MenuOptions.ShowFormatDrive ?? false);
2523

2624
public FormatDriveAction()
2725
{
2826
context = Ioc.Default.GetRequiredService<IContentPageContext>();
29-
drivesViewModel = Ioc.Default.GetRequiredService<DrivesViewModel>();
3027

3128
context.PropertyChanged += Context_PropertyChanged;
3229
}
+8-19
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,27 @@
11
// Copyright (c) 2024 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4-
using Files.Core.Storage.Storables;
5-
64
namespace Files.App.Data.Contracts
75
{
86
/// <summary>
9-
/// Represents a service to enumerate drives and create a storage device watcher
7+
/// Represents a service to enumerate and update drives.
108
/// </summary>
11-
public interface IRemovableDrivesService
9+
public interface IRemovableDrivesService : INotifyPropertyChanged
1210
{
1311
/// <summary>
14-
/// Gets the primary system drive. This item is typically excluded when enumerating removable drives
15-
/// </summary>
16-
/// <returns>The location of the drive which the operating system is installed to.</returns>
17-
Task<ILocatableFolder> GetPrimaryDriveAsync();
18-
19-
/// <summary>
20-
/// Creates a watcher for storage devices
12+
/// Gets drives.
2113
/// </summary>
22-
/// <returns>The created storage device watcher</returns>
23-
IStorageDeviceWatcher CreateWatcher();
14+
ObservableCollection<ILocatableFolder> Drives { get; }
2415

2516
/// <summary>
26-
/// Enumerates all removable drives
17+
/// Gets or sets a value that indicates whether the application should show consent dialog when the primary drive isn't accessible.
2718
/// </summary>
28-
/// <returns>A collection of removable storage devices</returns>
29-
IAsyncEnumerable<ILocatableFolder> GetDrivesAsync();
19+
bool ShowUserConsentOnInit { get; set; }
3020

3121
/// <summary>
32-
/// Refreshes the properties of a drive
22+
/// Updates all connected devices.
3323
/// </summary>
34-
/// <param name="drive"></param>
3524
/// <returns></returns>
36-
Task UpdateDrivePropertiesAsync(ILocatableFolder drive);
25+
Task UpdateDrivesAsync();
3726
}
3827
}

0 commit comments

Comments
 (0)