Stephane@243
|
1 |
using Microsoft.Win32.SafeHandles;
|
Stephane@243
|
2 |
using System;
|
StephaneLenclud@240
|
3 |
using System.Collections.Generic;
|
Stephane@243
|
4 |
using System.Diagnostics;
|
Stephane@243
|
5 |
using System.IO;
|
StephaneLenclud@240
|
6 |
using System.Linq;
|
Stephane@243
|
7 |
using System.Runtime.Serialization;
|
StephaneLenclud@240
|
8 |
using System.Text;
|
StephaneLenclud@240
|
9 |
using System.Threading.Tasks;
|
Stephane@243
|
10 |
using SharpLib.Win32;
|
Stephane@243
|
11 |
using System.ComponentModel;
|
Stephane@243
|
12 |
using System.Runtime.InteropServices;
|
StephaneLenclud@240
|
13 |
|
StephaneLenclud@240
|
14 |
namespace SharpLib.Ear
|
StephaneLenclud@240
|
15 |
{
|
Stephane@243
|
16 |
[DataContract]
|
Stephane@243
|
17 |
[AttributeObject(Id = "Action.OpticalDrive.Eject", Name = "Eject", Description = "Eject media from an optical drive.")]
|
Stephane@243
|
18 |
public class ActionOpticalDriveEject : Action
|
StephaneLenclud@240
|
19 |
{
|
Stephane@243
|
20 |
[DataMember]
|
Stephane@243
|
21 |
[AttributeObjectProperty
|
Stephane@243
|
22 |
(
|
Stephane@243
|
23 |
Id = "Action.OpticalDrive.Eject.Drive",
|
Stephane@243
|
24 |
Name = "Drive to eject",
|
Stephane@243
|
25 |
Description = "Select the drive you want to eject."
|
Stephane@243
|
26 |
)
|
Stephane@243
|
27 |
]
|
Stephane@243
|
28 |
public PropertyComboBox Drive { get; set; } = new PropertyComboBox();
|
Stephane@243
|
29 |
|
Stephane@243
|
30 |
|
Stephane@243
|
31 |
protected override void DoConstruct()
|
Stephane@243
|
32 |
{
|
Stephane@243
|
33 |
base.DoConstruct();
|
Stephane@243
|
34 |
PopulateOpticalDrives();
|
Stephane@243
|
35 |
CheckCurrentItem();
|
Stephane@243
|
36 |
}
|
Stephane@243
|
37 |
|
Stephane@243
|
38 |
|
StephaneLenclud@264
|
39 |
public override string BriefBase()
|
Stephane@243
|
40 |
{
|
StephaneLenclud@260
|
41 |
return AttributeName + " " + Drive.CurrentItem ;
|
Stephane@243
|
42 |
}
|
Stephane@243
|
43 |
|
Stephane@243
|
44 |
public override bool IsValid()
|
Stephane@243
|
45 |
{
|
Stephane@243
|
46 |
//This object is valid if our current item is contained in our drive list
|
Stephane@243
|
47 |
return Drive.Items.Contains(Drive.CurrentItem);
|
Stephane@243
|
48 |
}
|
Stephane@243
|
49 |
|
StephaneLenclud@258
|
50 |
protected override async Task DoExecute()
|
Stephane@243
|
51 |
{
|
Stephane@243
|
52 |
DriveEject(Drive.CurrentItem);
|
Stephane@243
|
53 |
}
|
Stephane@243
|
54 |
|
Stephane@243
|
55 |
|
Stephane@243
|
56 |
private void CheckCurrentItem()
|
Stephane@243
|
57 |
{
|
Stephane@243
|
58 |
if (!Drive.Items.Contains(Drive.CurrentItem) && Drive.Items.Count>0)
|
Stephane@243
|
59 |
{
|
Stephane@243
|
60 |
//Current item unknown, reset it then
|
Stephane@243
|
61 |
Drive.CurrentItem = Drive.Items[0];
|
Stephane@243
|
62 |
}
|
Stephane@243
|
63 |
}
|
Stephane@243
|
64 |
|
Stephane@243
|
65 |
/// <summary>
|
Stephane@243
|
66 |
///
|
Stephane@243
|
67 |
/// </summary>
|
Stephane@243
|
68 |
private void PopulateOpticalDrives()
|
Stephane@243
|
69 |
{
|
Stephane@243
|
70 |
//Reset our list of drives
|
Stephane@243
|
71 |
Drive.Items = new List<string>();
|
Stephane@243
|
72 |
//Go through each drives on our system and collected the optical ones in our list
|
Stephane@243
|
73 |
DriveInfo[] allDrives = DriveInfo.GetDrives();
|
Stephane@243
|
74 |
foreach (DriveInfo d in allDrives)
|
Stephane@243
|
75 |
{
|
Stephane@243
|
76 |
Debug.WriteLine("Drive " + d.Name);
|
Stephane@243
|
77 |
Debug.WriteLine(" Drive type: {0}", d.DriveType);
|
Stephane@243
|
78 |
|
Stephane@243
|
79 |
if (d.DriveType == DriveType.CDRom)
|
Stephane@243
|
80 |
{
|
Stephane@243
|
81 |
//This is an optical drive, add it now
|
Stephane@243
|
82 |
Drive.Items.Add(d.Name.Substring(0, 2));
|
Stephane@243
|
83 |
}
|
Stephane@243
|
84 |
}
|
Stephane@243
|
85 |
}
|
Stephane@243
|
86 |
|
Stephane@243
|
87 |
|
Stephane@243
|
88 |
/// <summary>
|
Stephane@243
|
89 |
///
|
Stephane@243
|
90 |
/// </summary>
|
Stephane@243
|
91 |
/// <param name="aPrefix"></param>
|
Stephane@243
|
92 |
static private void CheckLastError(string aPrefix)
|
Stephane@243
|
93 |
{
|
Stephane@243
|
94 |
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
|
Stephane@243
|
95 |
Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage);
|
Stephane@243
|
96 |
}
|
Stephane@243
|
97 |
|
Stephane@243
|
98 |
/// <summary>
|
Stephane@243
|
99 |
///
|
Stephane@243
|
100 |
/// </summary>
|
Stephane@243
|
101 |
/// <param name="data"></param>
|
Stephane@243
|
102 |
/// <returns></returns>
|
Stephane@243
|
103 |
static private IntPtr MarshalToPointer(object data)
|
Stephane@243
|
104 |
{
|
Stephane@243
|
105 |
IntPtr buf = Marshal.AllocHGlobal(
|
Stephane@243
|
106 |
Marshal.SizeOf(data));
|
Stephane@243
|
107 |
Marshal.StructureToPtr(data,
|
Stephane@243
|
108 |
buf, false);
|
Stephane@243
|
109 |
return buf;
|
Stephane@243
|
110 |
}
|
Stephane@243
|
111 |
|
Stephane@243
|
112 |
/// <summary>
|
Stephane@243
|
113 |
///
|
Stephane@243
|
114 |
/// </summary>
|
Stephane@243
|
115 |
/// <returns></returns>
|
Stephane@243
|
116 |
static private SafeFileHandle OpenVolume(string aDriveName)
|
Stephane@243
|
117 |
{
|
Stephane@243
|
118 |
return Function.CreateFile("\\\\.\\" + aDriveName,
|
Stephane@243
|
119 |
SharpLib.Win32.FileAccess.GENERIC_READ,
|
Stephane@243
|
120 |
SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
|
Stephane@243
|
121 |
IntPtr.Zero,
|
Stephane@243
|
122 |
CreationDisposition.OPEN_EXISTING,
|
Stephane@243
|
123 |
0,
|
Stephane@243
|
124 |
IntPtr.Zero);
|
Stephane@243
|
125 |
}
|
Stephane@243
|
126 |
|
Stephane@243
|
127 |
/// <summary>
|
Stephane@243
|
128 |
///
|
Stephane@243
|
129 |
/// </summary>
|
Stephane@243
|
130 |
/// <param name="aVolume"></param>
|
Stephane@243
|
131 |
/// <returns></returns>
|
Stephane@243
|
132 |
static private bool LockVolume(SafeFileHandle aVolume)
|
Stephane@243
|
133 |
{
|
Stephane@243
|
134 |
//Hope that's doing what I think it does
|
Stephane@243
|
135 |
IntPtr dwBytesReturned = new IntPtr();
|
Stephane@243
|
136 |
//Should not be needed but I'm not sure how to pass NULL in there.
|
Stephane@243
|
137 |
OVERLAPPED overlapped = new OVERLAPPED();
|
Stephane@243
|
138 |
|
Stephane@243
|
139 |
int tries = 0;
|
Stephane@243
|
140 |
const int KMaxTries = 100;
|
Stephane@243
|
141 |
const int KSleepTime = 10;
|
Stephane@243
|
142 |
bool success = false;
|
Stephane@243
|
143 |
|
Stephane@243
|
144 |
while (!success && tries < KMaxTries)
|
Stephane@243
|
145 |
{
|
Stephane@243
|
146 |
success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
|
Stephane@243
|
147 |
System.Threading.Thread.Sleep(KSleepTime);
|
Stephane@243
|
148 |
tries++;
|
Stephane@243
|
149 |
}
|
Stephane@243
|
150 |
|
Stephane@243
|
151 |
CheckLastError("Lock volume: ");
|
Stephane@243
|
152 |
|
Stephane@243
|
153 |
return success;
|
Stephane@243
|
154 |
}
|
Stephane@243
|
155 |
|
Stephane@243
|
156 |
/// <summary>
|
Stephane@243
|
157 |
///
|
Stephane@243
|
158 |
/// </summary>
|
Stephane@243
|
159 |
/// <param name="aVolume"></param>
|
Stephane@243
|
160 |
/// <returns></returns>
|
Stephane@243
|
161 |
static private bool DismountVolume(SafeFileHandle aVolume)
|
Stephane@243
|
162 |
{
|
Stephane@243
|
163 |
//Hope that's doing what I think it does
|
Stephane@243
|
164 |
IntPtr dwBytesReturned = new IntPtr();
|
Stephane@243
|
165 |
//Should not be needed but I'm not sure how to pass NULL in there.
|
Stephane@243
|
166 |
OVERLAPPED overlapped = new OVERLAPPED();
|
Stephane@243
|
167 |
|
Stephane@243
|
168 |
bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
|
Stephane@243
|
169 |
CheckLastError("Dismount volume: ");
|
Stephane@243
|
170 |
return res;
|
Stephane@243
|
171 |
}
|
Stephane@243
|
172 |
|
Stephane@243
|
173 |
|
Stephane@243
|
174 |
|
Stephane@243
|
175 |
/// <summary>
|
Stephane@243
|
176 |
///
|
Stephane@243
|
177 |
/// </summary>
|
Stephane@243
|
178 |
/// <param name="aVolume"></param>
|
Stephane@243
|
179 |
/// <param name="aPreventRemoval"></param>
|
Stephane@243
|
180 |
/// <returns></returns>
|
Stephane@243
|
181 |
static private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
|
Stephane@243
|
182 |
{
|
Stephane@243
|
183 |
//Hope that's doing what I think it does
|
Stephane@243
|
184 |
IntPtr dwBytesReturned = new IntPtr();
|
Stephane@243
|
185 |
//Should not be needed but I'm not sure how to pass NULL in there.
|
Stephane@243
|
186 |
OVERLAPPED overlapped = new OVERLAPPED();
|
Stephane@243
|
187 |
//
|
Stephane@243
|
188 |
PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
|
Stephane@243
|
189 |
preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
|
Stephane@243
|
190 |
IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
|
Stephane@243
|
191 |
|
Stephane@243
|
192 |
bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
|
Stephane@243
|
193 |
CheckLastError("Media removal: ");
|
Stephane@243
|
194 |
Marshal.FreeHGlobal(preventMediaRemovalParam);
|
Stephane@243
|
195 |
|
Stephane@243
|
196 |
return result;
|
Stephane@243
|
197 |
}
|
Stephane@243
|
198 |
|
Stephane@243
|
199 |
/// <summary>
|
Stephane@243
|
200 |
/// Eject optical drive media opening the tray if any.
|
Stephane@243
|
201 |
/// </summary>
|
Stephane@243
|
202 |
/// <param name="aVolume"></param>
|
Stephane@243
|
203 |
/// <returns></returns>
|
Stephane@243
|
204 |
static private bool MediaEject(SafeFileHandle aVolume)
|
Stephane@243
|
205 |
{
|
Stephane@243
|
206 |
//Hope that's doing what I think it does
|
Stephane@243
|
207 |
IntPtr dwBytesReturned = new IntPtr();
|
Stephane@243
|
208 |
//Should not be needed but I'm not sure how to pass NULL in there.
|
Stephane@243
|
209 |
OVERLAPPED overlapped = new OVERLAPPED();
|
Stephane@243
|
210 |
|
Stephane@243
|
211 |
bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
|
Stephane@243
|
212 |
CheckLastError("Media eject: ");
|
Stephane@243
|
213 |
return res;
|
Stephane@243
|
214 |
}
|
Stephane@243
|
215 |
|
Stephane@243
|
216 |
/// <summary>
|
Stephane@243
|
217 |
/// Close an optical drive tray.
|
Stephane@243
|
218 |
/// </summary>
|
Stephane@243
|
219 |
/// <param name="aVolume"></param>
|
Stephane@243
|
220 |
/// <returns></returns>
|
Stephane@243
|
221 |
static private bool MediaLoad(SafeFileHandle aVolume)
|
Stephane@243
|
222 |
{
|
Stephane@243
|
223 |
//Hope that's doing what I think it does
|
Stephane@243
|
224 |
IntPtr dwBytesReturned = new IntPtr();
|
Stephane@243
|
225 |
//Should not be needed but I'm not sure how to pass NULL in there.
|
Stephane@243
|
226 |
OVERLAPPED overlapped = new OVERLAPPED();
|
Stephane@243
|
227 |
|
Stephane@243
|
228 |
bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
|
Stephane@243
|
229 |
CheckLastError("Media load: ");
|
Stephane@243
|
230 |
return res;
|
Stephane@243
|
231 |
}
|
Stephane@243
|
232 |
|
Stephane@243
|
233 |
/// <summary>
|
Stephane@243
|
234 |
///
|
Stephane@243
|
235 |
/// </summary>
|
Stephane@243
|
236 |
/// <param name="aVolume"></param>
|
Stephane@243
|
237 |
/// <returns></returns>
|
Stephane@243
|
238 |
static private bool StorageCheckVerify(SafeFileHandle aVolume)
|
Stephane@243
|
239 |
{
|
Stephane@243
|
240 |
//Hope that's doing what I think it does
|
Stephane@243
|
241 |
IntPtr dwBytesReturned = new IntPtr();
|
Stephane@243
|
242 |
//Should not be needed but I'm not sure how to pass NULL in there.
|
Stephane@243
|
243 |
OVERLAPPED overlapped = new OVERLAPPED();
|
Stephane@243
|
244 |
|
Stephane@243
|
245 |
bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
|
Stephane@243
|
246 |
|
Stephane@243
|
247 |
CheckLastError("Check verify: ");
|
Stephane@243
|
248 |
|
Stephane@243
|
249 |
return res;
|
Stephane@243
|
250 |
}
|
Stephane@243
|
251 |
|
Stephane@243
|
252 |
|
Stephane@243
|
253 |
/// <summary>
|
Stephane@243
|
254 |
/// Perform media ejection.
|
Stephane@243
|
255 |
/// </summary>
|
Stephane@243
|
256 |
static private void DriveEject(string aDrive)
|
Stephane@243
|
257 |
{
|
Stephane@243
|
258 |
string drive = aDrive;
|
Stephane@243
|
259 |
if (drive.Length != 2)
|
Stephane@243
|
260 |
{
|
Stephane@243
|
261 |
//Not a proper drive spec.
|
Stephane@243
|
262 |
//Probably 'None' selected.
|
Stephane@243
|
263 |
return;
|
Stephane@243
|
264 |
}
|
Stephane@243
|
265 |
|
Stephane@243
|
266 |
SafeFileHandle handle = OpenVolume(drive);
|
Stephane@243
|
267 |
if (handle.IsInvalid)
|
Stephane@243
|
268 |
{
|
Stephane@243
|
269 |
CheckLastError("ERROR: Failed to open volume: ");
|
Stephane@243
|
270 |
return;
|
Stephane@243
|
271 |
}
|
Stephane@243
|
272 |
|
Stephane@243
|
273 |
if (LockVolume(handle) && DismountVolume(handle))
|
Stephane@243
|
274 |
{
|
Stephane@243
|
275 |
Debug.WriteLine("Volume was dismounted.");
|
Stephane@243
|
276 |
|
Stephane@243
|
277 |
if (PreventRemovalOfVolume(handle, false))
|
Stephane@243
|
278 |
{
|
Stephane@243
|
279 |
//StorageCheckVerify(handle);
|
Stephane@243
|
280 |
|
Stephane@243
|
281 |
DateTime before;
|
Stephane@243
|
282 |
before = DateTime.Now;
|
Stephane@243
|
283 |
bool ejectSuccess = MediaEject(handle);
|
Stephane@243
|
284 |
double ms = (DateTime.Now - before).TotalMilliseconds;
|
Stephane@243
|
285 |
|
Stephane@243
|
286 |
//We assume that if it take more than a certain time to for eject to execute it means we actually ejected.
|
Stephane@243
|
287 |
//If our eject completes too rapidly we assume the tray is already open and we will try to close it.
|
Stephane@243
|
288 |
if (ejectSuccess && ms > 100)
|
Stephane@243
|
289 |
{
|
Stephane@243
|
290 |
Debug.WriteLine("Media was ejected");
|
Stephane@243
|
291 |
}
|
Stephane@243
|
292 |
else if (MediaLoad(handle))
|
Stephane@243
|
293 |
{
|
Stephane@243
|
294 |
Debug.WriteLine("Media was loaded");
|
Stephane@243
|
295 |
}
|
Stephane@243
|
296 |
}
|
Stephane@243
|
297 |
}
|
Stephane@243
|
298 |
else
|
Stephane@243
|
299 |
{
|
Stephane@243
|
300 |
Debug.WriteLine("Volume lock or dismount failed.");
|
Stephane@243
|
301 |
}
|
Stephane@243
|
302 |
|
Stephane@243
|
303 |
//This is needed to make sure we can open the volume next time around
|
Stephane@243
|
304 |
handle.Dispose();
|
Stephane@243
|
305 |
}
|
Stephane@243
|
306 |
|
StephaneLenclud@240
|
307 |
}
|
StephaneLenclud@240
|
308 |
}
|