' ***********************************************************************
' Author : Elektro
' Last Modified On : 08-15-2014
' ***********************************************************************
' <copyright file="FileSplitter.vb" company="Elektro Studios">
' Copyright (c) Elektro Studios. All rights reserved.
' </copyright>
' ***********************************************************************
#Region " Imports "
Imports System.ComponentModel
Imports System.IO
#End Region
Public Class FileSplitter
#Region " Properties "
''' <summary>
''' Gets or sets the buffer-size to split or merge, in Bytes.
''' Default value is: 1048576 bytes (1 megabyte).
''' </summary>
''' <value>The buffer-size.</value>
Public Property BufferSize As Integer = 1048576I
#End Region
#Region " Events "
#Region " EventHandlers "
''' <summary>
''' Occurs when the progress changes splitting a file.
''' </summary>
Public Event SplitProgressChanged As EventHandler(Of SplitProgressChangedArgs)
''' <summary>
''' Occurs when the progress changes merging a file.
''' </summary>
Public Event MergeProgressChanged As EventHandler(Of MergeProgressChangedArgs)
#End Region
#Region " Event Args "
#Region " SplitProgressChanged "
''' <summary>
''' Contains the Event arguments of the SplitProgressChanged Event.
''' </summary>
Public Class SplitProgressChangedArgs : Inherits EventArgs
#Region " Constructors "
''' <summary>
''' Prevents a default instance of the <see cref="SplitProgressChangedArgs"/> class from being created.
''' </summary>
Private Sub New()
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="SplitProgressChangedArgs"/> class.
''' </summary>
''' <param name="TotalProgress">The total progress value.</param>
''' <param name="ChunkProgress">The current chunk progress value.</param>
''' <param name="ChunksToCreate">The amount of chunks to create.</param>
''' <param name="ChunksCreated">The amount of created chunks.</param>
Public Sub New(ByVal TotalProgress As Double,
ByVal ChunkProgress As Double,
ByVal ChunksToCreate As Integer,
ByVal ChunksCreated As Integer)
Me._TotalProgress = TotalProgress
Me._ChunkProgress = ChunkProgress
Me._ChunksToCreate = ChunksToCreate
Me._ChunksCreated = ChunksCreated
End Sub
#End Region
#Region " Properties "
''' <summary>
''' Gets the total progress value.
''' (From 0 to 100)
''' </summary>
''' <value>The total progress value.</value>
Public ReadOnly Property TotalProgress As Double
Get
Return Me._TotalProgress
End Get
End Property
Private _TotalProgress As Double = 0.0R
''' <summary>
''' Gets the current chunk progress value.
''' </summary>
''' <value>The current chunk progress value.</value>
Public ReadOnly Property ChunkProgress As Double
Get
Return Me._ChunkProgress
End Get
End Property
Private _ChunkProgress As Double = 0.0R
''' <summary>
''' Gets the amount of chunks to create.
''' </summary>
''' <value>The amount of chunks to create.</value>
Public ReadOnly Property ChunksToCreate As Integer
Get
Return Me._ChunksToCreate
End Get
End Property
Private _ChunksToCreate As Integer = 0I
''' <summary>
''' Gets the amount of created chunks.
''' </summary>
''' <value>The amount of created chunks.</value>
Public ReadOnly Property ChunksCreated As Integer
Get
Return Me._ChunksCreated
End Get
End Property
Private _ChunksCreated As Integer = 0I
#End Region
#Region " Hidden Methods "
''' <summary>
''' Serves as a hash function for a particular type.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub GetHashCode()
End Sub
''' <summary>
''' Determines whether the specified System.Object instances are considered equal.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub Equals()
End Sub
''' <summary>
''' Determines whether the specified System.Object instances are the same instance.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Private Shadows Sub ReferenceEquals()
End Sub
''' <summary>
''' Returns a String that represents the current object.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub ToString()
End Sub
#End Region
End Class
#End Region
#Region " MergeProgressChangedArgs "
''' <summary>
''' Contains the Event arguments of the MergeProgressChangedArgs Event.
''' </summary>
Public Class MergeProgressChangedArgs : Inherits EventArgs
#Region " Constructors "
''' <summary>
''' Prevents a default instance of the <see cref="MergeProgressChangedArgs"/> class from being created.
''' </summary>
Private Sub New()
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="MergeProgressChangedArgs"/> class.
''' </summary>
''' <param name="TotalProgress">The total progress value.</param>
''' <param name="ChunkProgress">The current chunk progress value.</param>
''' <param name="ChunksToMerge">The amount of chunks to merge.</param>
''' <param name="ChunksMerged">The amount of merged chunks.</param>
Public Sub New(ByVal TotalProgress As Double,
ByVal ChunkProgress As Double,
ByVal ChunksToMerge As Integer,
ByVal ChunksMerged As Integer)
Me._TotalProgress = TotalProgress
Me._ChunkProgress = ChunkProgress
Me._ChunksToMerge = ChunksToMerge
Me._ChunksMerged = ChunksMerged
End Sub
#End Region
#Region " Properties "
''' <summary>
''' Gets the total progress value.
''' (From 0 to 100)
''' </summary>
''' <value>The total progress value.</value>
Public ReadOnly Property TotalProgress As Double
Get
Return Me._TotalProgress
End Get
End Property
Private _TotalProgress As Double = 0.0R
''' <summary>
''' Gets the current chunk progress value.
''' </summary>
''' <value>The current chunk progress value.</value>
Public ReadOnly Property ChunkProgress As Double
Get
Return Me._ChunkProgress
End Get
End Property
Private _ChunkProgress As Double = 0.0R
''' <summary>
''' Gets the amount of chunks to merge.
''' </summary>
''' <value>The amount of chunks to merge.</value>
Public ReadOnly Property ChunksToMerge As Integer
Get
Return Me._ChunksToMerge
End Get
End Property
Private _ChunksToMerge As Integer = 0I
''' <summary>
''' Gets the amount of merged chunks.
''' </summary>
''' <value>The amount of merged chunks.</value>
Public ReadOnly Property ChunksMerged As Integer
Get
Return Me._ChunksMerged
End Get
End Property
Private _ChunksMerged As Integer = 0I
#End Region
#Region " Hidden Methods "
''' <summary>
''' Serves as a hash function for a particular type.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub GetHashCode()
End Sub
''' <summary>
''' Determines whether the specified System.Object instances are considered equal.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub Equals()
End Sub
''' <summary>
''' Determines whether the specified System.Object instances are the same instance.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Private Shadows Sub ReferenceEquals()
End Sub
''' <summary>
''' Returns a String that represents the current object.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub ToString()
End Sub
#End Region
End Class
#End Region
#End Region
#End Region
#Region " Hidden Methods "
''' <summary>
''' Serves as a hash function for a particular type.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub GetHashCode()
End Sub
''' <summary>
''' Determines whether the specified System.Object instances are considered equal.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub Equals()
End Sub
''' <summary>
''' Determines whether the specified System.Object instances are the same instance.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Private Shadows Sub ReferenceEquals()
End Sub
''' <summary>
''' Returns a String that represents the current object.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub ToString()
End Sub
#End Region
#Region " Public Methods "
''' <summary>
''' Splits the specified file.
''' </summary>
''' <param name="InputFile">Indicates the file to split.</param>
''' <param name="ChunkSize">Indicates the size of each chunk.</param>
''' <param name="ChunkName">Indicates the name-format for the chunks.</param>
''' <param name="ChunkExt">Indicates the file-extension for the chunks.</param>
''' <param name="Overwrite">
''' If set to <c>true</c> any existing file will be overwritten if needed to create a chunk,
''' otherwise, an exception will be thrown.
''' </param>
''' <param name="DeleteAfterSplit">If set to <c>true</c> the input file will be deleted after a successful split.</param>
''' <exception cref="System.IO.FileNotFoundException">The specified file doesn't exists.</exception>
''' <exception cref="System.IO.IOException">File already exists.</exception>
''' <exception cref="System.OverflowException">'ChunkSize' should be smaller than the Filesize.</exception>
Public Sub Split(ByVal InputFile As String,
ByVal ChunkSize As Long,
Optional ByVal ChunkName As String = Nothing,
Optional ByVal ChunkExt As String = Nothing,
Optional ByVal Overwrite As Boolean = False,
Optional ByVal DeleteAfterSplit As Boolean = False)
If Not File.
Exists(InputFile
) Then Throw New FileNotFoundException("The specified file doesn't exists.", InputFile)
Exit Sub
End If
' The progress event arguments.
Dim ProgressArguments As SplitProgressChangedArgs
' FileInfo instance of the input file.
Dim fInfo As New FileInfo(InputFile)
' The total filesize to split, in bytes.
Dim TotalSize As Long = fInfo.Length
' The remaining size to calculate the percentage, in bytes.
Dim SizeRemaining As Long = TotalSize
' Counts the length of the current chunk file to calculate the percentage, in bytes.
Dim SizeWritten As Long = 0L
' The buffer to read data and write the chunks.
Dim Buffer As Byte() = New Byte() {}
' The buffer length.
Dim BufferLength As Integer = Me.BufferSize
' The total amount of chunks to create.
Dim ChunkCount As Integer = CInt(Math.Floor(fInfo.Length / ChunkSize))
' Keeps track of the current chunk.
Dim ChunkIndex As Integer = 0I
' Keeps track of the total percentage done.
Dim TotalProgress As Double = 0.0R
' Keeps track of the current chunk percentage done.
Dim ChunkProgress As Double = 0.0R
' A zero-filled string to enumerate the chunk files.
Dim Zeros As String = String.Empty
' The given filename for each chunk.
Dim ChunkFile As String = String.Empty
' The chunk file basename.
ChunkName = If(String.IsNullOrEmpty(ChunkName),
Path.Combine(fInfo.DirectoryName, Path.GetFileNameWithoutExtension(fInfo.Name)),
Path.Combine(fInfo.DirectoryName, ChunkName))
' The chunk file extension.
ChunkExt = If(String.IsNullOrEmpty(ChunkExt),
fInfo.Extension.Substring(1I),
ChunkExt)
' If ChunkSize is bigger than filesize then...
If ChunkSize >= fInfo.Length Then
Throw New OverflowException("'ChunkSize' should be smaller than the Filesize.")
Exit Sub
' For cases where a chunksize is smaller than the buffersize.
ElseIf ChunkSize < BufferLength Then
BufferLength = CInt(ChunkSize)
End If ' ChunkSize <>...
' If not file-overwrite is allowed then...
If Not Overwrite Then
For Index As Integer = 0I To (ChunkCount)
' Set chunk filename.
Zeros = New String("0", CStr(ChunkCount).Length - CStr(Index + 1I).Length)
ChunkFile = String.Format("{0}.{1}.{2}", ChunkName, Zeros & CStr(Index + 1I), ChunkExt)
' If chunk file already exists then...
If File.
Exists(ChunkFile
) Then
Throw New IOException(String.Format("File already exists: {0}", ChunkFile))
Exit Sub
End If ' File.Exists(ChunkFile)
Next Index
Zeros = String.Empty
ChunkFile = String.Empty
End If ' Overwrite
' Open the file to start reading bytes.
Using InputStream As New FileStream(fInfo.FullName, FileMode.Open)
Using BinaryReader As New BinaryReader(InputStream)
While (InputStream.Position < InputStream.Length)
' Set chunk filename.
Zeros = New String("0", CStr(ChunkCount).Length - CStr(ChunkIndex + 1I).Length)
ChunkFile = String.Format("{0}.{1}.{2}", ChunkName, Zeros & CStr(ChunkIndex + 1I), ChunkExt)
' Reset written byte-length counter.
SizeWritten = 0L
' Create the chunk file to Write the bytes.
Using OutputStream As New FileStream(ChunkFile, FileMode.Create)
Using BinaryWriter As New BinaryWriter(OutputStream)
' Read until reached the end-bytes of the input file.
While (SizeWritten < ChunkSize) AndAlso (InputStream.Position < InputStream.Length)
' Read bytes from the original file (BufferSize byte-length).
Buffer = BinaryReader.ReadBytes(BufferLength)
' Write those bytes in the chunk file.
BinaryWriter.Write(Buffer)
' Increment the bytes-written counter.
SizeWritten += Buffer.Count
' Decrease the bytes-remaining counter.
SizeRemaining -= Buffer.Count
' Set the total progress.
TotalProgress = (TotalSize - SizeRemaining) * (100I / TotalSize)
' Set the current chunk progress.
ChunkProgress =
If(Not ChunkIndex = ChunkCount,
(100I / ChunkSize) * (SizeWritten - BufferLength),
(100I / (InputStream.Length - (ChunkSize * ChunkIndex))) * (SizeWritten - BufferLength))
' Set the progress event-arguments.
ProgressArguments =
New SplitProgressChangedArgs(
TotalProgress:=If(Not TotalProgress > 99.9R, TotalProgress, 99.9R),
ChunkProgress:=ChunkProgress,
ChunksToCreate:=ChunkCount + 1I,
ChunksCreated:=ChunkIndex)
' Report the progress event-arguments.
RaiseEvent SplitProgressChanged(Me, ProgressArguments)
End While ' (SizeWritten < ChunkSize) AndAlso (InputStream.Position < InputStream.Length)
OutputStream.Flush()
End Using ' BinaryWriter
End Using ' OutputStream
ChunkIndex += 1I 'Increment the chunk file counter.
End While ' InputStream.Position < InputStream.Length
End Using ' BinaryReader
End Using ' InputStream
' Set the progress event-arguments.
ProgressArguments =
New SplitProgressChangedArgs(
TotalProgress:=100.0R,
ChunkProgress:=100.0R,
ChunksToCreate:=ChunkCount + 1I,
ChunksCreated:=ChunkIndex)
' Report the progress event-arguments.
RaiseEvent SplitProgressChanged(Me, ProgressArguments)
End Sub
''' <summary>
''' Merges the specified file.
''' </summary>
''' <param name="InputFile">
''' Indicates the file to merge its chunks.
''' This should be the first chunk file (eg: 'File.Part.01.mkv')
''' </param>
''' <param name="OutputFile">Indicates the output file.</param>
''' <param name="Overwrite">
''' If set to <c>true</c>, in case that the 'OutputFile' exists it will be overwritten,
''' otherwise, an exception will be thrown.
''' </param>
''' <param name="DeleteChunksAfterMerged">
''' If set to <c>true</c>, the chunks will be deleted after a successful.
''' </param>
''' <exception cref="System.IO.FileNotFoundException">The specified file doesn't exists.</exception>
''' <exception cref="System.IO.IOException">File already exists.</exception>
''' <exception cref="System.Exception">The last chunk file is missing.</exception>
''' <exception cref="System.Exception">Unexpected chunk filesize-count detected.</exception>
Public Sub Merge(ByVal InputFile As String,
Optional ByVal OutputFile As String = Nothing,
Optional ByVal Overwrite As Boolean = False,
Optional DeleteChunksAfterMerged As Boolean = False)
If Not File.
Exists(InputFile
) Then Throw New FileNotFoundException("The specified file doesn't exists.", InputFile)
Exit Sub
ElseIf Not Overwrite
AndAlso File.
Exists(OutputFile
) Then Throw New IOException(String.Format("File already exists: {0}", OutputFile))
Exit Sub
End If
' The progress event arguments.
Dim ProgressArguments As MergeProgressChangedArgs
' FileInfo instance of the input chunk file.
Dim fInfo As New FileInfo(InputFile)
' Get the filename without extension.
Dim Filename As String = Path.GetFileNameWithoutExtension(fInfo.FullName)
' Remove the chunk enumeration from the filename.
Filename = Filename.Substring(0I, Filename.LastIndexOf("."c))
' TSet the pattern to find the chunk files to merge.
Dim ChunkPatternSearch As String =
Filename & ".*" & If(Not String.IsNullOrEmpty(fInfo.Extension), fInfo.Extension, "")
' Retrieve all the chunk files to merge them.
Dim Chunks As IEnumerable(Of FileInfo) =
From Chunk As String In
Directory.GetFiles(fInfo.DirectoryName, ChunkPatternSearch, SearchOption.TopDirectoryOnly)
Select New FileInfo(Chunk)
If Chunks.Count < 2I Then ' If chunk files are less than 2 then...
Throw New Exception("The last chunk file is missing.")
Exit Sub
End If
' The total filesize to merge, in bytes.
Dim TotalSize As Long =
(From Chunk As FileInfo In Chunks Select Chunk.Length).Sum
' Gets the filesize of the chunk files and the last chunk file, in bytes.
Dim ChunkSizes As Long() =
(From Chunk As FileInfo In Chunks
Select Chunk.Length Order By Length Descending
).Distinct.ToArray
If ChunkSizes.Count > 2I Then ' If chunk sizes are more than 2...
Throw New Exception("Unexpected chunk filesize-count detected.")
Exit Sub
End If
' The remaining size to calculate the percentage, in bytes.
Dim SizeRemaining As Long = TotalSize
' Counts the length of the current chunk file to calculate the percentage, in bytes.
Dim SizeWritten As Long = 0L
' Counts the length of the written size on the current chunk file, in bytes.
Dim ChunkSizeWritten As Long = 0L
' The buffer to read data and merge the chunks.
Dim Buffer As Byte() = New Byte() {}
' The buffer length.
Dim BufferLength As Integer = Me.BufferSize
' The total amount of chunks to merge.
Dim ChunkCount As Integer = Chunks.Count
' Keeps track of the current chunk.
Dim ChunkIndex As Integer = 0I
' Keeps track of the total percentage done.
Dim TotalProgress As Double = 0.0R
' Create the output file to merge the chunks inside.
Using OutputStream As New FileStream(OutputFile, FileMode.Create)
Using BinaryWriter As New BinaryWriter(OutputStream)
' Iterate the chunks.
For Each Chunk As FileInfo In Chunks
' Open the chunk to start reading bytes.
Using InputStream As New FileStream(Chunk.FullName, FileMode.Open)
Using BinaryReader As New BinaryReader(InputStream)
' Read until reached the end-bytes of the chunk file.
While (InputStream.Position < InputStream.Length)
' Read bytes from the chunk file (BufferSize byte-length).
Buffer = BinaryReader.ReadBytes(BufferLength)
' Write those bytes in the output file.
BinaryWriter.Write(Buffer)
' Increment the bytes-written counters.
SizeWritten += Buffer.Count
ChunkSizeWritten += Buffer.Count
' Decrease the bytes-remaining counter.
SizeRemaining -= Buffer.Count
' Set the total progress.
TotalProgress = (TotalSize - SizeRemaining) * (100I / TotalSize)
' Set the progress event-arguments.
ProgressArguments = New MergeProgressChangedArgs(
TotalProgress:=If(Not TotalProgress > 99.9R, TotalProgress, 99.9R),
ChunkProgress:=(100I / InputStream.Length) * (ChunkSizeWritten - BufferLength),
ChunksToMerge:=ChunkCount,
ChunksMerged:=ChunkIndex)
' Report the progress.
RaiseEvent MergeProgressChanged(Me, ProgressArguments)
End While ' (InputStream.Position < InputStream.Length)
ChunkIndex += 1I ' Increment the chunk file counter.
ChunkSizeWritten = 0L ' Reset the bytes-written for the next chunk.
End Using ' BinaryReader
End Using ' InputStream
Next Chunk
OutputStream.Flush()
End Using ' BinaryWriter
End Using ' OutputStream
' Set the progress event-arguments.
ProgressArguments = New MergeProgressChangedArgs(
TotalProgress:=100.0R,
ChunkProgress:=100.0R,
ChunksToMerge:=ChunkCount,
ChunksMerged:=ChunkIndex)
' Report the progress.
RaiseEvent MergeProgressChanged(Me, ProgressArguments)
If DeleteChunksAfterMerged Then ' Delethe the chunk files.
For Each Chunk As FileInfo In Chunks
File.
Delete(Chunk.
FullName) Next Chunk
End If ' DeleteChunksAfterMerged
End Sub
#End Region
End Class