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