/*
* IsoFromMedia - Erstellung von einem ISO-Image anhand eines CD/DVD Mediums
* ----------------------------------------------------------------
* Copyright © 2009 Konstantin Gross
* http://www.texturenland.de
* http://blog.texturenland.de
*/
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace TL.IsoImage
{
/// <summary>
/// Ermöglicht ISO-Images von Medien (CD/DVD) anzulegen
/// </summary>
public class IsoFromMedia
{
#region Variablen
/// <summary>
/// BackgroundWorker für das erstellen der ISO-Datei
/// </summary>
BackgroundWorker bgCreator;
/// <summary>
/// Lesender FileStream
/// </summary>
FileStream streamReader;
/// <summary>
/// Schreibender FileStream
/// </summary>
FileStream streamWriter;
#endregion
#region Konstanten
/// <summary>
/// 128 KB Blockgröße
/// </summary>
const int BUFFER = 0x20000;
/// <summary>
/// Maximal 4 GB Größe pro Datei auf FAT32-System
/// </summary>
const long LIMIT = 4294967296;
#endregion
#region Events
/// <summary>
/// Wird ausgelöst, wenn ein Fortschritt stattfindet
/// </summary>
public event IsoEventHandler OnProgress;
/// <summary>
/// Wird ausgelöst, wenn eine Meldung beim erstellen auftaucht
/// </summary>
public event IsoEventHandler OnMessage;
/// <summary>
/// Wird ausgelöst, wenn die Erstellung fertig ist
/// </summary>
public event IsoEventHandler OnFinish;
#endregion
#region Eigenschaften
/// <summary>
/// Pfad zu der ISO-Datei
/// </summary>
string PathToIso { get; set; }
/// <summary>
/// Größe des Mediums
/// </summary>
public long MediumSize { get; set; }
/// <summary>
/// Handle vom Medium
/// </summary>
SafeFileHandle Handle { get; set; }
#endregion
#region Konstruktor
/// <summary>
/// Konstruktor
/// </summary>
public IsoFromMedia()
{
bgCreator = new BackgroundWorker();
bgCreator.WorkerSupportsCancellation = true;
bgCreator.DoWork += new DoWorkEventHandler(bgCreator_DoWork);
bgCreator.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgCreator_RunWorkerCompleted);
}
#endregion
#region Methoden
/// <summary>
/// Startet den Thread mit dem erstellen der ISO-Datei
/// </summary>
void bgCreator_DoWork(object sender, DoWorkEventArgs e)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
try
{
streamReader = new FileStream(Handle, FileAccess.Read, BUFFER);
streamWriter = new FileStream(PathToIso, FileMode.Create, FileAccess.Write, FileShare.None, BUFFER);
byte[] buffer = new byte[BUFFER];
//Lese Buffer-Blöcke von der Quelle und schreibe diese in die ISO-Datei
do
{
if (bgCreator.CancellationPending)
{
e.Cancel = true;
Stop();
break;
}
streamReader.Read(buffer, 0, BUFFER);
streamWriter.Write(buffer, 0, BUFFER);
if (OnProgress != null)
{
//Fortschritt in Prozent
int percent = Convert.ToInt32((streamWriter.Length * 100) / MediumSize);
EventIsoArgs eArgs = new EventIsoArgs(streamWriter.Position, percent);
OnProgress(eArgs);
}
} while (streamReader.Position == streamWriter.Position);
}
catch (Exception ex)
{
if (OnMessage != null)
{
EventIsoArgs eArgs = new EventIsoArgs("Fehler beim erstellen des Images: " + ex.Message);
OnMessage(eArgs);
}
}
finally
{
if (OnFinish != null)
{
EventIsoArgs eArgs = new EventIsoArgs(stopWatch.Elapsed);
OnFinish(eArgs);
}
}
}
/// <summary>
/// Wenn die Erstellung abgeschlossen ist
/// </summary>
void bgCreator_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
CloseAll();
}
/// <summary>
/// Erstellt ein ISO-Image von einem Medium (CD/DVD)
/// </summary>
/// <param name="source">CD/DVD</param>
/// <param name="destination">Pfad, wohin die ISO-Datei abgelegt werden soll</param>
/// <returns>
/// Running = Erstellung läuft
/// InvalidHandle = Ungültiger Handle
/// NoDevice = Die Quelle ist kein Medium (CD/DVD)
/// NotEnoughMemory = Nicht genügend Speicherplatz vorhanden
/// LimitExceeded = Ziel überschreitet FAT32 Größe von 4 GB (4096 MB)
/// NotReady = Das Gerät ist nicht bereit
/// </returns>
public Status CreateIsoFromMedia(string source, string destination)
{
//Ist das Gerät bereit?
if (!new DriveInfo(source).IsReady)
return Status.NotReady;
//Quelle CD/DVD
if (new DriveInfo(source).DriveType != DriveType.CDRom)
return Status.NoDevice;
//Mediumgröße ermitteln
MediumSize = GetMediumLength(source);
//Überprüfe Speicherplatz
long diskSize = new DriveInfo(Path.GetPathRoot(destination)).AvailableFreeSpace;
if (diskSize <= MediumSize)
return Status.NotEnoughMemory;
//Überprüfe Kapazität von > 4096 MB (NTFS)
if (!CheckNTFS(destination) && MediumSize >= LIMIT)
return Status.LimitExceeded;
//Erstelle Handle
Handle = Win32.CreateFile(source);
if (!string.IsNullOrEmpty(destination))
PathToIso = destination;
//Wenn ungültiger Handle oder geschlossener Handle
if (Handle.IsInvalid || Handle.IsClosed)
return Status.InvalidHandle;
//Erstelle Thread zum erstellen der ISO-Datei
bgCreator.RunWorkerAsync();
return Status.Running;
}
/// <summary>
/// Beendet die Erstellung des Images und löscht zugleich das Image
/// </summary>
public void Stop()
{
CloseAll();
if (File.Exists(PathToIso))
File.Delete(PathToIso);
if (OnMessage != null)
{
EventIsoArgs e = new EventIsoArgs(@"Erstellung des Images abgebrochen");
OnMessage(e);
}
}
/// <summary>
/// Schließt alle Streams und Handles und gibt Ressourcen frei
/// </summary>
private void CloseAll()
{
if (bgCreator != null)
{
bgCreator.CancelAsync();
bgCreator.Dispose();
}
if (streamReader != null)
{
streamReader.Close();
streamReader.Dispose();
}
if (streamWriter != null)
{
streamWriter.Close();
streamWriter.Dispose();
}
if (Handle != null)
{
Handle.Close();
Handle.Dispose();
}
}
/// <summary>
/// Größe des Mediums (CD/DVD)
/// </summary>
/// <param name="drive">Quell-Laufwerk.</param>
/// <returns>Größe in Bytes</returns>
private long GetMediumLength(string drive)
{
return new DriveInfo(drive).TotalSize;
}
/// <summary>
/// Zeigt an, ob das Dateisystem NTFS ist
/// </summary>
/// <param name="destination">Pfad zu der ISO-Datei</param>
/// <returns>True, wenn NTFS</returns>
private bool CheckNTFS(string destination)
{
return new DriveInfo(Path.GetPathRoot(destination)).DriveFormat == "NTFS" ? true : false;
}
#endregion
}
#region Enumeration
/// <summary>
/// Gibt den Status der ISO-Image Erstellung aus
/// </summary>
public enum Status
{
/// <summary>
/// Erstellung läuft
/// </summary>
Running = 1,
/// <summary>
/// Ungültiger Handle
/// </summary>
InvalidHandle = -1,
/// <summary>
/// Die Quelle ist kein Medium (CD/DVD)
/// </summary>
NoDevice = -2,
/// <summary>
/// Nicht genügend Speicherplatz vorhanden
/// </summary>
NotEnoughMemory = -3,
/// <summary>
/// Ziel überschreitet FAT32 Größenlimit von 4 GB (4096 MB)
/// </summary>
LimitExceeded = -4,
/// <summary>
/// Das Gerät ist nicht bereit
/// </summary>
NotReady = -5
}
#endregion
#region EventIsoArgs
public delegate void IsoEventHandler(EventIsoArgs e);
/// <summary>
/// Beinhaltet zusätzliche Daten für Event
/// </summary>
public class EventIsoArgs : EventArgs
{
/// <summary>
/// Bereits geschriebene Bytes
/// </summary>
public long WrittenSize { get; private set; }
/// <summary>
/// Fortschritt in Prozenten
/// </summary>
public int ProgressPercent { get; private set; }
/// <summary>
/// Laufzeit
/// </summary>
public TimeSpan ElapsedTime { get; private set; }
/// <summary>
/// Nachricht
/// </summary>
public string Message { get; private set; }
public EventIsoArgs(TimeSpan value)
: base()
{
ElapsedTime = value;
}
public EventIsoArgs(long value, int percent)
: base()
{
WrittenSize = value;
ProgressPercent = percent;
}
public EventIsoArgs(string value)
: base()
{
Message = value;
}
}
#endregion
#region Win32
/// <summary>
/// Stellt die Funktionalität bereit, mit Windowsmethoden
/// </summary>
internal class Win32
{
/// <summary>
/// Lesezugriff
/// </summary>
static uint GENERIC_READ = 0x80000000;
/// <summary>
/// Gibt an, dass nachfolgende öffnen Vorgänge auf das Objekt nur erfolgreich sind, wenn ein Lesezugriff angefordert wird
/// </summary>
static uint FILE_SHARE_READ = 0x1;
/// <summary>
/// Öffnet die Datei. Wird fehlschlagen, wenn die Datei nicht existiert
/// </summary>
static uint OPEN_EXISTING = 0x3;
/// <summary>
/// Gibt an, dass die Datei keine anderen Attribute hat, dieses Attribut ist nur gültig wenn es allein verwendet wird
/// </summary>
static uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
/// <summary>
/// Gibt ein Handle zurück, das genutzt werden kann, um auf eine Datei oder ein Gerät
/// auf unterschiedlichste Art und Weise zuzugreifen
/// </summary>
/// <param name="lpFileName">Der Name der Datei oder des Gerätes, das erstellt oder geöffnet werden soll</param>
/// <param name="dwDesiredAccess">Der Zugriff auf die angeforderte Datei oder des Gerätes</param>
/// <param name="dwShareMode">Der angeforderte Austausch-Modus der Datei oder des Geräts</param>
/// <param name="lpSecurityAttributes">Zeiger der auf ein Sicherheits-Attribut zeigt</param>
/// <param name="dwCreationDisposition">Eine Aktion, die auf eine Datei oder ein Gerät durchgeführt wird, wenn es vorhanden ist oder nicht vorhanden ist</param>
/// <param name="dwFlagsAndAttributes">Datei/Gerät-Attribut, am häufigsten wird FILE_ATTRIBUTE_NORMAL genutzt</param>
/// <param name="hTemplateFile">Ein Handle auf eine Vorlagen-Datei (beim öffnen spielt dieser Parameter keine Rolle)</param>
/// <returns>Wenn die Methode erfolgreich ausgeführt werden konnte, gibt es einen gültigen Handle auf eine Datei oder ein Gerät zurück</returns>
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
/// <summary>
/// Erstellt das Handle vom Medium
/// </summary>
/// <param name="device">Medium (CD/DVD)</param>
/// <returns>Handle des Mediums</returns>
public static SafeFileHandle CreateFile(string device)
{
//Prüfe wie das Medium angegeben wurde und kürz es demenstprechend
//Z.B. Z:\ -> Z: ansonsten ändere nichts
string devName = device.EndsWith(@"\") ? device.Substring(0, device.Length - 1) : device;
//Erstelle das Handle
IntPtr devHandle = CreateFile(string.Format(@"\\.\{0}", devName), GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
return new SafeFileHandle(devHandle, true);
}
}
#endregion
}