SharpLibEar/ActionOpticalDriveEject.cs
author StephaneLenclud
Wed, 31 Aug 2016 20:20:32 +0200
changeset 265 82e87f4956ea
parent 260 d44943088c67
permissions -rw-r--r--
Actions can now have children.
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
}