using System;
using System.IO;
using System.Windows.Forms;
using Helper.IO;
using SCSI;
using SCSI.Multimedia;
using System.Threading;
using System.Runtime.InteropServices;
namespace ISOBurn
{
public partial class FormMain : Form
{
private MultimediaDevice currentCD;
private static readonly TimeSpan UPDATE_PAUSE = TimeSpan.FromMilliseconds(25);
private static readonly TimeSpan SPEED_UPDATE_PAUSE = TimeSpan.FromMilliseconds(250);
private ProgressInfo lastProgress;
private int lastProgressReportTick;
public FormMain() { this.InitializeComponent(); }
[DllImport("User32.dll")]
private static extern bool SetCursorPos(int X, int Y);
private void RefreshCursor() { SetCursorPos(System.Windows.Forms.Control.MousePosition.X, System.Windows.Forms.Control.MousePosition.Y); }
[DllImport("User32.dll", SetLastError = true)]
private static extern bool EnableWindow(IntPtr hWnd, [MarshalAs(UnmanagedType.Bool)] bool bEnable);
private void btnBurn_Click(object sender, EventArgs e)
{
var drive = (DriveInfo)this.cmbDevice.SelectedItem;
var stream = new Win32FileStream(@"\\.\" + drive.Name.TrimEnd(Path.DirectorySeparatorChar), FileAccess.ReadWrite, FileShare.None, FileMode.Open, FileOptions.None);
var spti = new SPTI(stream.SafeFileHandle, false);
this.currentCD = new MultimediaDevice(spti, false);
this.cmbDevice.Enabled = false;
this.btnBrowse.Enabled = false;
this.btnBurn.Enabled = false;
this.txtFileName.ReadOnly = true;
this.UseWaitCursor = true;
this.bwCDBurner.RunWorkerAsync(new DoWorkInfo(this.currentCD));
this.btnCancel.Enabled = true;
}
protected override void OnLoad(EventArgs e)
{
var drives = System.IO.DriveInfo.GetDrives();
foreach (var drive in drives)
{
if (drive.DriveType == System.IO.DriveType.CDRom)
{
this.cmbDevice.Items.Add(drive);
if (this.cmbDevice.SelectedItem == null) { this.cmbDevice.SelectedItem = drive; }
}
}
base.OnLoad(e);
}
private class DoWorkInfo { public DoWorkInfo(MultimediaDevice device) { this.Device = device; } public MultimediaDevice Device; }
private class ProgressInfo
{
public ProgressInfo(string description, long completed, long total, BurnStage stage)
{
this.Description = description;
this.Completed = completed;
this.Total = total;
this.Stage = stage;
}
public string Description;
public long Completed; public long Total;
public BurnStage Stage;
}
private enum BurnStage { Blanking, Burning, ClosingTrack, ClosingSession, }
private void bwCDBurner_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
//The actual burning occurs here, asynchronously from the UI
this.lastProgressReportTick = Environment.TickCount;
var info = (DoWorkInfo)e.Argument;
using (var mmstream = new SCSIStream(info.Device))
{
info.Device.SetCDSpeed(new SetCDSpeedCommand(ushort.MaxValue, ushort.MaxValue, RotationControl.CAV));
//info.Device.PreventAllowMediumRemoval(new PreventAllowMediumRemovalCommand(true, false));
try
{
int prevTick = Environment.TickCount;
SenseData sense;
if (info.Device.ReadDiscInformation().Erasable)
{
if (MessageBox.Show("Detected an erasable disc. Would you like to quickly erase it (highly recommended)?", "Erase Disc", MessageBoxButtons.YesNo, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1) == DialogResult.Yes)
{
info.Device.Blank(new BlankCommand(BlankingType.BlankMinimal, true, 0));
while ((sense = info.Device.RequestSense()).SenseKey == SenseKey.NotReady
&& sense.AdditionalSenseCode == AdditionalSenseCode.LogicalUnitNotReady
&& sense.AdditionalSenseCodeQualifier == (AdditionalSenseCodeQualifier)7)
{
int tick = Environment.TickCount;
if (tick - prevTick > UPDATE_PAUSE.TotalMilliseconds)
{
this.bwCDBurner.ReportProgress((int)(sense.SenseKeySpecific.ProgressIndication.ProgressIndicationFraction * 100),
new ProgressInfo("Erasing CD (cannot be canceled)...", sense.SenseKeySpecific.ProgressIndication.ProgressIndication, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Blanking));
tick = prevTick;
}
else { Thread.Sleep(Math.Min((int)UPDATE_PAUSE.TotalMilliseconds - (tick - prevTick), 0)); }
}
this.bwCDBurner.ReportProgress(100, new ProgressInfo("CD erased, starting burn...", ProgressIndicationBytes.ProgressIndicationDenominator, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Blanking));
}
}
var tracks = info.Device.ReadAllTracksInformation();
if (tracks[tracks.Length - 1].NextWritableAddress == null)
{ throw new InvalidOperationException("Last track is not writable. Writing cannot continue."); }
using (Stream fileStream = new FileStream(this.txtFileName.Text, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
if (tracks.Length > 0) { mmstream.Position = info.Device.SectorSize * tracks[tracks.Length - 1].NextWritableAddress.Value; }
var buffer = new byte[info.Device.SectorSize];
while (fileStream.Position < fileStream.Length && !this.bwCDBurner.CancellationPending)
{
int tick = Environment.TickCount;
if (tick - prevTick > UPDATE_PAUSE.TotalMilliseconds)
{
fileStream.Read(buffer, 0, buffer.Length);
mmstream.Write(buffer, 0, buffer.Length);
this.bwCDBurner.ReportProgress((int)(fileStream.Position * 100 / fileStream.Length), new ProgressInfo("Burning data...", fileStream.Position, fileStream.Length, BurnStage.Burning));
tick = prevTick;
}
else { Thread.Sleep(Math.Min((int)UPDATE_PAUSE.TotalMilliseconds - (tick - prevTick), 0)); }
}
this.bwCDBurner.ReportProgress((int)(fileStream.Position * 100 / fileStream.Length), new ProgressInfo("Flushing...", fileStream.Position, fileStream.Length, BurnStage.Burning));
this.bwCDBurner.ReportProgress(100, new ProgressInfo("Burn completed...", ProgressIndicationBytes.ProgressIndicationDenominator, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.Burning));
mmstream.Flush();
}
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Closing track...", 0, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.ClosingTrack));
info.Device.CloseTrackOrSession(new CloseSessionOrTrackCommand(true, TrackSessionCloseFunction.CloseLogicalTrack, 1));
while ((sense = info.Device.RequestSense()).SenseKey == SenseKey.NotReady
&& sense.AdditionalSenseCode == AdditionalSenseCode.LogicalUnitNotReady
&& sense.AdditionalSenseCodeQualifier == (AdditionalSenseCodeQualifier)7)
{
int tick = Environment.TickCount;
if (tick - prevTick > UPDATE_PAUSE.TotalMilliseconds)
{
this.bwCDBurner.ReportProgress((int)(sense.SenseKeySpecific.ProgressIndication.ProgressIndicationFraction * 100),
new ProgressInfo("Closing session (cannot be canceled)...", sense.SenseKeySpecific.ProgressIndication.ProgressIndication, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.ClosingTrack));
prevTick = tick;
}
else { Thread.Sleep(Math.Min((int)UPDATE_PAUSE.TotalMilliseconds - (tick - prevTick), 0)); }
}
this.bwCDBurner.ReportProgress(100, new ProgressInfo("Track closed.", ProgressIndicationBytes.ProgressIndicationDenominator, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.ClosingTrack));
this.bwCDBurner.ReportProgress(0, new ProgressInfo("Closing session...", 0, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.ClosingSession));
info.Device.CloseTrackOrSession(new CloseSessionOrTrackCommand(true, TrackSessionCloseFunction.CloseSessionOrStopBGFormat, 1));
while ((sense = info.Device.RequestSense()).SenseKey == SenseKey.NotReady
&& sense.AdditionalSenseCode == AdditionalSenseCode.LogicalUnitNotReady
&& sense.AdditionalSenseCodeQualifier == (AdditionalSenseCodeQualifier)7)
{
int tick = Environment.TickCount;
if (tick - prevTick > UPDATE_PAUSE.TotalMilliseconds)
{
this.bwCDBurner.ReportProgress((int)(sense.SenseKeySpecific.ProgressIndication.ProgressIndicationFraction * 100),
new ProgressInfo("Closing session (cannot be canceled)...", sense.SenseKeySpecific.ProgressIndication.ProgressIndication, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.ClosingSession));
prevTick = tick;
}
else { Thread.Sleep(Math.Min((int)UPDATE_PAUSE.TotalMilliseconds - (tick - prevTick), 0)); }
}
this.bwCDBurner.ReportProgress(100, new ProgressInfo("Session closed.", ProgressIndicationBytes.ProgressIndicationDenominator, ProgressIndicationBytes.ProgressIndicationDenominator, BurnStage.ClosingSession));
}
finally
{
//info.Device.PreventAllowMediumRemoval(new PreventAllowMediumRemovalCommand(false, false));
}
}
e.Result = e.Argument;
}
private void bwCDBurner_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
var state = (ProgressInfo)e.UserState;
bool setNewProgress;
var elapsed = TimeSpan.FromMilliseconds(Environment.TickCount - this.lastProgressReportTick);
if (this.lastProgress != null && this.lastProgress.Stage == state.Stage && state.Stage == BurnStage.Burning)
{
double bytesPerSecond = (state.Completed - this.lastProgress.Completed) / elapsed.TotalSeconds;
if (elapsed >= SPEED_UPDATE_PAUSE && !double.IsInfinity(bytesPerSecond) && !double.IsNaN(bytesPerSecond))
{
this.lblInfo.Text = string.Format("{0} ({1:N0} KB/s)", state.Description, bytesPerSecond / 1024);
setNewProgress = true;
}
else { setNewProgress = false; }
}
else
{
this.lblInfo.Text = state.Description;
setNewProgress = true;
}
if (setNewProgress)
{
this.lastProgress = state;
this.lastProgressReportTick = Environment.TickCount;
}
if (state.Total > int.MaxValue) { this.pbMain.Maximum = int.MaxValue; this.pbMain.Value = (int)((double)state.Completed * int.MaxValue / state.Total); }
else { this.pbMain.Maximum = (int)state.Total; this.pbMain.Value = (int)state.Completed; }
}
private void bwCDBurner_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
this.UseWaitCursor = false;
this.currentCD.Dispose();
this.currentCD = null;
if (e.Error != null)
{
MessageBox.Show(string.Format("{0}", e.Error), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else if (e.Cancelled)
{ this.lblInfo.Text = "Burn canceled. You may need to eject and re-insert the CD to refresh Explorer."; }
else
{ this.lblInfo.Text = "Burn successful. You may need to eject and re-insert the CD to refresh Explorer."; }
this.pbMain.Value = this.pbMain.Minimum;
this.btnBurn.Enabled = true;
this.btnBrowse.Enabled = true;
this.txtFileName.ReadOnly = false;
this.btnCancel.Enabled = false;
this.cmbDevice.Enabled = true;
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (this.bwCDBurner.IsBusy)
{
this.bwCDBurner.CancelAsync();
}
this.btnCancel.Enabled = false;
}
private void btnBrowse_Click(object sender, EventArgs e)
{
if (this.ofdMain.ShowDialog(this) == DialogResult.OK)
{
this.txtFileName.Text = this.ofdMain.FileName;
}
}
}
}