Stephane@243: using Microsoft.Win32.SafeHandles;
Stephane@243: using System;
StephaneLenclud@240: using System.Collections.Generic;
Stephane@243: using System.Diagnostics;
Stephane@243: using System.IO;
StephaneLenclud@240: using System.Linq;
Stephane@243: using System.Runtime.Serialization;
StephaneLenclud@240: using System.Text;
StephaneLenclud@240: using System.Threading.Tasks;
Stephane@243: using SharpLib.Win32;
Stephane@243: using System.ComponentModel;
Stephane@243: using System.Runtime.InteropServices;
StephaneLenclud@240:
StephaneLenclud@240: namespace SharpLib.Ear
StephaneLenclud@240: {
Stephane@243: [DataContract]
Stephane@243: [AttributeObject(Id = "Action.OpticalDrive.Eject", Name = "Eject", Description = "Eject media from an optical drive.")]
Stephane@243: public class ActionOpticalDriveEject : Action
StephaneLenclud@240: {
Stephane@243: [DataMember]
Stephane@243: [AttributeObjectProperty
Stephane@243: (
Stephane@243: Id = "Action.OpticalDrive.Eject.Drive",
Stephane@243: Name = "Drive to eject",
Stephane@243: Description = "Select the drive you want to eject."
Stephane@243: )
Stephane@243: ]
Stephane@243: public PropertyComboBox Drive { get; set; } = new PropertyComboBox();
Stephane@243:
Stephane@243:
Stephane@243: protected override void DoConstruct()
Stephane@243: {
Stephane@243: base.DoConstruct();
Stephane@243: PopulateOpticalDrives();
Stephane@243: CheckCurrentItem();
Stephane@243: }
Stephane@243:
Stephane@243:
StephaneLenclud@264: public override string BriefBase()
Stephane@243: {
StephaneLenclud@260: return AttributeName + " " + Drive.CurrentItem ;
Stephane@243: }
Stephane@243:
Stephane@243: public override bool IsValid()
Stephane@243: {
Stephane@243: //This object is valid if our current item is contained in our drive list
Stephane@243: return Drive.Items.Contains(Drive.CurrentItem);
Stephane@243: }
Stephane@243:
StephaneLenclud@258: protected override async Task DoExecute()
Stephane@243: {
Stephane@243: DriveEject(Drive.CurrentItem);
Stephane@243: }
Stephane@243:
Stephane@243:
Stephane@243: private void CheckCurrentItem()
Stephane@243: {
Stephane@243: if (!Drive.Items.Contains(Drive.CurrentItem) && Drive.Items.Count>0)
Stephane@243: {
Stephane@243: //Current item unknown, reset it then
Stephane@243: Drive.CurrentItem = Drive.Items[0];
Stephane@243: }
Stephane@243: }
Stephane@243:
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: private void PopulateOpticalDrives()
Stephane@243: {
Stephane@243: //Reset our list of drives
Stephane@243: Drive.Items = new List();
Stephane@243: //Go through each drives on our system and collected the optical ones in our list
Stephane@243: DriveInfo[] allDrives = DriveInfo.GetDrives();
Stephane@243: foreach (DriveInfo d in allDrives)
Stephane@243: {
Stephane@243: Debug.WriteLine("Drive " + d.Name);
Stephane@243: Debug.WriteLine(" Drive type: {0}", d.DriveType);
Stephane@243:
Stephane@243: if (d.DriveType == DriveType.CDRom)
Stephane@243: {
Stephane@243: //This is an optical drive, add it now
Stephane@243: Drive.Items.Add(d.Name.Substring(0, 2));
Stephane@243: }
Stephane@243: }
Stephane@243: }
Stephane@243:
Stephane@243:
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private void CheckLastError(string aPrefix)
Stephane@243: {
Stephane@243: string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Stephane@243: Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage);
Stephane@243: }
Stephane@243:
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private IntPtr MarshalToPointer(object data)
Stephane@243: {
Stephane@243: IntPtr buf = Marshal.AllocHGlobal(
Stephane@243: Marshal.SizeOf(data));
Stephane@243: Marshal.StructureToPtr(data,
Stephane@243: buf, false);
Stephane@243: return buf;
Stephane@243: }
Stephane@243:
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private SafeFileHandle OpenVolume(string aDriveName)
Stephane@243: {
Stephane@243: return Function.CreateFile("\\\\.\\" + aDriveName,
Stephane@243: SharpLib.Win32.FileAccess.GENERIC_READ,
Stephane@243: SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
Stephane@243: IntPtr.Zero,
Stephane@243: CreationDisposition.OPEN_EXISTING,
Stephane@243: 0,
Stephane@243: IntPtr.Zero);
Stephane@243: }
Stephane@243:
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private bool LockVolume(SafeFileHandle aVolume)
Stephane@243: {
Stephane@243: //Hope that's doing what I think it does
Stephane@243: IntPtr dwBytesReturned = new IntPtr();
Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there.
Stephane@243: OVERLAPPED overlapped = new OVERLAPPED();
Stephane@243:
Stephane@243: int tries = 0;
Stephane@243: const int KMaxTries = 100;
Stephane@243: const int KSleepTime = 10;
Stephane@243: bool success = false;
Stephane@243:
Stephane@243: while (!success && tries < KMaxTries)
Stephane@243: {
Stephane@243: success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
Stephane@243: System.Threading.Thread.Sleep(KSleepTime);
Stephane@243: tries++;
Stephane@243: }
Stephane@243:
Stephane@243: CheckLastError("Lock volume: ");
Stephane@243:
Stephane@243: return success;
Stephane@243: }
Stephane@243:
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private bool DismountVolume(SafeFileHandle aVolume)
Stephane@243: {
Stephane@243: //Hope that's doing what I think it does
Stephane@243: IntPtr dwBytesReturned = new IntPtr();
Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there.
Stephane@243: OVERLAPPED overlapped = new OVERLAPPED();
Stephane@243:
Stephane@243: bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
Stephane@243: CheckLastError("Dismount volume: ");
Stephane@243: return res;
Stephane@243: }
Stephane@243:
Stephane@243:
Stephane@243:
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
Stephane@243: {
Stephane@243: //Hope that's doing what I think it does
Stephane@243: IntPtr dwBytesReturned = new IntPtr();
Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there.
Stephane@243: OVERLAPPED overlapped = new OVERLAPPED();
Stephane@243: //
Stephane@243: PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
Stephane@243: preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
Stephane@243: IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
Stephane@243:
Stephane@243: bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
Stephane@243: CheckLastError("Media removal: ");
Stephane@243: Marshal.FreeHGlobal(preventMediaRemovalParam);
Stephane@243:
Stephane@243: return result;
Stephane@243: }
Stephane@243:
Stephane@243: ///
Stephane@243: /// Eject optical drive media opening the tray if any.
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private bool MediaEject(SafeFileHandle aVolume)
Stephane@243: {
Stephane@243: //Hope that's doing what I think it does
Stephane@243: IntPtr dwBytesReturned = new IntPtr();
Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there.
Stephane@243: OVERLAPPED overlapped = new OVERLAPPED();
Stephane@243:
Stephane@243: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
Stephane@243: CheckLastError("Media eject: ");
Stephane@243: return res;
Stephane@243: }
Stephane@243:
Stephane@243: ///
Stephane@243: /// Close an optical drive tray.
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private bool MediaLoad(SafeFileHandle aVolume)
Stephane@243: {
Stephane@243: //Hope that's doing what I think it does
Stephane@243: IntPtr dwBytesReturned = new IntPtr();
Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there.
Stephane@243: OVERLAPPED overlapped = new OVERLAPPED();
Stephane@243:
Stephane@243: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
Stephane@243: CheckLastError("Media load: ");
Stephane@243: return res;
Stephane@243: }
Stephane@243:
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: ///
Stephane@243: static private bool StorageCheckVerify(SafeFileHandle aVolume)
Stephane@243: {
Stephane@243: //Hope that's doing what I think it does
Stephane@243: IntPtr dwBytesReturned = new IntPtr();
Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there.
Stephane@243: OVERLAPPED overlapped = new OVERLAPPED();
Stephane@243:
Stephane@243: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
Stephane@243:
Stephane@243: CheckLastError("Check verify: ");
Stephane@243:
Stephane@243: return res;
Stephane@243: }
Stephane@243:
Stephane@243:
Stephane@243: ///
Stephane@243: /// Perform media ejection.
Stephane@243: ///
Stephane@243: static private void DriveEject(string aDrive)
Stephane@243: {
Stephane@243: string drive = aDrive;
Stephane@243: if (drive.Length != 2)
Stephane@243: {
Stephane@243: //Not a proper drive spec.
Stephane@243: //Probably 'None' selected.
Stephane@243: return;
Stephane@243: }
Stephane@243:
Stephane@243: SafeFileHandle handle = OpenVolume(drive);
Stephane@243: if (handle.IsInvalid)
Stephane@243: {
Stephane@243: CheckLastError("ERROR: Failed to open volume: ");
Stephane@243: return;
Stephane@243: }
Stephane@243:
Stephane@243: if (LockVolume(handle) && DismountVolume(handle))
Stephane@243: {
Stephane@243: Debug.WriteLine("Volume was dismounted.");
Stephane@243:
Stephane@243: if (PreventRemovalOfVolume(handle, false))
Stephane@243: {
Stephane@243: //StorageCheckVerify(handle);
Stephane@243:
Stephane@243: DateTime before;
Stephane@243: before = DateTime.Now;
Stephane@243: bool ejectSuccess = MediaEject(handle);
Stephane@243: double ms = (DateTime.Now - before).TotalMilliseconds;
Stephane@243:
Stephane@243: //We assume that if it take more than a certain time to for eject to execute it means we actually ejected.
Stephane@243: //If our eject completes too rapidly we assume the tray is already open and we will try to close it.
Stephane@243: if (ejectSuccess && ms > 100)
Stephane@243: {
Stephane@243: Debug.WriteLine("Media was ejected");
Stephane@243: }
Stephane@243: else if (MediaLoad(handle))
Stephane@243: {
Stephane@243: Debug.WriteLine("Media was loaded");
Stephane@243: }
Stephane@243: }
Stephane@243: }
Stephane@243: else
Stephane@243: {
Stephane@243: Debug.WriteLine("Volume lock or dismount failed.");
Stephane@243: }
Stephane@243:
Stephane@243: //This is needed to make sure we can open the volume next time around
Stephane@243: handle.Dispose();
Stephane@243: }
Stephane@243:
StephaneLenclud@240: }
StephaneLenclud@240: }