# HG changeset patch # User Stephane Lenclud # Date 1471619574 -7200 # Node ID cc2251d065db413d4792614d059deb84ad9b006e # Parent 0a956121c273aab09d1b25b368b5495947f8da9d Optical drive eject action now functional. diff -r 0a956121c273 -r cc2251d065db Server/FormEditObject.cs --- a/Server/FormEditObject.cs Thu Aug 18 20:14:30 2016 +0200 +++ b/Server/FormEditObject.cs Fri Aug 19 17:12:54 2016 +0200 @@ -104,22 +104,25 @@ private void FetchPropertiesValue(T aObject) { int ctrlIndex = 0; + //For each of our properties foreach (PropertyInfo pi in aObject.GetType().GetProperties()) { - AttributeObjectProperty[] attributes = - ((AttributeObjectProperty[]) pi.GetCustomAttributes(typeof(AttributeObjectProperty), true)); + //Get our property attribute + AttributeObjectProperty[] attributes = ((AttributeObjectProperty[]) pi.GetCustomAttributes(typeof(AttributeObjectProperty), true)); if (attributes.Length != 1) { + //No attribute, skip this property then. continue; } - AttributeObjectProperty attribute = attributes[0]; + //Check that we support this type of property if (!IsPropertyTypeSupported(pi)) { continue; } + //Now fetch our property value GetPropertyValueFromControl(iTableLayoutPanel.Controls[ctrlIndex+1], pi, aObject); //+1 otherwise we get the label ctrlIndex+=2; //Jump over the label too @@ -163,6 +166,14 @@ PropertyFile value = new PropertyFile {FullPath=ctrl.Text}; aInfo.SetValue(aObject, value); } + else if (aInfo.PropertyType == typeof(PropertyComboBox)) + { + ComboBox ctrl = (ComboBox)aControl; + string currentItem = ctrl.SelectedItem.ToString(); + PropertyComboBox pcb = (PropertyComboBox)aInfo.GetValue(aObject); + pcb.CurrentItem = currentItem; + } + //TODO: add support for other types here } @@ -191,21 +202,21 @@ { //Enum properties are using combo box ComboBox ctrl = new ComboBox(); - ctrl.AutoSize = true; - ctrl.Sorted = true; + ctrl.AutoSize = true; + ctrl.Sorted = true; ctrl.DropDownStyle = ComboBoxStyle.DropDownList; //Data source is fine but it gives us duplicate entries for duplicated enum values //ctrl.DataSource = Enum.GetValues(aInfo.PropertyType); //Therefore we need to explicitly create our items - Size cbSize = new Size(0,0); + Size cbSize = new Size(0, 0); foreach (string name in aInfo.PropertyType.GetEnumNames()) { ctrl.Items.Add(name.ToString()); Graphics g = this.CreateGraphics(); //Since combobox autosize would not work we need to get measure text ourselves - SizeF size=g.MeasureString(name.ToString(), ctrl.Font); - cbSize.Width = Math.Max(cbSize.Width,(int)size.Width); + SizeF size = g.MeasureString(name.ToString(), ctrl.Font); + cbSize.Width = Math.Max(cbSize.Width, (int)size.Width); cbSize.Height = Math.Max(cbSize.Height, (int)size.Height); } @@ -225,7 +236,7 @@ CheckBox ctrl = new CheckBox(); ctrl.AutoSize = true; ctrl.Text = aAttribute.Description; - ctrl.Checked = (bool)aInfo.GetValue(aObject); + ctrl.Checked = (bool)aInfo.GetValue(aObject); return ctrl; } else if (aInfo.PropertyType == typeof(string)) @@ -263,8 +274,27 @@ return ctrl; } + else if (aInfo.PropertyType == typeof(PropertyComboBox)) + { + //ComboBox property + ComboBox ctrl = new ComboBox(); + ctrl.AutoSize = true; + ctrl.Sorted = true; + ctrl.DropDownStyle = ComboBoxStyle.DropDownList; + //Data source is such a pain to set the current item + //ctrl.DataSource = ((PropertyComboBox)aInfo.GetValue(aObject)).Items; + + PropertyComboBox pcb = ((PropertyComboBox)aInfo.GetValue(aObject)); + foreach (string item in pcb.Items) + { + ctrl.Items.Add(item); + } + + ctrl.SelectedItem = ((PropertyComboBox)aInfo.GetValue(aObject)).CurrentItem; + // + return ctrl; + } //TODO: add support for other control type here - return null; } @@ -294,6 +324,11 @@ { return true; } + else if (aInfo.PropertyType == typeof(PropertyComboBox)) + { + return true; + } + //TODO: add support for other type here return false; diff -r 0a956121c273 -r cc2251d065db Server/FormMain.Designer.cs --- a/Server/FormMain.Designer.cs Thu Aug 18 20:14:30 2016 +0200 +++ b/Server/FormMain.Designer.cs Fri Aug 19 17:12:54 2016 +0200 @@ -247,7 +247,7 @@ this.toolStripStatusLabelSpring, this.toolStripStatusLabelPower, this.toolStripStatusLabelFps}); - this.statusStrip.Location = new System.Drawing.Point(0, 539); + this.statusStrip.Location = new System.Drawing.Point(0, 540); this.statusStrip.Name = "statusStrip"; this.statusStrip.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional; this.statusStrip.Size = new System.Drawing.Size(784, 22); @@ -1124,7 +1124,7 @@ // buttonEventEdit // this.buttonEventEdit.Enabled = false; - this.buttonEventEdit.Location = new System.Drawing.Point(6, 35); + this.buttonEventEdit.Location = new System.Drawing.Point(6, 187); this.buttonEventEdit.Name = "buttonEventEdit"; this.buttonEventEdit.Size = new System.Drawing.Size(96, 23); this.buttonEventEdit.TabIndex = 29; @@ -1135,7 +1135,7 @@ // buttonEventDelete // this.buttonEventDelete.Enabled = false; - this.buttonEventDelete.Location = new System.Drawing.Point(6, 64); + this.buttonEventDelete.Location = new System.Drawing.Point(6, 216); this.buttonEventDelete.Name = "buttonEventDelete"; this.buttonEventDelete.Size = new System.Drawing.Size(96, 23); this.buttonEventDelete.TabIndex = 28; @@ -1145,7 +1145,7 @@ // // buttonEventAdd // - this.buttonEventAdd.Location = new System.Drawing.Point(6, 6); + this.buttonEventAdd.Location = new System.Drawing.Point(6, 158); this.buttonEventAdd.Name = "buttonEventAdd"; this.buttonEventAdd.Size = new System.Drawing.Size(96, 23); this.buttonEventAdd.TabIndex = 27; @@ -1156,7 +1156,7 @@ // buttonEventTest // this.buttonEventTest.Enabled = false; - this.buttonEventTest.Location = new System.Drawing.Point(6, 93); + this.buttonEventTest.Location = new System.Drawing.Point(6, 245); this.buttonEventTest.Name = "buttonEventTest"; this.buttonEventTest.Size = new System.Drawing.Size(96, 23); this.buttonEventTest.TabIndex = 26; @@ -1167,7 +1167,7 @@ // buttonActionEdit // this.buttonActionEdit.Enabled = false; - this.buttonActionEdit.Location = new System.Drawing.Point(6, 190); + this.buttonActionEdit.Location = new System.Drawing.Point(6, 35); this.buttonActionEdit.Name = "buttonActionEdit"; this.buttonActionEdit.Size = new System.Drawing.Size(96, 23); this.buttonActionEdit.TabIndex = 25; @@ -1200,7 +1200,7 @@ // buttonActionTest // this.buttonActionTest.Enabled = false; - this.buttonActionTest.Location = new System.Drawing.Point(6, 248); + this.buttonActionTest.Location = new System.Drawing.Point(6, 93); this.buttonActionTest.Name = "buttonActionTest"; this.buttonActionTest.Size = new System.Drawing.Size(96, 23); this.buttonActionTest.TabIndex = 22; @@ -1211,7 +1211,7 @@ // buttonActionDelete // this.buttonActionDelete.Enabled = false; - this.buttonActionDelete.Location = new System.Drawing.Point(6, 219); + this.buttonActionDelete.Location = new System.Drawing.Point(6, 64); this.buttonActionDelete.Name = "buttonActionDelete"; this.buttonActionDelete.Size = new System.Drawing.Size(96, 23); this.buttonActionDelete.TabIndex = 21; @@ -1222,9 +1222,9 @@ // buttonActionAdd // this.buttonActionAdd.Enabled = false; - this.buttonActionAdd.Location = new System.Drawing.Point(6, 157); + this.buttonActionAdd.Location = new System.Drawing.Point(6, 6); this.buttonActionAdd.Name = "buttonActionAdd"; - this.buttonActionAdd.Size = new System.Drawing.Size(96, 27); + this.buttonActionAdd.Size = new System.Drawing.Size(96, 23); this.buttonActionAdd.TabIndex = 20; this.buttonActionAdd.Text = "Add Action"; this.buttonActionAdd.UseVisualStyleBackColor = true; @@ -1370,7 +1370,7 @@ // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(784, 561); + this.ClientSize = new System.Drawing.Size(784, 562); this.Controls.Add(this.labelFontHeight); this.Controls.Add(this.labelFontWidth); this.Controls.Add(this.labelWarning); diff -r 0a956121c273 -r cc2251d065db Server/FormMain.cs --- a/Server/FormMain.cs Thu Aug 18 20:14:30 2016 +0200 +++ b/Server/FormMain.cs Fri Aug 19 17:12:54 2016 +0200 @@ -151,7 +151,7 @@ // We loaded events and actions from our settings // Internalizer apparently skips constructor so we need to initialize it here // Though I reckon that should only be needed when loading an empty EAR manager I guess. - Properties.Settings.Default.EarManager.Init(); + Properties.Settings.Default.EarManager.Construct(); } iSkipFrameRendering = false; iClosing = false; diff -r 0a956121c273 -r cc2251d065db SharpLibEar/ActionOpticalDriveEject.cs --- a/SharpLibEar/ActionOpticalDriveEject.cs Thu Aug 18 20:14:30 2016 +0200 +++ b/SharpLibEar/ActionOpticalDriveEject.cs Fri Aug 19 17:12:54 2016 +0200 @@ -1,12 +1,308 @@ -using System; +using Microsoft.Win32.SafeHandles; +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Linq; +using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; +using SharpLib.Win32; +using System.ComponentModel; +using System.Runtime.InteropServices; namespace SharpLib.Ear { - class ActionOpticalDriveEject + [DataContract] + [AttributeObject(Id = "Action.OpticalDrive.Eject", Name = "Eject", Description = "Eject media from an optical drive.")] + public class ActionOpticalDriveEject : Action { + [DataMember] + [AttributeObjectProperty + ( + Id = "Action.OpticalDrive.Eject.Drive", + Name = "Drive to eject", + Description = "Select the drive you want to eject." + ) + ] + public PropertyComboBox Drive { get; set; } = new PropertyComboBox(); + + + protected override void DoConstruct() + { + base.DoConstruct(); + PopulateOpticalDrives(); + CheckCurrentItem(); + } + + + public override string Brief() + { + return Name + " " + Drive.CurrentItem ; + } + + public override bool IsValid() + { + //This object is valid if our current item is contained in our drive list + return Drive.Items.Contains(Drive.CurrentItem); + } + + protected override void DoExecute() + { + DriveEject(Drive.CurrentItem); + } + + + private void CheckCurrentItem() + { + if (!Drive.Items.Contains(Drive.CurrentItem) && Drive.Items.Count>0) + { + //Current item unknown, reset it then + Drive.CurrentItem = Drive.Items[0]; + } + } + + /// + /// + /// + private void PopulateOpticalDrives() + { + //Reset our list of drives + Drive.Items = new List(); + //Go through each drives on our system and collected the optical ones in our list + DriveInfo[] allDrives = DriveInfo.GetDrives(); + foreach (DriveInfo d in allDrives) + { + Debug.WriteLine("Drive " + d.Name); + Debug.WriteLine(" Drive type: {0}", d.DriveType); + + if (d.DriveType == DriveType.CDRom) + { + //This is an optical drive, add it now + Drive.Items.Add(d.Name.Substring(0, 2)); + } + } + } + + + /// + /// + /// + /// + static private void CheckLastError(string aPrefix) + { + string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage); + } + + /// + /// + /// + /// + /// + static private IntPtr MarshalToPointer(object data) + { + IntPtr buf = Marshal.AllocHGlobal( + Marshal.SizeOf(data)); + Marshal.StructureToPtr(data, + buf, false); + return buf; + } + + /// + /// + /// + /// + static private SafeFileHandle OpenVolume(string aDriveName) + { + return Function.CreateFile("\\\\.\\" + aDriveName, + SharpLib.Win32.FileAccess.GENERIC_READ, + SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE, + IntPtr.Zero, + CreationDisposition.OPEN_EXISTING, + 0, + IntPtr.Zero); + } + + /// + /// + /// + /// + /// + static private bool LockVolume(SafeFileHandle aVolume) + { + //Hope that's doing what I think it does + IntPtr dwBytesReturned = new IntPtr(); + //Should not be needed but I'm not sure how to pass NULL in there. + OVERLAPPED overlapped = new OVERLAPPED(); + + int tries = 0; + const int KMaxTries = 100; + const int KSleepTime = 10; + bool success = false; + + while (!success && tries < KMaxTries) + { + success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); + System.Threading.Thread.Sleep(KSleepTime); + tries++; + } + + CheckLastError("Lock volume: "); + + return success; + } + + /// + /// + /// + /// + /// + static private bool DismountVolume(SafeFileHandle aVolume) + { + //Hope that's doing what I think it does + IntPtr dwBytesReturned = new IntPtr(); + //Should not be needed but I'm not sure how to pass NULL in there. + OVERLAPPED overlapped = new OVERLAPPED(); + + bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); + CheckLastError("Dismount volume: "); + return res; + } + + + + /// + /// + /// + /// + /// + /// + static private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval) + { + //Hope that's doing what I think it does + IntPtr dwBytesReturned = new IntPtr(); + //Should not be needed but I'm not sure how to pass NULL in there. + OVERLAPPED overlapped = new OVERLAPPED(); + // + PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL(); + preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval); + IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval); + + bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped); + CheckLastError("Media removal: "); + Marshal.FreeHGlobal(preventMediaRemovalParam); + + return result; + } + + /// + /// Eject optical drive media opening the tray if any. + /// + /// + /// + static private bool MediaEject(SafeFileHandle aVolume) + { + //Hope that's doing what I think it does + IntPtr dwBytesReturned = new IntPtr(); + //Should not be needed but I'm not sure how to pass NULL in there. + OVERLAPPED overlapped = new OVERLAPPED(); + + bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); + CheckLastError("Media eject: "); + return res; + } + + /// + /// Close an optical drive tray. + /// + /// + /// + static private bool MediaLoad(SafeFileHandle aVolume) + { + //Hope that's doing what I think it does + IntPtr dwBytesReturned = new IntPtr(); + //Should not be needed but I'm not sure how to pass NULL in there. + OVERLAPPED overlapped = new OVERLAPPED(); + + bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); + CheckLastError("Media load: "); + return res; + } + + /// + /// + /// + /// + /// + static private bool StorageCheckVerify(SafeFileHandle aVolume) + { + //Hope that's doing what I think it does + IntPtr dwBytesReturned = new IntPtr(); + //Should not be needed but I'm not sure how to pass NULL in there. + OVERLAPPED overlapped = new OVERLAPPED(); + + bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); + + CheckLastError("Check verify: "); + + return res; + } + + + /// + /// Perform media ejection. + /// + static private void DriveEject(string aDrive) + { + string drive = aDrive; + if (drive.Length != 2) + { + //Not a proper drive spec. + //Probably 'None' selected. + return; + } + + SafeFileHandle handle = OpenVolume(drive); + if (handle.IsInvalid) + { + CheckLastError("ERROR: Failed to open volume: "); + return; + } + + if (LockVolume(handle) && DismountVolume(handle)) + { + Debug.WriteLine("Volume was dismounted."); + + if (PreventRemovalOfVolume(handle, false)) + { + //StorageCheckVerify(handle); + + DateTime before; + before = DateTime.Now; + bool ejectSuccess = MediaEject(handle); + double ms = (DateTime.Now - before).TotalMilliseconds; + + //We assume that if it take more than a certain time to for eject to execute it means we actually ejected. + //If our eject completes too rapidly we assume the tray is already open and we will try to close it. + if (ejectSuccess && ms > 100) + { + Debug.WriteLine("Media was ejected"); + } + else if (MediaLoad(handle)) + { + Debug.WriteLine("Media was loaded"); + } + } + } + else + { + Debug.WriteLine("Volume lock or dismount failed."); + } + + //This is needed to make sure we can open the volume next time around + handle.Dispose(); + } + } } diff -r 0a956121c273 -r cc2251d065db SharpLibEar/Event.cs --- a/SharpLibEar/Event.cs Thu Aug 18 20:14:30 2016 +0200 +++ b/SharpLibEar/Event.cs Fri Aug 19 17:12:54 2016 +0200 @@ -18,16 +18,22 @@ Description = "When enabled an event instance can be triggered." ) ] - public bool Enabled { get; set; } + public bool Enabled { get; set; } = true; [DataMember] public List Actions = new List(); + protected override void DoConstruct() + { + base.DoConstruct(); - protected Event() - { - Enabled = true; + // TODO: Construct properties too + foreach (Action a in Actions) + { + a.Construct(); + } + } diff -r 0a956121c273 -r cc2251d065db SharpLibEar/Manager.cs --- a/SharpLibEar/Manager.cs Thu Aug 18 20:14:30 2016 +0200 +++ b/SharpLibEar/Manager.cs Fri Aug 19 17:12:54 2016 +0200 @@ -15,8 +15,7 @@ /// Users can implement their own events and actions. /// [DataContract] - [KnownType("DerivedTypes")] - public class Manager + public class Manager: Object { /// /// Our events instances. @@ -24,23 +23,24 @@ [DataMember] public List Events; - /// - /// Constructor - /// - public Manager() - { - Init(); - } /// /// Executes after internalization took place. /// - public void Init() + protected override void DoConstruct() { + base.DoConstruct(); + if (Events == null) { Events = new List(); } + + // TODO: Object properties should be constructed too + foreach (Event e in Events) + { + e.Construct(); + } } @@ -86,15 +86,5 @@ } } } - - /// - /// Allow extending our data contract. - /// See KnownType above. - /// - /// - private static IEnumerable DerivedTypes() - { - return SharpLib.Utils.Reflection.GetDerivedTypes(); - } } } \ No newline at end of file diff -r 0a956121c273 -r cc2251d065db SharpLibEar/Object.cs --- a/SharpLibEar/Object.cs Thu Aug 18 20:14:30 2016 +0200 +++ b/SharpLibEar/Object.cs Fri Aug 19 17:12:54 2016 +0200 @@ -21,6 +21,33 @@ [KnownType("DerivedTypes")] public abstract class Object: IComparable { + private bool iConstructed = false; + + public Object() + { + Construct(); + } + + /// + /// Needed as our constructor is not called following internalization. + /// + public void Construct() + { + if (!iConstructed) + { + DoConstruct(); + iConstructed = true; + } + } + + /// + /// + /// + protected virtual void DoConstruct() + { + + } + /// /// Static object name. /// diff -r 0a956121c273 -r cc2251d065db SharpLibEar/PropertyComboBox.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SharpLibEar/PropertyComboBox.cs Fri Aug 19 17:12:54 2016 +0200 @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace SharpLib.Ear +{ + /// + /// ComboBox property + /// + [DataContract] + [AttributeObject(Id = "Property.ComboBox", Name = "ComboBox", Description = "ComboBox property.")] + public class PropertyComboBox : Object + { + public IList Items { get; set; } = new List(); + + [DataMember] + public string CurrentItem { get; set; } + } +} diff -r 0a956121c273 -r cc2251d065db SharpLibEar/SharpLibEar.csproj --- a/SharpLibEar/SharpLibEar.csproj Thu Aug 18 20:14:30 2016 +0200 +++ b/SharpLibEar/SharpLibEar.csproj Fri Aug 19 17:12:54 2016 +0200 @@ -48,6 +48,10 @@ MinimumRecommendedRules.ruleset + + ..\packages\SharpLibWin32.0.0.9\lib\net20\SharpLibWin32.dll + True + @@ -73,6 +77,7 @@ + @@ -81,6 +86,9 @@ SharpLibUtils + + +