Click here to Skip to main content
15,893,266 members
Articles / Programming Languages / C#

SCSI Library in C# - Burn CDs and DVDs, Access Hard Disks, etc.

Rate me:
Please Sign up or sign in to vote.
4.77/5 (48 votes)
19 Jun 2017Ms-PL6 min read 146K   8.1K   146  
Ever wonder how programs like Nero work? They make their own SCSI libraries... like this!
#define DRIVE_ICON
//#define DRIVE_ICON_DYNAMIC
#define EFFICIENT_PROGRESS_REPORTING
#define OPEN_WITH_NO_ACCESS
//#define NEW_ADD_METHOD
#if !DEBUG
#define CATCH
#endif

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Reflection;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Shell;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using FileSystems.Udf;
using Helper.Forms;
using Helper.IO;
using Scsi;
using Scsi.Multimedia;
using System.Security;

namespace BurnApp
{
	public partial class FormMain : Form
	{
		private static readonly TextFormatFlags COMBO_BOX_TEXT_FLAGS = TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine;
		private static readonly Padding ICON_PADDING = new Padding(4, 2, 0, 2);
		private static readonly ThreadStart BeginModalMessageLoop = (ThreadStart)Delegate.CreateDelegate(typeof(ThreadStart), typeof(Application).GetMethod("BeginModalMessageLoop", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
		private static readonly ThreadStart EndModalMessageLoop = (ThreadStart)Delegate.CreateDelegate(typeof(ThreadStart), typeof(Application).GetMethod("EndModalMessageLoop", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
		private static readonly char[] PATH_SEPS = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
		private static readonly string[] PATH_SEP_STRS = new string[] { Path.DirectorySeparatorChar.ToString(), Path.AltDirectorySeparatorChar.ToString() };
		private const string FOLDER_IMAGE_KEY = "FOLDER";
		private const string CD_DRIVE_IMAGE_KEY = "CD_DRIVE";
		private static readonly bool DIRECT_IO = false;
		private static readonly int ICON_CACHE_SIZE = 2048;
		private static readonly SafeLibraryHandle shell32 = SafeLibraryHandle.LoadLibraryEx("Shell32.dll");
		private UdfMaster master = new UdfMaster(Program.BLOCK_SIZE) { ApplicationId = new EntityIdentifier(EntityIdentifierFlags.None, Program.CurrentAssemblyTitle, new ApplicationIdentifierSuffix()), AlignFileIdentifiers = true };

		#region Nested Types
		private struct COMBOBOXINFO
		{
			public int cbSize;
			public Rect rcItem;
			public Rect rcButton;
			public int stateButton;
			public IntPtr hwndCombo;
			public IntPtr hwndItem;
			public IntPtr hwndList;
		}

		private class NodeTag { public NodeTag() { } }

		private class DirectoryTreeNode : TreeNode, IDirectoryInfo
		{
			private DirectorySecurity security;
			private DirectoryTreeNodeFileInfoCollection _Files;

			public class DirectoryTreeNodeFileInfoCollection : System.Collections.ObjectModel.KeyedCollection<string, IFileInfo>
			{
				private DirectoryTreeNode dir;
				internal DirectoryTreeNodeFileInfoCollection(DirectoryTreeNode dir) : base(StringComparer.Ordinal) { this.dir = dir; }
				protected override string GetKeyForItem(IFileInfo item) { return item.Name; }
				protected override void InsertItem(int index, IFileInfo item)
				{
					if (this.dir.Contains(this.GetKeyForItem(item))) { throw new ArgumentException("A file with the same name already exists.", "item"); }
					var asVirt = item as VirtualFileInfo;
					base.InsertItem(index, item);
					if (asVirt != null) { asVirt.Parent = this.dir; }
				}
				protected override void SetItem(int index, IFileInfo item)
				{
					if (dir.Nodes.ContainsKey(this.GetKeyForItem(item)) || base.Contains(this.GetKeyForItem(item))) { throw new ArgumentException("A file with the same name already exists.", "item"); }
					var prev = base[index];
					var prevAsVirt = prev as VirtualFileInfo;
					if (prevAsVirt != null) { prevAsVirt.Parent = null; }
					var asVirt = item as VirtualFileInfo;
					base.SetItem(index, item);
					if (asVirt != null) { asVirt.Parent = this.dir; }
				}
			}

			public bool Contains(string fileOrDirName) { return this.Nodes.ContainsKey(fileOrDirName) || this.Files.Contains(fileOrDirName); }

			public DirectoryTreeNode() : base() { this._Files = new DirectoryTreeNodeFileInfoCollection(this); }
			public DirectoryTreeNode(string text) : this() { this.Text = text; }

			void IDirectoryInfo.Create(DirectorySecurity directorySecurity) { this.security = directorySecurity; }
			IDirectoryInfo IDirectoryInfo.CreateSubdirectory(string path, DirectorySecurity directorySecurity) { var node = new DirectoryTreeNode() { security = directorySecurity, Name = path, Text = path, Tag = new NodeTag() }; this.Nodes.Add(node); return node; }
			void IDirectoryInfo.Delete(bool recursive)
			{
				if (recursive) { throw new NotImplementedException(); }
				else
				{
					this.Remove();
					IDirectoryInfo idir = this;
					idir.Refresh();
				}
			}
			public DirectorySecurity GetAccessControl(AccessControlSections includeSections) { return this.security; }

			public IDirectoryInfo[] GetDirectories(string searchPattern, SearchOption searchOption)
			{
				if (searchPattern != null && searchPattern != "*" || searchOption != SearchOption.TopDirectoryOnly)
				{ throw new NotSupportedException(); }
				var result = new DirectoryTreeNode[this.Nodes.Count];
				this.Nodes.CopyTo(result, 0);
				return result;
			}

			public IFileInfo[] GetFiles(string searchPattern, SearchOption searchOption)
			{
				if (searchPattern != null && searchPattern != "*" || searchOption != SearchOption.TopDirectoryOnly)
				{ throw new NotSupportedException(); }
				var files = new IFileInfo[this.Files.Count];
				this.Files.CopyTo(files, 0);
				return files;
			}

			public IFileSystemInfo[] GetFileSystemInfos(string searchPattern, SearchOption searchOption)
			{
				var dirs = this.GetDirectories(searchPattern, searchOption);
				var files = this.GetFiles(searchPattern, searchOption);
				var result = new IFileSystemInfo[dirs.Length + files.Length];
				Array.Copy(files, 0, result, 0, files.Length);
				Array.Copy(dirs, 0, result, files.Length, dirs.Length);
				return result;
			}

			public DirectoryTreeNodeFileInfoCollection Files { get { return this._Files; } set { this._Files = value; } }

			IDirectoryInfo IDirectoryInfo.Parent { get { return (DirectoryTreeNode)this.Parent; } } //TODO: DirectoryTreeNode.Parent is a bottleneck!
			IDirectoryInfo IDirectoryInfo.Root { get { TreeNode p = this; while (p.Parent != null) { p = p.Parent; } return (IDirectoryInfo)p; } }
			void IDirectoryInfo.SetAccessControl(DirectorySecurity directorySecurity) { this.security = directorySecurity; }

			[DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
			private FileAttributes _Attributes = FileAttributes.Directory;
			FileAttributes IFileSystemInfo.Attributes { get { return this._Attributes; } set { this._Attributes = value; } }
			[DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
			private DateTime _CreationTime = DateTime.Now;
			DateTime IFileSystemInfo.CreationTime { get { return this._CreationTime; } set { this._CreationTime = value; } }
			void IFileSystemInfo.Delete() { IDirectoryInfo me = this; me.Delete(false); }
			bool IFileSystemInfo.Exists { get { return true; } }
			string IFileSystemInfo.Extension { get { return Path.GetExtension(this.Name); } }
			private string _FullName;
			string IFileSystemInfo.FullName { get { if (this._FullName == null) { this._FullName = this.WithEndingSeparator(this.FullPath); } return this._FullName; } }
			long IFileSystemInfo.IndexNumber { get { return -1; } }
			[DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
			private DateTime _LastAccessTime = DateTime.Now;
			DateTime IFileSystemInfo.LastAccessTime { get { return this._LastAccessTime; } set { this._LastAccessTime = value; } }
			[DebuggerBrowsableAttribute(DebuggerBrowsableState.Never)]
			private DateTime _LastWriteTime = DateTime.Now;
			DateTime IFileSystemInfo.LastWriteTime { get { return this._LastWriteTime; } set { this._LastWriteTime = value; } }
			void IFileSystemInfo.Refresh()
			{
				this._FullName = null;

				foreach (DirectoryTreeNode item in this.Nodes)
				{
					IDirectoryInfo idir = item;
					idir.Refresh(); //Refresh all children as well
				}
			}

			public void RenameFile(IFileInfo fileInfo, string newName)
			{
				string prevName = fileInfo.Name;
				if (!this.Files.Remove(fileInfo)) { throw new InvalidOperationException(); }
				try
				{
					fileInfo.Rename(newName);
					this.Files.Add(fileInfo);
				}
				catch
				{
					fileInfo.Rename(prevName);
					this.Files.Add(fileInfo);
					fileInfo.Refresh();
					throw;
				}
			}

			private string WithEndingSeparator(string s) { var ps = this.TreeView.PathSeparator; if (!s.EndsWith(ps)) { s += ps; } return s; }

			public override object Clone()
			{
				var me = (DirectoryTreeNode)base.Clone();
				me._Files = new DirectoryTreeNodeFileInfoCollection(me);
				for (int i = 0; i < this._Files.Count; i++)
				{ me._Files.Add(this._Files[i]); }
				return me;
			}

			public void Rename(string newName) { this.Text = this.Name = newName; }

			string IFileSystemInfo.Name { get { return this.Name; } }
		}

		private struct ProgressDialogInfo
		{
			private ProgressDialog dialog;
			private static readonly int TOTAL = int.MaxValue;
			private int start;
			private int length;

			public ProgressDialogInfo(ProgressDialog dialog) : this(dialog, 0, TOTAL) { }
			private ProgressDialogInfo(ProgressDialog dialog, int startOffset, int length) { this.dialog = dialog; this.start = startOffset; this.length = length; }

			public bool Canceled { get { return this.dialog.Canceled; } }
			public bool HasDialog { get { return this.dialog != null; } }
			public void SetLine(int line, string text, bool compact) { this.dialog.SetLine(line, text, compact); }
			public void SetProgress(double completed)
			{
				var c = (int)(this.start + completed * this.length);
				Debug.Assert(c >= this.start && c <= this.start + this.length);
				this.dialog.SetProgress(c, TOTAL);
			}

			public ProgressDialogInfo ExtractSegment(int startDivision, int length, int totalDivisions)
			{
				if (startDivision + (long)length > totalDivisions) { throw new ArgumentException("Invalid progress segment."); }
				var s = (int)(this.start + this.length * (long)startDivision / totalDivisions);
				var l = (int)(this.length * (long)length / totalDivisions);
				Debug.Assert(s >= this.start && s + (long)l <= this.start + (long)this.length);
				return new ProgressDialogInfo(this.dialog, s, l);
			}
		}

		private class MyTreeView : TreeView
		{
			private static readonly MethodInfo NodeFromHandle_Method = typeof(TreeView).GetMethod("NodeFromHandle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			
			public TreeNode FocusedNode
			{
				get
				{
					IntPtr h = WinHelper.SendMessage(this, 0x110a, (IntPtr)8, (IntPtr)0, false);
					var node = (TreeNode)NodeFromHandle_Method.Invoke(this, new object[] { h });
					if (node == null) { node = this.SelectedNode; }
					return node;
				}
			}
		}

		private class MySplitContainer : SplitContainer
		{
			public MySplitContainer() { this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false); }
		}

		private class DriveItem
		{
			public DriveItem(string drive, string desc)
			{
				this.DevicePath = drive;
				this.Description = desc;
			}

			public string DevicePath;
			public string Description;
#if DRIVE_ICON
			public object Icon;
#endif
			public override string ToString() { return this.Description; }
		}

		private class ItemTag { public ItemTag(IFileInfo file, DirectoryTreeNode dir) { this.FileInfo = file; this.Directory = dir; } public IFileInfo FileInfo; public DirectoryTreeNode Directory; }
		#endregion

		public FormMain()
		{
			this.InitializeComponent();
			Program.SetProperty(this.lvFiles, "DoubleBuffered", true, true);
			this.capacityBar.Markers = new CapacityBar.Marker[]
			{
				new CapacityBar.Marker(700L << 20, "CD", 1F, new Pen(Color.DarkGreen, 1) { DashStyle = DashStyle.Dash }),
				new CapacityBar.Marker(4700000000, "DVD", 1F, new Pen(Color.Red, 1) { DashStyle = DashStyle.Dash }),
				new CapacityBar.Marker(8500000000, "DVD DL", 1F, new Pen(Color.Crimson, 1) { DashStyle = DashStyle.Dash }),
				new CapacityBar.Marker(20000000000, "HD DVD", 1F, new Pen(Color.RoyalBlue, 1) { DashStyle = DashStyle.Dash }),
				new CapacityBar.Marker(25000000000, "BD", 1F, new Pen(Color.DarkBlue, 1) { DashStyle = DashStyle.Dash }),
				new CapacityBar.Marker(40000000000, "HD DVD DL", 1F, new Pen(Color.MediumVioletRed, 1) { DashStyle = DashStyle.Dash }),
				new CapacityBar.Marker(50000000000, "BD DL", 1F, new Pen(Color.Violet, 1) { DashStyle = DashStyle.Dash }),
			};
			this.tsbOpenImage.Image = Properties.Resources.DiscImageFile;
			this.tsbBurn.Image = Properties.Resources.Burn;
			this.tsMain.Renderer = new ToolStripNoBorderRenderer(this.tsMain.Renderer);
			this.tvDirectories.PathSeparator = Path.DirectorySeparatorChar.ToString();
			var root = new DirectoryTreeNode("Disc") { ImageKey = CD_DRIVE_IMAGE_KEY, SelectedImageKey = CD_DRIVE_IMAGE_KEY, Tag = new NodeTag() };
			root.Name = root.Text;
			this.master.Root = new VirtualDirectoryInfo(root);
			this.tvDirectories.Nodes.Add(root);
			this.scMain.Panel2Collapsed = !this.tsbShowExplorer.Checked;
			try
			{
#if DRIVE_ICON
				object cdIcon;
				cdIcon = Program.LoadImageIcon(shell32.DangerousGetHandle(), 12, this.imgListNodes.ImageSize.Width, this.imgListNodes.ImageSize.Height, LoadOptions.DefaultColor);
				if (cdIcon != null)
				{
					if (cdIcon is Icon) { this.imgListNodes.Images.Add(CD_DRIVE_IMAGE_KEY, (Icon)cdIcon); }
					else { this.imgListNodes.Images.Add(CD_DRIVE_IMAGE_KEY, (Image)cdIcon); }
				}
#endif
				this.imgListNodes.Images.Add(FOLDER_IMAGE_KEY, Program.LoadImageIcon(shell32.DangerousGetHandle(), 4, this.imgListNodes.ImageSize.Width, this.imgListNodes.ImageSize.Height, LoadOptions.DefaultColor));
			}
			catch { }

#if DRIVE_ICON
			this.tscmbDevice.ComboBox.DrawMode = DrawMode.OwnerDrawFixed;
			this.tscmbDevice.ComboBox.Dock = DockStyle.Fill;
			this.tscmbDevice.ComboBox.DrawItem += new DrawItemEventHandler(tscmbDevice_ComboBox_DrawItem);
			this.tscmbDevice.ComboBox.ItemHeight = Math.Max(this.tscmbDevice.ComboBox.PreferredHeight, this.imgListFiles.ImageSize.Height + ICON_PADDING.Vertical);
#endif

			try
			{
				using (var explore = Program.LoadImageIcon(shell32.DangerousGetHandle(), 46, this.tsMain.ImageScalingSize.Width, this.tsMain.ImageScalingSize.Height, LoadOptions.DefaultColor))
				{ this.tsbShowExplorer.Image = explore.ToBitmap(); }
			}
			catch { }
		}

		private void tscmbDevice_ComboBox_DrawItem(object sender, DrawItemEventArgs e)
		{
			var cmb = (ComboBox)sender;
			e.DrawBackground();
			e.DrawFocusRectangle();
			var current = e.Index >= 0 ? (DriveItem)cmb.Items[e.Index] : (DriveItem)cmb.SelectedItem;
			Size iconSize = this.imgListNodes.ImageSize;
			Rectangle imageBounds;
			if (current != null && current.Icon != null)
			{ imageBounds = new Rectangle(e.Bounds.X + ICON_PADDING.Left, e.Bounds.Y + (e.Bounds.Height - iconSize.Height) / 2, iconSize.Width + ICON_PADDING.Horizontal, iconSize.Height); }
			else { imageBounds = new Rectangle(e.Bounds.Location, ICON_PADDING.Size); }
			var textBounds = new Rectangle(imageBounds.Right, e.Bounds.Top, e.Bounds.Width - imageBounds.Width, e.Bounds.Height);
			TextRenderer.DrawText(e.Graphics, e.Index < 0 ? cmb.Text : current.ToString(), e.Font, textBounds, e.ForeColor, e.BackColor, COMBO_BOX_TEXT_FLAGS);
			if (current != null && current.Icon != null)
			{
				if (current.Icon is Icon) { e.Graphics.DrawIconUnstretched((Icon)current.Icon, imageBounds); }
				else { e.Graphics.DrawImageUnscaled((Image)current.Icon, imageBounds); }
			}
		}

		private static EnumValue[] GetEnumValues<TEnum>(params TEnum[] values)
			where TEnum : struct
		{
			var result = new EnumValue[values.Length];
			for (int i = 0; i < values.Length; i++) { result[i] = new EnumValue((Enum)(object)values[i]); }
			return result;
		}

		private MultimediaDevice OpenDrive(string device, Win32FileAccess access) { return new MultimediaDevice(new Win32Spti(new Win32FileStream(device, access, FileShare.ReadWrite /*Must be specified in order to be dismountable*/, FileMode.Open, Win32FileOptions.None).SafeFileHandle, false, DIRECT_IO), false) { TimeoutSeconds = 60 }; }

		private ListViewItem ToItem(DirectoryTreeNode parent, ImageList imageList, IFileInfo fi)
		{
			var asVirt = fi as VirtualFileSystemInfo;
			string path = asVirt != null ? asVirt.LinkedTarget.FullName : fi.FullName;
			var item = new ListViewItem(new string[] { fi.Name, path, fi.Length.ToString("N0"), }, fi.FullName) { Name = fi.Name, Tag = new ItemTag(fi, parent), UseItemStyleForSubItems = false };

			if (imageList != null)
			{
				int iImage = imageList.Images.IndexOfKey(item.ImageKey);
				if (iImage >= 0) { item.ImageIndex = iImage; }
				else
				{
					Icon icon = Program.SHGetFileIcon(asVirt != null ? asVirt.LinkedTarget.FullName : fi.FullName, (int)fi.Attributes, out iImage, false);

					if (icon != null)
					{
						if (iImage < 0)
						{
							while (imageList.Images.Count > ICON_CACHE_SIZE)
							{ imageList.Images.RemoveAt(0); }
							imageList.Images.Add(item.ImageKey, icon);
							iImage = imageList.Images.Count - 1;
						}
						imageList.Images.Add(item.ImageKey, icon);
						item.ImageIndex = iImage;
					}
				}
			}
			return item;
		}

		private AddResult ToNode(IDirectoryInfo dir, bool archiveOnly, string imageKey, ProgressDialogInfo progress, ref int lastProgressReportTick, out DirectoryTreeNode node)
		{
			string name = dir.Name;
			if (string.IsNullOrEmpty(name))
			{
				var asVirtDir = dir as VirtualDirectoryInfo;
				if (asVirtDir != null)
				{
					var asLinkedIDir = asVirtDir.LinkedTarget as IDirectoryInfo;
					if (asLinkedIDir != null)
					{ name = Path.GetPathRoot(asLinkedIDir.FullName).Replace(PATH_SEP_STRS[0], string.Empty).Replace(PATH_SEP_STRS[1], string.Empty); }
				}
				if (string.IsNullOrEmpty(name))
				{ throw new InvalidOperationException("The given folder must have a name." + Environment.NewLine + "If you are adding a drive's root folder, please add the contents individually instead."); }
			}
			node = new DirectoryTreeNode()
			{
				Tag = new NodeTag(),
				ImageKey = imageKey,
				SelectedImageKey = imageKey,
				Name = name,
				Text = name,
			};
			IFileSystemInfo[] infos;
			for (; ; )
			{
				try
				{
					infos = dir.GetFileSystemInfos(null, SearchOption.TopDirectoryOnly);
					break;
				}
				catch (Exception ex)
				{
					if (!(ex is IOException | ex is SecurityException | ex is UnauthorizedAccessException)) { throw; }
					var dr = MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
					if (dr == DialogResult.Ignore) { return AddResult.Skipped; }
					else if (dr == DialogResult.Abort) { return AddResult.Failed; }
					else { infos = null; }
				}
			}
			{
#if EFFICIENT_PROGRESS_REPORTING
				int tick = Environment.TickCount;
				if (progress.HasDialog && tick - lastProgressReportTick >= Program.FAST_UPDATE_PAUSE_MILLIS)
#endif
				{
#if EFFICIENT_PROGRESS_REPORTING
					lastProgressReportTick = tick;
#endif
					if (progress.Canceled) { return AddResult.Failed; }
					progress.SetLine(1, string.Format("Processing '{0}'", dir.Name), false);
					progress.SetLine(2, dir.FullName, false);
					progress.SetProgress(1 / (double)(infos.Length + 1));
				}
			}
			IDirectoryInfo asIDir = node;
			asIDir.Attributes = dir.Attributes;
			asIDir.CreationTime = dir.CreationTime;
			//asIDir.IndexNumber = dir.IndexNumber;
			asIDir.LastAccessTime = dir.LastAccessTime;
			asIDir.LastWriteTime = dir.LastWriteTime;
			for (int i = 0; i < infos.Length; i++)
			{
				var child = infos[i];
				{
#if EFFICIENT_PROGRESS_REPORTING
					int tick = Environment.TickCount;
					if (progress.HasDialog && tick - lastProgressReportTick >= Program.FAST_UPDATE_PAUSE_MILLIS)
#endif
					{
#if EFFICIENT_PROGRESS_REPORTING
						lastProgressReportTick = tick;
#endif
						if (progress.Canceled) { return AddResult.Failed; }
						progress.SetLine(1, string.Format("Processing '{0}'", child.Name), false);
						progress.SetLine(2, child.FullName, false);
						progress.SetProgress((i + 1) / (double)(infos.Length + 1));
					}
				}

				var asSubDir = child as IDirectoryInfo;
				if (asSubDir != null)
				{
					DirectoryTreeNode subNode;
					var result = this.ToNode(asSubDir, archiveOnly, imageKey, progress.ExtractSegment((i + 1), 1, (infos.Length + 1)), ref lastProgressReportTick, out subNode);
					if (result == AddResult.Failed) { return result; }
					if (result == AddResult.Succeeded) { node.Nodes.Add(subNode); }
				}
				else
				{
					if (!archiveOnly || (child.Attributes & FileAttributes.Archive) != 0)
					{
						node.Files.Add(new VirtualFileInfo((IFileInfo)child)); //Making it virtual is CRITICAL!! Otherwise the user could delete the file itself!!
					}
				}
			}
			{
#if EFFICIENT_PROGRESS_REPORTING
				int tick = Environment.TickCount;
				if (progress.HasDialog && tick - lastProgressReportTick >= Program.FAST_UPDATE_PAUSE_MILLIS)
#endif
				{
#if EFFICIENT_PROGRESS_REPORTING
					lastProgressReportTick = tick;
#endif
					if (progress.Canceled) { return AddResult.Failed; }
					progress.SetLine(1, string.Format("Processing '{0}'", dir.Name), false);
					progress.SetLine(2, dir.FullName, false);
					progress.SetProgress((infos.Length + 1) / (double)(infos.Length + 1));
				}
			}
			return AddResult.Succeeded;
		}

		protected override void OnShown(EventArgs e)
		{
			base.OnShown(e);

			using (new CursorChange(this, Cursors.WaitCursor))
			{
				string oldText = this.sbPnlStage.Text;
				try
				{
					this.CalculateSpace();
					this.sbPnlStage.Text = "Finding and opening drives, please wait...";
					this.Update();

#if CATCH
					try
#endif
					{
						var maxSize = new Size(32, 16);
						this.tscmbDevice.Items.Clear();

						{
							var virtualDrive = new DriveItem(null, "Virtual Image Recorder")
							{
#if DRIVE_ICON
								Icon = Properties.Resources.DiscFile16,
#endif
							};
							this.tscmbDevice.Items.Add(virtualDrive);
							this.tscmbDevice.SelectedIndex = 0;

							var measure = TextRenderer.MeasureText(virtualDrive.ToString(), this.tscmbDevice.Font, this.tscmbDevice.ContentRectangle.Size, COMBO_BOX_TEXT_FLAGS);

							if (virtualDrive.Icon != null)
							{
								Size iconSize;
								if (virtualDrive.Icon is Icon) { iconSize = ((Icon)virtualDrive.Icon).Size; }
								else { iconSize = ((Image)virtualDrive.Icon).Size; }
								measure = new Size(measure.Width + iconSize.Width + ICON_PADDING.Horizontal, Math.Max(measure.Height, iconSize.Height + ICON_PADDING.Vertical));
							}
							maxSize = new Size(Math.Max(maxSize.Width, measure.Width), Math.Max(maxSize.Height, measure.Height));
						}

						var allDevices = Win32FileStream.QueryDosDevices(null);

						Trace.Assert(allDevices != null, "Device array was null!");
						for (int i = 0; i < allDevices.Length; i++)
						{
							var m = Regex.Match(allDevices[i], @"^(?<DEVICE>CdRom\d+)$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
							if (m.Success)
							{
								var deviceName = m.Groups["DEVICE"].Value;
								Trace.Assert(deviceName != null, "Device name captured was null!");
								var devicePath = string.Format(@"\\.\{0}", deviceName);
#if CATCH
								try
#endif
								{
									Exception ex = null;
									StandardInquiryData inquiry = null;
									string deviceType = null;
#if GET_ADDRESS
									byte lun = 0, pathId = 0, targetId = 0, portNumber = 0;
#endif
#if OPEN_WITH_NO_ACCESS
#if CATCH
									try
#endif
									{
										//We use the interface to query information instead of the device
										//This way we don't need any read or write access to the drive,
										//meaning there's no media access delay
										using (var cd = OpenDrive(devicePath, Win32FileAccess.ReadAttributes | Win32FileAccess.Synchronize /*no access*/))
										{
#if GET_ADDRESS
											lun = cd.Interface.LogicalUnitNumber;
											pathId = cd.Interface.PathId;
											targetId = cd.Interface.TargetId;
											portNumber = cd.Interface.PortNumber;
#endif
#if CATCH
											try
#endif
											{
												/*Try to be nice*/
												inquiry = cd.Interface.ScsiInquiry(false);
											}
#if CATCH
											catch { inquiry = null; }
#endif

											if (inquiry == null) //We failed with no access; now try method #2
											{
												//OK, I've seen this fail before, so now we have to open the drive
												//with READ access and try another method (use a different I/O Control Code)
												using (var cd2 = OpenDrive(devicePath, Win32FileAccess.GenericRead | Win32FileAccess.Synchronize))
												{
													try { inquiry = cd2.Interface.CdromInquiry(); }
													catch { inquiry = null; } //Fail silently...
												}
											}

											if (inquiry == null) //If we're still unlucky (which shouldn't really happen) use method #3
											{
												//I really don't expect failure here, but let's brute-force with pass-through
												//(needs read/write access and has the same delay as last method)
												using (var cd2 = OpenDrive(devicePath, Win32FileAccess.GenericRead | Win32FileAccess.GenericWrite | Win32FileAccess.Synchronize))
												{ inquiry = cd2.Inquiry(); }
											}

											//Now we're hoping everything's fine...
										}
									}
#if CATCH
									catch (Exception ex2) { ex = ex2; }
#endif
#else
									try
									{
										using (var cd = OpenDrive(devicePath, Win32FileAccess.GenericRead | Win32FileAccess.Synchronize))
										{
											lun = cd.Interface.LogicalUnitNumber;
											pathId = cd.Interface.PathId;
											targetId = cd.Interface.TargetId;
											portNumber = cd.Interface.PortNumber;
											inquiry = cd.Interface.CdromInquiry();
											deviceType = cd.Interface.GetCdromConfiguration<ProfileListFeature>().GetDriveType();
										}
									}
									catch (Exception ex2) { ex = ex2; }
#endif
									string win32Path;
#if CATCH
									try
#endif
									{ win32Path = string.Format(@"{0}:", Win32FileStream.GetDriveLetter(Win32FileStream.QueryFirstDosDevice(deviceName), true)); }
#if CATCH
									catch { win32Path = devicePath; }
#endif
									string description;
									if (inquiry != null)
									{
										string vendor = VendorIdentifiers.GetVendorName(inquiry.VendorIdentification);
										if (vendor == null) { vendor = inquiry.VendorIdentification; }
#if GET_ADDRESS
										//string address = string.Format("{0:N0}:{1:N0}:{2:N0}:{3:N0}", portNumber, pathId, targetId, lun);
#else
										string address = null;
#endif
										description = string.Format("{0} " +
#if GET_ADDRESS
											"[{1}]" +
#endif
 " {2} {3} {4}", win32Path, address, vendor, inquiry.ProductIdentification, inquiry.ProductRevisionLevel);
										if (deviceType != null) { win32Path = string.Format("{0} ({1})", win32Path, deviceType); }
									}
									else
									{
										description = string.Format("{0} (Error: {1})", win32Path, ex.Message);
									}
									var item = new DriveItem(devicePath, description);
#if DRIVE_ICON && DRIVE_ICON_DYNAMIC
									try { int iconIndex; item.Icon = Program.SHGetFileIcon(win32Path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar, null, out iconIndex, false); }
									catch { }
#endif
									int index = this.tscmbDevice.Items.Add(item);
									if (this.tscmbDevice.SelectedIndex <= 0) { this.tscmbDevice.SelectedIndex = index; }
									var measure = TextRenderer.MeasureText(item.ToString(), this.tscmbDevice.Font, this.tscmbDevice.ContentRectangle.Size, COMBO_BOX_TEXT_FLAGS);

									if (item.Icon != null)
									{
										Size iconSize;
										if (item.Icon is Icon) { iconSize = ((Icon)item.Icon).Size; }
										else { iconSize = ((Image)item.Icon).Size; }
										measure = new Size(measure.Width + iconSize.Width + ICON_PADDING.Horizontal, Math.Max(measure.Height, iconSize.Height + ICON_PADDING.Vertical));
									}

									maxSize = new Size(Math.Max(maxSize.Width, measure.Width), Math.Max(maxSize.Height, measure.Height));
								}
#if CATCH
								catch (Exception ex) { MessageBox.Show(this, string.Format("Unexpected error while adding drive {0}:" + Environment.NewLine + "{1}", deviceName, ex), "Error Adding Drive", MessageBoxButtons.OK, MessageBoxIcon.Error); }
#endif
							}
						}

						var info = new COMBOBOXINFO() { cbSize = Marshal.SizeOf(typeof(COMBOBOXINFO)) };
						unsafe { WinHelper.SendMessage(this.tscmbDevice.ComboBox, 0x0164, IntPtr.Zero, (IntPtr)(&info), true); }
						this.tscmbDevice.ComboBox.ItemHeight = maxSize.Height;
						this.tscmbDevice.ComboBox.Width = this.tscmbDevice.ComboBox.Width - (info.rcItem.right - info.rcItem.left) + 4 + maxSize.Width;
					}
#if CATCH
					catch (Exception ex) { MessageBox.Show(this, string.Format("Unexpected error while adding drives:" + Environment.NewLine + "{0}", ex), "Error Adding Drives", MessageBoxButtons.OK, MessageBoxIcon.Error); }
#endif
				}
				finally { this.sbPnlStage.Text = oldText; }
			}
		}

		private void tvDirectories_AfterSelect(object sender, TreeViewEventArgs e)
		{
			var node = (DirectoryTreeNode)e.Node;
			if (!this.lvFiles.VirtualMode)
			{
				var tag = (NodeTag)e.Node.Tag;
				var items = new List<ListViewItem>();
				int lastTick = Environment.TickCount;
				var files = node.GetFiles(null, SearchOption.TopDirectoryOnly);
				try
				{
					for (int i = 0; i < files.Length; i++)
					{
						var fi = files[i];
						int tick = Environment.TickCount;
						if (tick - lastTick >= Program.FAST_UPDATE_PAUSE_MILLIS)
						{
							this.sbPnlStage.Text = string.Format("Loading icon {0:N0} of {1:N0} ({2:P2})...", i, files.Length, i / (double)files.Length);
							this.sbPnlDescription.Text = fi.FullName;
							lastTick = tick;
						}
						items.Add(this.ToItem(node, this.lvFiles.SmallImageList, fi));
					}
				}
				finally { this.sbPnlStage.Text = this.sbPnlDescription.Text = string.Empty; }
				this.lvFiles.Items.Clear();
				this.lvFiles.SmallImageList = this.lvFiles.SmallImageList;
				try
				{
					this.sbPnlStage.Text = "Displaying files...";
					this.lvFiles.Items.AddRange(items.ToArray());
				}
				finally { this.sbPnlStage.Text = string.Empty; }
			}
			else { this.lvFiles.VirtualListSize = node.Files.Count; this.lvFiles.Invalidate(); }
		}

		private class FullPathComparer<TDir> : Comparer<TDir> where TDir : IDirectoryInfo { public override int Compare(TDir x, TDir y) { return x.FullName.CompareTo(y.FullName); } public static readonly FullPathComparer<TDir> Instance = new FullPathComparer<TDir>(); }

		private void Add_NewMethod(string[] paths, bool archiveOnly)
		{
			var root = (DirectoryTreeNode)this.tvDirectories.FocusedNode;
			string rootPath = root.FullPath;
			var allPaths = new List<string>(paths.Length);
			for (int i = 0; i < paths.Length; i++)
			{
				var path = paths[i].TrimEnd(PATH_SEPS); //Doesn't end in separator; normalized
				if (Directory.Exists(path))
				{
					var currentDir = new Win32DirectoryInfo(path);
					DirectoryTreeNode currentNode;
					int lastProgressReportTick = Environment.TickCount;
					this.ToNode(currentDir, archiveOnly, FOLDER_IMAGE_KEY, new ProgressDialogInfo(null), ref lastProgressReportTick, out currentNode);

					var dirsToFiles = new SortedDictionary<Win32DirectoryInfo, Win32FileInfo[]>(FullPathComparer<Win32DirectoryInfo>.Instance);


					foreach (var directory in dirsToFiles)
					{
						string dirRelativePath = directory.Key.FullName.Substring(path.Length);
						dirRelativePath = NoTrailingSeparator(dirRelativePath);
						foreach (var file in directory.Value)
						{
						}
					}
				}
				else { allPaths.Add(path); }
			}
		}

		private static string NoTrailingSeparator(string dirRelativePath) { if (!string.IsNullOrEmpty(dirRelativePath) && (dirRelativePath[dirRelativePath.Length - 1] == Path.DirectorySeparatorChar || dirRelativePath[dirRelativePath.Length - 1] == Path.AltDirectorySeparatorChar)) { dirRelativePath = dirRelativePath.Substring(dirRelativePath.Length - 1); } return dirRelativePath; }

		private AddResult AddFolder(DirectoryTreeNode parent, string dirPath, bool archiveOnly, ProgressDialogInfo progress, out DirectoryTreeNode addedNode)
		{
			//If this is really a file, then do something else
			if (File.Exists(dirPath)) { ListViewItem item; addedNode = null; return this.AddFile(parent, dirPath, archiveOnly, progress, out item); }
			else
			{
				var lastTick = Environment.TickCount;
				int lastProgressReportTick = lastTick;
				var result = this.ToNode(new VirtualDirectoryInfo(new Win32DirectoryInfo(dirPath)), archiveOnly, FOLDER_IMAGE_KEY, progress, ref lastProgressReportTick, out addedNode);
				if (result == AddResult.Failed) { return result; }
				if (progress.HasDialog && progress.Canceled) { return AddResult.Failed; }
				if (result == AddResult.Succeeded)
				{
					if (!this.CheckFileNameAndDisplayMessageBox(addedNode.Name)) { return AddResult.Failed; }
					if (parent.Nodes.ContainsKey(addedNode.Name))
					{
						MessageBox.Show(this, string.Format("A directory with the same name already exists:" + Environment.NewLine + "{0}", dirPath), "Duplicate Folder", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
						return AddResult.Failed;
					}
					parent.Nodes.Add(addedNode);
				}
				return result;
			}
		}

		private AddResult AddFile(DirectoryTreeNode selectedNode, string filePath, bool archiveOnly, ProgressDialogInfo progress, out ListViewItem item)
		{
			if (Directory.Exists(filePath))
			{
				DirectoryTreeNode node;
				item = null;
				var result = this.AddFolder(selectedNode, filePath, archiveOnly, progress, out node);
				if (result == AddResult.Succeeded) { selectedNode.Expand(); }
				return result;
			}
			else
			{
				var tag = (NodeTag)selectedNode.Tag;
				var file = new VirtualFileInfo(new Win32FileInfo(filePath)); //Making it virtual is CRITICAL!! Otherwise the user could delete the file itself!!
				if (archiveOnly)
				{
					if ((file.Attributes & FileAttributes.Archive) == 0)
					{
						item = null;
						return AddResult.Skipped;
					}
				}
				if (!this.CheckFileNameAndDisplayMessageBox(file.Name)) { item = null; return AddResult.Failed; }
				item = this.ToItem(selectedNode, this.lvFiles.SmallImageList, file);
				if (this.lvFiles.Items.ContainsKey(item.Name))
				{
					MessageBox.Show(this, string.Format("A file with the same name already exists:" + Environment.NewLine + "{0}", filePath), "Duplicate File", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
					return AddResult.Failed;
				}
				selectedNode.Files.Add(file);
				if (this.lvFiles.VirtualMode) { this.lvFiles.VirtualListSize++; }
				else { this.lvFiles.Items.Add(item); }
				return AddResult.Succeeded;
			}
		}

		private enum AddResult { Succeeded, Skipped, Failed }

		private void AddFiles(DirectoryTreeNode selectedNode, bool archiveOnly, string[] filePaths)
		{
#if NEW_ADD_METHOD
			this.Add(filePaths);
#else
			using (new CursorChange(this, Cursors.WaitCursor))
			{
#if CATCH
				try
#endif
				{
					var progress = new ProgressDialog() { Title = "Adding files..." };
#if CATCH
					try
#endif
					{ progress.SetAnimation(shell32.DangerousGetHandle(), 161); }
#if CATCH
					catch { }
#endif
					progress.SetLine(3, "Note: Progress may not accurately indicate time left.", false);
					progress.Start(this.Handle, null, ProgressDialogStartFlags.AutoTime | ProgressDialogStartFlags.Modal);
					progress.SetTimer(ProgressDialogTimerAction.Reset);
					if (Environment.OSVersion.Version.Major < 6) { BeginModalMessageLoop(); }
					try
					{
						var progressInfo = new ProgressDialogInfo(progress);
						int lastProgressReportTick = Environment.TickCount;
						ListViewItem lastItemAdded = null;
						for (int i = 0; i < filePaths.Length; i++)
						{
							ListViewItem item;
							var result = this.AddFile(selectedNode, filePaths[i], archiveOnly, progressInfo.ExtractSegment(i, 1, filePaths.Length), out item);
							if (result == AddResult.Failed) { break; }
							if (result == AddResult.Succeeded && item != null) { lastItemAdded = item; }
						}
						if (lastItemAdded != null) { this.lvFiles.FocusedItem = lastItemAdded; }
					}
					finally
					{
						if (Environment.OSVersion.Version.Major < 6) { EndModalMessageLoop(); }
						progress.Stop();
					}
				}
#if CATCH
				catch (Exception ex)
				{
					MessageBox.Show(this,
#if DEBUG
					ex.ToString()
#else
 ex.Message
#endif
, "Error Adding File", MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
#endif
			}

#endif
			this.CalculateSpace();
		}

		private void AddFolders(DirectoryTreeNode selectedNode, bool archiveOnly, string[] folderPaths)
		{
#if NEW_METHOD
			this.Add(folderPaths);
#else
			int prevIndex = selectedNode.Index;
			var parentOfSelectedNode = selectedNode.Parent;
			DirectoryTreeNode lastNode = selectedNode;
			using (new CursorChange(this, Cursors.WaitCursor))
			{
#if CATCH
				try
#endif
				{
					var progress = new ProgressDialog() { Title = "Adding folder..." };
#if CATCH
					try
#endif
					{ progress.SetAnimation(shell32.DangerousGetHandle(), 161); }
#if CATCH
					catch { }
#endif
					progress.SetLine(3, "Note: Progress may not accurately indicate time left.", false);
					progress.Start(this.Handle, null, ProgressDialogStartFlags.AutoTime | ProgressDialogStartFlags.Modal);
					progress.SetTimer(ProgressDialogTimerAction.Reset);
					if (Environment.OSVersion.Version.Major < 6) { BeginModalMessageLoop(); }
					try
					{
						var progressInfo = new ProgressDialogInfo(progress);
						int lastProgressReportTick = Environment.TickCount;
						for (int i = 0; i < folderPaths.Length; i++)
						{
							DirectoryTreeNode node;
							var result = this.AddFolder(selectedNode, folderPaths[i], archiveOnly, progressInfo.ExtractSegment(i, 1, folderPaths.Length), out node);
							if (result == AddResult.Failed) { break; }
							if (result == AddResult.Succeeded && node != null) { lastNode = node; }
						}
					}
					finally
					{
						if (Environment.OSVersion.Version.Major < 6) { EndModalMessageLoop(); }
						progress.Stop();
					}
				}
#if CATCH
				catch (Exception ex) { MessageBox.Show(this, string.Format("An error occurred while adding the files:" + Environment.NewLine + "{0}", ex.Message), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); }
#endif
			}
#endif
			if (lastNode != null) { EnsureNodeVisible(lastNode); }
			this.CalculateSpace();
		}

		private void lvFiles_DragEnter(object sender, DragEventArgs e) { this.Focus(); e.Effect = GetEffect(e, false); }
		private void lvFiles_DragLeave(object sender, EventArgs e) { }
		private void lvFiles_DragOver(object sender, DragEventArgs e) { e.Effect = GetEffect(e, false); this.lvFiles.Focus(); }

		private void lvFiles_DragDrop(object sender, DragEventArgs e)
		{
			if ((e.Effect & (DragDropEffects.Copy | DragDropEffects.Move)) != 0)
			{
				if (e.Effect == DragDropEffects.Move)
				{
					var dr = MessageBox.Show(this, "You held the SHIFT key, which alters the way in which the data is added." + Environment.NewLine + "In this mode, ONLY files that have their Archive bits set will be added." + Environment.NewLine + "If this is what you intended, press Yes; if you wanted a normal drag, press no.", "Archive-bit Drag Mode", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
					if (dr == DialogResult.No) { e.Effect = DragDropEffects.Copy; }
					else if (dr != DialogResult.Yes) { return; }
				}
				var shellFiles = (string[])e.Data.GetData(System.Windows.Forms.DataFormats.FileDrop);
				if (shellFiles != null) { this.AddFiles((DirectoryTreeNode)this.tvDirectories.FocusedNode, e.Effect == DragDropEffects.Move, shellFiles); }
				else { this.tvDirectories_DragDrop(sender, e); }
			}
		}

		private void tvDirectories_DragOver(object sender, DragEventArgs e)
		{
			e.Effect = GetEffect(e, true);

			var node = this.tvDirectories.GetNodeAt(this.tvDirectories.PointToClient(new Point(e.X, e.Y)));
			if (node != null) { this.tvDirectories.SelectedNode = node; }
			this.tvDirectories.Focus();
		}

		private static DragDropEffects GetEffect(DragEventArgs e, bool allowSelfDrop)
		{
			bool shift = (e.KeyState & 4) != 0;
			bool ctrl = (e.KeyState & 8) != 0;
			bool alt = (e.KeyState & 32) != 0;
			var result = e.AllowedEffect & (shift ? DragDropEffects.Move : DragDropEffects.Copy);
			if (e.Data.GetDataPresent(DataFormats.FileDrop) || (allowSelfDrop && (e.Data.GetDataPresent(typeof(ListViewItem[])) | e.Data.GetDataPresent(typeof(TreeNode))))) { }
			else { result = DragDropEffects.None; }
			return result;
		}

		private void tvDirectories_DragLeave(object sender, EventArgs e) { }
		private void tvDirectories_DragEnter(object sender, DragEventArgs e) { this.Focus(); e.Effect = GetEffect(e, true); }

		private void tvDirectories_DragDrop(object sender, DragEventArgs e)
		{
			if ((e.Effect & (DragDropEffects.Copy | DragDropEffects.Move)) != 0)
			{
				var destNode = (DirectoryTreeNode)this.tvDirectories.FocusedNode;
				using (new CursorChange(this, Cursors.WaitCursor))
				{
					var shellFiles = (string[])e.Data.GetData(System.Windows.Forms.DataFormats.FileDrop);
					if (shellFiles != null) { this.AddFolders(destNode, e.Effect == DragDropEffects.Move, shellFiles); }
					else
					{
						var sourceNode = (DirectoryTreeNode)e.Data.GetData(typeof(DirectoryTreeNode));
						if (sourceNode != null)
						{
							if (sourceNode != destNode)
							{
								if (CanMove(sourceNode, destNode))
								{
									if (!destNode.Contains(sourceNode.Name))
									{
										var clone = (DirectoryTreeNode)sourceNode.Clone();
										destNode.Nodes.Add(clone);
										sourceNode.Remove();
									}
									else
									{
										MessageBox.Show(this, string.Format("The target folder already contains a file or folder named '{0}'.", sourceNode.Name), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
									}
								}
								else { MessageBox.Show(this, "Cannot move a folder into its own subfolder.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); }
							}
						}
						else
						{
							var sourceItems = (ListViewItem[])e.Data.GetData(typeof(ListViewItem[]));
							if (sourceItems != null)
							{
								foreach (var sourceItem in sourceItems)
								{
									var tag = (ItemTag)sourceItem.Tag;
									sourceNode = tag.Directory;
									if (sourceNode != destNode)
									{
										if (!destNode.Contains(sourceItem.Name))
										{
											if ((e.KeyState & 4) != 0 && !sourceNode.Files.Remove(tag.FileInfo))
											{ throw new InvalidOperationException(); }
											destNode.Files.Add(tag.FileInfo);
											if (this.lvFiles.VirtualMode)
											{
												this.lvFiles.VirtualListSize = destNode.Files.Count;
												this.lvFiles.Invalidate();
											}
											else { sourceItem.Remove(); }
										}
									}
								}
							}
						}
					}
				}
			}
		}

		private static void EnsureNodeVisible(DirectoryTreeNode node) { var tv = (MyTreeView)node.TreeView; var prevNode = tv.SelectedNode; try { tv.SelectedNode = node; } finally { tv.SelectedNode = prevNode; } }

		private static bool CanMove(TreeNode source, TreeNode destinationParent)
		{
			while (destinationParent != null)
			{
				if (destinationParent == source) { return false; }
				destinationParent = destinationParent.Parent;
			}
			return true;
		}

		private ListViewItem GetItem(DirectoryTreeNode node, int index) { return this.ToItem(node, this.lvFiles.SmallImageList, node.Files[index]); }

		//Returns True of name is valid, otherwise false
		private bool CheckFileNameAndDisplayMessageBox(string name)
		{
			if (UdfHelper.GetByteLength(name) >= byte.MaxValue)
			{
				MessageBox.Show(this, string.Format("File names must be shorter than {0} bytes:" + Environment.NewLine + "{1}", byte.MaxValue, name), "File Name Too Long", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				return false;
			}
			return true;
		}

		private void tvDirectories_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e) { }

		private void tvDirectories_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
		{
			if (!string.IsNullOrEmpty(e.Label))
			{
				try
				{
					if (e.Node.Parent == null /*root?*/)
					{
						if (e.Label.Length > 32) { MessageBox.Show(this, "The volume label must be at most 32 characters.", "Volume Label Too Long", MessageBoxButtons.OK, MessageBoxIcon.Error); e.CancelEdit = true; }
						else
						{
							if (this.CheckFileNameAndDisplayMessageBox(e.Label)) { this.tvDirectories.Nodes[0].Text = e.Label; }
							else { e.CancelEdit = true; }
						}
					}
					else { e.Node.Name = e.Label; }
				}
				catch { e.CancelEdit = true; throw; }
			}
			else { e.CancelEdit = true; }
		}

		private void tvDirectories_KeyDown(object sender, KeyEventArgs e)
		{
			switch (e.KeyCode)
			{
				case Keys.F2:
					if (this.tvDirectories.SelectedNode != null) { this.tvDirectories.SelectedNode.BeginEdit(); }
					break;
				case Keys.Delete:
					this.DeleteCurrentFolder();
					break;
			}
		}

		private void DeleteCurrentFolder()
		{
			var toRemove = (DirectoryTreeNode)this.tvDirectories.SelectedNode;
			if (toRemove != null)
			{
				if (toRemove.Parent != null)
				{
					if (MessageBox.Show(this, string.Format("Are you sure you want to remove the directory \"{0}\" and all of its children?", toRemove.Name), "Confirm Remove", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
					{
						if (toRemove.NextNode == null) { this.tvDirectories.SelectedNode = toRemove.PrevVisibleNode; }
						toRemove.Remove();
						IDirectoryInfo idir = toRemove;
						idir.Refresh();
					}
				}
				else { MessageBox.Show(this, "Cannot remove the root directory.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); }
			}
			this.CalculateSpace();
		}

		private void lvFiles_DrawItem(object sender, DrawListViewItemEventArgs e) { e.DrawDefault = true; }
		private void lvFiles_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) { e.DrawDefault = true; }
		private void lvFiles_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e) { e.DrawDefault = true; }

		private void txtIdentifier_Validating(object sender, System.ComponentModel.CancelEventArgs e)
		{
			var textBox = (TextBox)sender;
			if (string.IsNullOrEmpty(textBox.Text))
			{
				//e.Cancel = true;
				textBox.Text = "Untitled";
			}
		}

		private void DoWork(BurnType type, IStreamSource source)
		{
			using (new CursorChange(this, Cursors.WaitCursor))
			{
#if CATCH
				try
#endif
				{
					MultimediaDevice targetDevice = null;
#if CATCH
					try
#endif
					{
						var item = (DriveItem)this.tscmbDevice.SelectedItem;
						if (type != BurnType.Burn || item.DevicePath != null)
						{
							if (type == BurnType.Erase && item.DevicePath == null)
							{ throw new InvalidOperationException("This is a virtual drive; it contains no physical disc to erase."); }
							targetDevice = this.OpenDrive(item.DevicePath, Win32FileAccess.GenericRead | Win32FileAccess.GenericWrite | Win32FileAccess.Synchronize);
							try { targetDevice.SynchronizeCache(); }
							catch { }
						}
						var info = new DoWorkInfo(type, source, targetDevice);

						info.BufferSize = 8 << 20;
						if (info.Type == BurnType.Erase)
						{
							if (info.TargetDevice == null) { throw new InvalidOperationException("Cannot erase disc in an image recorder."); }
						}
						else
						{
							using (var form = new FormStartBurn())
							{
								form.ApproximateSpaceNeeded = source.GetLength();
								form.MasteringOptionsEnabled = info.Source is UdfMaster;
								form.CurrentDevice = info.TargetDevice;
								if (form.ShowDialog(this) == DialogResult.OK)
								{
									info.SetCDSpeed = form.SetCDSpeed;
									info.BufferSize = form.BufferSize;
									var asMaster = info.Source as UdfMaster;
									if (asMaster != null)
									{
										asMaster.EncodeDuplicatesOnce = form.FindDuplicates;
										asMaster.AllowIcbDataEmbedding = form.EmbedFiles;
									}
								}
								else
								{
									if (form.CurrentDevice != null)
									{
										form.CurrentDevice.Dispose();
										form.CurrentDevice = null;
									}
									return;
								}
							}
						}
						if (type == BurnType.Burn && item.DevicePath == null)
						{
							//If the drive path is null, we're saving to an image file
							if (this.sfdSaveImage.ShowDialog(this) == DialogResult.OK)
							{ info.TargetImage = FSInterface.Wrap(new FileInfo(this.sfdSaveImage.FileName)); }
							else { return; }
						}
						((FormMdiParent)this.MdiParent).DoWork(info);
					}
#if CATCH
					catch
					{
						if (targetDevice != null) { targetDevice.Dispose(); }
						throw;
					}
#endif
				}
#if CATCH
				catch (Exception ex)
				{
					MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
#endif
			}
		}

		private void cmbAllOptions_SelectedIndexChanged(object sender, EventArgs e)
		{
			var enumValue = (EnumValue)((ComboBox)sender).SelectedItem;
			string description;
			if (enumValue != null)
			{
				description = enumValue.Description;
			}
			else { description = null; }
			this.ttpMain.SetToolTip((Control)sender, description);
		}

		private void lvFiles_AfterLabelEdit(object sender, LabelEditEventArgs e)
		{
			if (e.Label != null)
			{
				if (this.CheckFileNameAndDisplayMessageBox(e.Label))
				{
					var item = this.lvFiles.Items[e.Item];
					var tag = (ItemTag)item.Tag;
					string prevName = tag.FileInfo.Name;
#if CATCH
					try
#endif
					{
						item.Name = e.Label;
#if CATCH
						try
#endif
						{
							tag.Directory.RenameFile(tag.FileInfo, e.Label);
						}
#if CATCH
						catch
						{
							item.Name = prevName;
							throw;
						}
#endif
						item.Text = item.Name;
					}
#if CATCH
					catch
					{
						e.CancelEdit = true;
						throw;
					}
#endif
				}
				else { e.CancelEdit = true; }
			}
			else { e.CancelEdit = true; }
		}

		private void lvFiles_KeyDown(object sender, KeyEventArgs e)
		{
			switch (e.KeyCode)
			{
				case Keys.F2:
					if (this.lvFiles.FocusedItem != null) { this.lvFiles.FocusedItem.BeginEdit(); }
					break;
				case Keys.Delete:
					this.DeleteSelectedFiles();
					break;
			}
		}

		private void DeleteSelectedFiles()
		{
			if (this.lvFiles.SelectedIndices.Count > 0)
			{
				var indices = new int[this.lvFiles.SelectedIndices.Count];
				this.lvFiles.SelectedIndices.CopyTo(indices, 0);
				if (MessageBox.Show(this, string.Format("Are you sure you want to remove the {0:N0} selected file(s)?", indices.Length), "Confirm Remove", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
				{
					var node = (DirectoryTreeNode)this.tvDirectories.SelectedNode;
					for (int i = indices.Length - 1; i >= 0; i--)
					{
						this.lvFiles.SelectedIndices.Remove(indices[i]);
						node.Files.RemoveAt(indices[i]);
						this.lvFiles.VirtualListSize--;
					}
				}
				this.CalculateSpace();
			}
		}

		private void lvFiles_BeforeLabelEdit(object sender, LabelEditEventArgs e) { }
		private void lvFiles_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) { e.Item = this.GetItem((DirectoryTreeNode)this.tvDirectories.SelectedNode, e.ItemIndex); }

		private UdfMaster GetUdfMaster()
		{
			this.master.LogicalVolumeIdentifier = this.tvDirectories.Nodes[0].Text;
			return this.master;
		}

		private void tsbBurn_Click(object sender, EventArgs e) { this.DoWork(BurnType.Burn, this.GetUdfMaster()); }

		private void tsbOpenImage_Click(object sender, EventArgs e)
		{
			var item = (DriveItem)this.tscmbDevice.SelectedItem;
			if (item.DevicePath != null || MessageBox.Show(this, "You have requested to open an image file and \"burn\" it to a virtual drive." + Environment.NewLine + "This action will, in effect, simply open an existing file and create a new copy of it." + Environment.NewLine + "If this is what you intended, press OK; otherwise, press Cancel and select a different drive.", "Possible Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2) == DialogResult.OK)
			{
				if (this.ofdMain.ShowDialog(this) == DialogResult.OK)
				{
					this.DoWork(BurnType.Burn, FSInterface.Wrap(new FileInfo(this.ofdMain.FileName)));
				}
			}
		}

		private void CalculateSpace()
		{
			using (new CursorChange(this, Cursors.WaitCursor))
			{
				long metadataSize, fileCount, dirCount;
				long partitionSize = this.GetUdfMaster().GetLength(out fileCount, out dirCount, out metadataSize);
				long sizeExceptPartition = 256 * Program.BLOCK_SIZE /*For first AVDP*/ + (256 + 1) * Program.BLOCK_SIZE  /*For last AVDP(s)*/;
				long estimate = sizeExceptPartition + partitionSize;
				metadataSize += sizeExceptPartition;
				this.sbPnlStage.Text = string.Format("{0:N0} {1} containing {2:N0} {3}", dirCount, dirCount == 1 ? "folder" : "folders", fileCount, fileCount == 1 ? "file" : "files");
				this.sbPnlDescription.Text = string.Format("{0:N1} MiB (including {1:N1} MiB for metadata)", (double)estimate / (1024 * 1024), (double)metadataSize / (1024 * 1024));
				this.capacityBar.Value = estimate;
				long nextSmallestMarker = -1;
				long largestMarker = -1;
				if (this.capacityBar.Markers != null)
				{
					foreach (var marker in this.capacityBar.Markers)
					{
						if (marker.Location >= this.capacityBar.Value) { nextSmallestMarker = nextSmallestMarker == -1 ? marker.Location : Math.Min(marker.Location, nextSmallestMarker); }
						largestMarker = largestMarker == -1 ? marker.Location : Math.Max(largestMarker, marker.Location);
					}
				}
				if (nextSmallestMarker != -1) { this.capacityBar.Capacity = nextSmallestMarker; }
				else { this.capacityBar.Capacity = largestMarker; }

				var item = (DriveItem)this.tscmbDevice.SelectedItem;
#if false
				if (item.DevicePath == null) { this.capacityBar.Capacity = this.capacityBar.Value; }
				else
				{
					using (var device = this.OpenDrive(item.DevicePath, 0))
					{
						Win32DeviceType devType;
						var types = device.Interface.GetMediaTypesEx(out devType);

						if (types.Length > 0)
						{
							var t = types[0];
							this.capacityBar.Capacity = (long)t.DiskGeometry.BytesPerSector * (long)t.DiskGeometry.SectorsPerTrack * (long)t.DiskGeometry.TracksPerCylinder * (long)t.DiskGeometry.Cylinders;
						}
						else { this.capacityBar.Capacity = this.capacityBar.Value; }
					}
				}
#endif
			}
		}

		private void tvDirectories_ItemDrag(object sender, ItemDragEventArgs e) { if (e.Button == MouseButtons.Left) { var effects = this.tvDirectories.DoDragDrop(e.Item, DragDropEffects.Copy | DragDropEffects.Move); } }

		private void lvFiles_ItemDrag(object sender, ItemDragEventArgs e)
		{
			if (e.Button == MouseButtons.Left)
			{
				var asItem = e.Item as ListViewItem;
				if (asItem != null)
				{
					ListViewItem[] items;
					if (this.lvFiles.VirtualMode)
					{
						var selectedIndices = new int[this.lvFiles.SelectedIndices.Count];
						this.lvFiles.SelectedIndices.CopyTo(selectedIndices, 0);
						items = new ListViewItem[selectedIndices.Length];
						for (int i = 0; i < selectedIndices.Length; i++)
						{ items[i] = this.GetItem(((ItemTag)asItem.Tag).Directory, selectedIndices[i]); }
					}
					else
					{
						items = new ListViewItem[this.lvFiles.SelectedItems.Count];
						this.lvFiles.SelectedItems.CopyTo(items, 0);
					}
					var effects = this.lvFiles.DoDragDrop(items, DragDropEffects.Copy);
				}
			}
		}

		private void miDirectoriesNewFolder_Click(object sender, EventArgs e)
		{
			var node = (DirectoryTreeNode)this.tvDirectories.SelectedNode;
			string name = "New Folder";
			if (node.Contains(name))
			{
				for (int i = 1; i < int.MaxValue; i++)
				{
					string n = name + " " + i.ToString();
					if (!node.Contains(n)) { name = n; break; }
				}
			}
			DirectoryTreeNode newNode;
			int lastProgressReportTick = Environment.TickCount;
			var virt = new VirtualDirectoryInfo(null);
			virt.Rename(name);
			this.ToNode(virt, false, FOLDER_IMAGE_KEY, new ProgressDialogInfo(null), ref lastProgressReportTick, out newNode);
			node.Nodes.Add(newNode);
			this.tvDirectories.SelectedNode = newNode;
			newNode.BeginEdit();
		}

		protected override CreateParams CreateParams { get { var cp = base.CreateParams; cp.Style &= ~0x00030000; cp.ClassStyle |= 0x0200; return cp; } }

		private void miRecorderBlank_Click(object sender, EventArgs e) { this.DoWork(BurnType.Erase, null); }

		private void miRecorderGetFeatures_Click(object sender, EventArgs e)
		{
			var item = (DriveItem)this.tscmbDevice.SelectedItem;
			if (item.DevicePath == null)
			{
				MessageBox.Show(this, "This is a virtual drive used for image recording; it has no particular features.", "Invalid Operation", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			}
			else
			{
				using (new CursorChange(this, Cursors.WaitCursor))
				{
					using (var form = new FormFeatures())
					{
						using (var cd = this.OpenDrive(item.DevicePath, Win32FileAccess.GenericRead | Win32FileAccess.Synchronize))
						{
							var features = cd.Interface.GetCdromConfiguration(FeatureCode.ProfileList, FeatureRequestType.SupportedFeatureHeaderAndDescriptors);
							form.SelectedObject = features;
						}
						form.ShowDialog(this);
					}
				}
			}
		}

		private void tsbShowExplorer_CheckedChanged(object sender, EventArgs e) { this.scMain.Panel2Collapsed = !this.tsbShowExplorer.Checked; }

		private void miSplitterRotate_Click(object sender, EventArgs e)
		{
			double percent = (double)this.scMain.SplitterDistance / (this.scMain.Orientation == Orientation.Vertical ? (double)this.scMain.ClientSize.Width : (double)this.scMain.ClientSize.Height);
			this.scMain.Orientation = this.scMain.Orientation == Orientation.Vertical ? Orientation.Horizontal : Orientation.Vertical;
			this.scMain.SplitterDistance = (int)(percent * (this.scMain.Orientation == Orientation.Vertical ? (double)this.scMain.ClientSize.Width : (double)this.scMain.ClientSize.Height));
		}

		private void shellBrowser_ItemFilter(object sender, ItemFilterEventArgs e)
		{
			var rid = e.RelativeId;
			bool success = false;
			try
			{
				rid.DangerousAddRef(ref success);
				Trace.Assert(success);
				var ids = new IntPtr[] { rid.DangerousGetHandle() };
				var sfgao = SFGAO.Folder | SFGAO.FileSystem | SFGAO.FileSystemAncestor;
				var f = ShellUtil.OpenFolder(e.ParentId);
				f.GetAttributesOf(ids.Length, ids, ref sfgao);
				e.Cancel = (sfgao & (SFGAO.FileSystemAncestor | SFGAO.FileSystem)) == 0;
			}
			finally { if (success) { rid.DangerousRelease(); } }
		}

		private void miRecorderReadWriteErrorRecoveryParameters_Click(object sender, EventArgs e)
		{
			var item = (DriveItem)this.tscmbDevice.SelectedItem;
			if (item.DevicePath == null)
			{
				MessageBox.Show(this, "This is a virtual drive used for image recording; it has no error recovery parameters.", "Invalid Operation", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			}
			else
			{
				using (new CursorChange(this, Cursors.WaitCursor))
				{
					using (var form = new FormErrorRecovery())
					{
						using (var cd = this.OpenDrive(item.DevicePath, Win32FileAccess.GenericRead | Win32FileAccess.GenericWrite | Win32FileAccess.Synchronize))
						{
							form.SelectedObject = cd.GetReadWriteErrorRecoveryParameters(new ModeSense10Command(PageControl.CurrentValues));
							if (form.ShowDialog(this) == DialogResult.OK)
							{
								cd.SetReadWriteErrorRecoveryParameters(new ModeSelect10Command(false, true), form.SelectedObject);
							}
						}
					}
				}
			}
		}
	}

	[System.Security.SuppressUnmanagedCodeSecurity]
	internal sealed class SafeLibraryHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid
	{
		private SafeLibraryHandle() : base(true) { }

		protected override bool ReleaseHandle() { return FreeLibrary(base.handle); }

		internal static SafeLibraryHandle LoadLibraryEx(string libFilename) { var result = LoadLibraryEx(libFilename, IntPtr.Zero, 0); if (result.IsInvalid) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } return result; }

		[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
		[DllImport("Kernel32.dll", SetLastError = true)]
		private static extern bool FreeLibrary(IntPtr hModule);
		
		[DllImport("Kernel32.dll", SetLastError = true)]
		private static extern SafeLibraryHandle LoadLibraryEx(string libFilename, IntPtr reserved, int flags);

		[DllImport("Kernel32.dll", SetLastError = true)]
		public static extern IntPtr GetProcAddress(SafeLibraryHandle hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);
		
		[DllImport("Kernel32.dll", SetLastError = true)]
		public static extern IntPtr GetProcAddress(SafeLibraryHandle hModule, IntPtr lpProcName);
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions