Colabora .NET

Implementando un Splitter en .NET

 

Fecha: 29/Nov/2006 (29-11-06)
Autor: Horacio Nuñez Hernández - [email protected]

 


Prefacio

Antes que nada, agradecerle al Guille (nuevamente) por todas las cosas que aprendido en este sitio y mas que nada por darle la oportunidad a estudiantes como yo de formar parte de esta comunidad .NET, ah y por los artículos de Todo Programación sobre Programación Orientada a Objetos :).

Introducción

Como dice el titulo, en este articulo veremos como implementar un sencillo Splitter, un programa que divide y reagrupa archivos al de estilo Hacha o el mismo Winrar (que también tiene esta opción).

El principio

El principio que usaremos es muy sencillo y se resume a esto:

Es decir, tomamos los bytes de un archivo y los escribimos en nuevos archivos, manteniendo el orden que tenían en el archivo original de forma tal que cuando queramos reagrupar estos fragmentos lo hagamos de forma correcta. ¿Sencillo verdad?
Para implementar esta funcionalidad usando el Class Library del .NET framework debemos hacer uso del namespace System.IO. Más específicamente las clases:

  • StreamWriter
  • StreamReader
  • FileStream

Las dos primeras permiten la lectura/escritura de caracteres  en un Stream  (flujo de bytes que representa un archivo) en una codificación determinada. Nos servirán para escribir el manifiesto, un archivo que contiene información para que podamos reagrupar el archivo. FileStream en cambio permite operaciones sincrónicas y asincrónicas de lectura/ escritura de un archivo. Por lo que será la base del proceso de división y posterior reagrupamiento.

El algoritmo

Utilizando las clases anteriormente descritas, implementaremos dos métodos estáticos contenidos dentro de la  clase SplitBoss: SplitFile y JoinFile

public static void JoinFile(object JoinInfo)
public static void SplitFile(object SplitInfo)
Public Shared Sub JoinFile(ByVal JoinInfo As Object)
Public Shared Sub SplitFile(ByVal SplitInfo As Object)

Estos métodos reciben como parámetro un object, se preguntaran ¿por que? Obviamente estos métodos tienen conocer información acerca del proceso que harán: path del archivo, tamaño de los "pedazos", etc. A esto agregarle que nuestra aplicación debe ser capaz de mostrar el proceso, y seguir siendo capaz de actualizar su interfaz, ya que esta se bloquearía mientras trata con los archivos. La respuesta es la programación concurrente, multihebra o multihilo, y ya en el framework 2.0 podemos pasar parámetros a un hilo de manera "elegante" y no de manera indirecta como sucedia antes.

Una aplicación por default esta compuesta por un hilo de ejecución, pudiéndose agregar muchos mas, siendo el sistema operativo el encargado de gestionar su comportamiento en paralelo. Como mencione unas líneas arriba, con al versión 2.0 del framework podemos pasarle parámetros a los hilos, algo que nos estaba vetado en la primera versión. Hablando en términos de framework, en al versión 1.0 los métodos que iniciaban un hilo (o "vivían en el) tenían que corresponder con la firma del delegado ThreadStart:

 public delegate void ThreadStart ()
 Public Delegate Sub ThreadStart()

El cual como ven no recibe ningún parámetro, por lo que debíamos proporcionarle la información indirectamente (o sea que desde el cuerpo del método obtener la información de variables locales, funciones, etc). Ahora esta disponible el delegado ParameterizedThreadStart, que si recibe un parámetro:

public delegate void ParameterizedThreadStart(Object obj);
Public Delegate Sub ParameterizedThreadStart(ByVal obj As Object)

De ahí que los métodos SplitFile y JoinFile reciban un object, para que coincidan con esta firma. Este object que pasaremos será un objeto de las clases JoinInfo y SplitInfo segun sea el proceso, una vez en el cuerpo del método haremos un cast moldeando el Object para que podamos acceder a la información que necesitamos.

Ahora bien, con todo esto de la programación concurrente necesitamos saber cuando un hilo ha terminado, la manera mas sencilla es que el mismo nos avise. Rápidamente pensamos en eventos, que es perfectamente posible, pero personalmente me decanto por los delegados (que al final es mas o menos lo mismo ya que los delegados son la base de los eventos) . De manera tal que el hilo que la implementación de SplitBoss será menos dependiente del programa como tal, ya que interactuaran a través de delegados. El hilo que hará el trabajo sucio solo tiene que devolver la llamada y ejecutar un método definido con anterioridad pasándole algunos parámetros.

Ahora veamos el código de la clase SplitBoss:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Diagnostics;

namespace Spliter
{ 
    class SplitBoss
    {
        //Delegados
        public delegate void NewLogCallback(string Message,DateTime Time); 
                //Una nueva entrada en log
        public delegate void ProcessChangesCallback(int Value); 
                //Avance del progreso   
        public delegate void FinishProcessBecauseErrorCallback(); 
                //Terminado el proceso por error
        public delegate void FinishProcessCallback();
                //Terminado correctamente

        //Definimos las extensiones
        static string SegmentExtension = ".spt";
        static string ManifiestExtension = ".mspt";

        //Clases que representan la información para llevar a cabo los procesos
        public class SplitInfo
        {
            string filepath; //Archivo a dividir
            public string FilePath
            {
                get { return filepath; }
            }

            int segmentsize; //Tamaño maximo que puede tener un pedazo
            public int SegmentSize
            {
                get { return segmentsize; }
            }

            string folderdestiny; //Carpeta donde depositaremos los pedazos de archivo
            public string FolderDestiny
            {
                get { return folderdestiny; }
            }

            NewLogCallback logcallback;
            public NewLogCallback LogCallback
            {
                get { return logcallback; }
            }

            ProcessChangesCallback processcallback;
            public ProcessChangesCallback ProcessCallback
            {
                get { return processcallback; }
            }

            FinishProcessCallback finishcallback;
            public FinishProcessCallback FinnishCallback
            {
                get { return finishcallback; } 
            }

            FinishProcessBecauseErrorCallback errorcallback;
            public FinishProcessBecauseErrorCallback ErrorCallback
            {
                get { return errorcallback; }
            }

            public SplitInfo(string FilePath,string FolderDestiny, int SegmentSize,
            NewLogCallback LogCallback, ProcessChangesCallback ProcessCallback, 
            FinishProcessCallback FinishCallback, 
            FinishProcessBecauseErrorCallback errorCallback) 
            {
                filepath=FilePath;
                folderdestiny=FolderDestiny;
                segmentsize = SegmentSize;
                logcallback=LogCallback;
                processcallback=ProcessCallback;
                finishcallback = FinishCallback;
                errorcallback = ErrorCallback;
            }


        }

        public class JoinInfo
        {

            string manifiestpath;//Path del archivo manifiesto,
                                 // los pedazos tienen que estar en la misma carpeta
            public string ManifiestPath
            {
                get { return manifiestpath; }
            }

            string folderdestiny; //Carpeta donde se reagruparan loa archivos
            public string FolderDestiny
            {
                get { return folderdestiny; }
            }

            NewLogCallback logcallback;
            public NewLogCallback LogCallback
            {
                get { return logcallback; }
            }

            ProcessChangesCallback processcallback;
            public ProcessChangesCallback ProcessCallback
            {
                get { return processcallback; }
            }

            FinishProcessCallback finishcallback;
            public FinishProcessCallback FinnishCallback
            {
                get { return finishcallback; } 
            }

            FinishProcessBecauseErrorCallback errorcallback;
            public FinishProcessBecauseErrorCallback ErrorCallback
            {
                get { return errorcallback; }
            }

            public JoinInfo(string ManifiestPath, string FolderDestiny,
            NewLogCallback LogCallback, ProcessChangesCallback ProcessCallback,
            FinishProcessCallback FinishCallback, 
            FinishProcessBecauseErrorCallback ErrorCallback)
            {
                manifiestpath = ManifiestPath;
                folderdestiny = FolderDestiny;
                logcallback = LogCallback;
                processcallback = ProcessCallback;
                finishcallback = FinishCallback;
                errorcallback = ErrorCallback;
            }
        }


        public static void JoinFile(object JoinInfo)
        {
            //definimos estas variables para que esten visible en el bloque finally
            //pudiendo liberar los recursos en caso de error
            StreamReader ManifiestReader=null;
            FileStream FileToJoin=null;
            FileStream Segment=null;

            //hacemos un cast para poder acceder a las propiedades
            JoinInfo info = (JoinInfo)JoinInfo;

            try
            {
                //Creamos una entrada en el log
                info.LogCallback("Obteniendo información del manifiesto", DateTime.Now);

                //Abrimos el archivo manifiesto
                ManifiestReader = new StreamReader(info.ManifiestPath);

                //Leemos los datos incluidos en el manifiesto
                string Filename = ManifiestReader.ReadLine(); //Nombre del archivo original, 
                int SegmentSize = System.Convert.ToInt32(ManifiestReader.ReadLine());
			//Tamaño maximo que pueden tener los pedazos
                int count = System.Convert.ToInt32(ManifiestReader.ReadLine());
                            //Numero de pedazos

                //Liberamos los recursos relaciónados con el archivo manifiesto
                ManifiestReader.Close();

                //Creamos una entrada en el log
                info.LogCallback("Creando el archivo", DateTime.Now);

                //Creamos el archivo donde reagruparemos los pedazos,
                // recreando el archivo original
                string FilePath = info.FolderDestiny + @"\" + Filename;
                FileToJoin = new FileStream(FilePath, FileMode.Create, FileAccess.Write);

                //Creamos el buffer
                //Un arreglo de bytes 
                byte[] buffer = new byte[SegmentSize-1];


                //iteraremos extrayendo los bytes de cada pedazo
                // y los escribiremos en el nuevo archivo
                //Teniendo en cuenta que cada proceso de lectura (y escritura) hace avanzar
                //la posición del puntero en el archivo en cuestion,
                // este puntero no tiene nada que ver con los
                //punteros de c++
                for (int i = 0; i <= count - 1; i++)
                {
                    //Abrimos el pedazo
                    string SegmentPath = Path.GetDirectoryName(info.ManifiestPath) + 
			@"\" + Filename + "_" + i + SegmentExtension;
                    Segment = new FileStream(SegmentPath, FileMode.Open, FileAccess.Read);

                    //Leemos en origen y escribimos en destino
                    int n = Segment.Read(buffer, 0, buffer.Length-1);
                    FileToJoin.Write(buffer, 0, n);

                    //Notificamos el avance del proceso
                    info.ProcessCallback(n);

                    //Creamos una entrada en el log
                    info.LogCallback("Añadido segmento " + i, DateTime.Now);

                    //liberamos los recursos relaciónados con el pedazo
                    Segment.Close();
                }

                //liberamos los recursos
                FileToJoin.Close();

                //Creamos una entrada en el log
                info.LogCallback("El proceso ha finalizado", DateTime.Now);

                //Notificamos que el proceso ha terminado correctamente
                info.FinnishCallback();
            }

            catch (Exception ex)
            {
                //liberamos todos los recursos
                //verificando antes que no sean referencias nulas

                if (FileToJoin != null)FileToJoin.Close();

                if (ManifiestReader!= null) ManifiestReader.Close();

                if (Segment != null) Segment.Close();

                //Creamos una entrada en el log, expecificando el error
                info.LogCallback("El proceso a terminado por un error: " + ex.Message, 
                        DateTime.Now);

                //Notificamos que el proceso ha terminado debido a un error
                info.ErrorCallback();
            }
        }

        public static void SplitFile(object SplitInfo)
        {

            //definimos estas variables para que esten visible en el bloque finally
            //pudiendo liberar los recursos en caso de error
            FileStream FileToSplit=null;
            FileStream Segment = null;
            StreamWriter ManifiestWriter = null;

            //hacemos un cast para poder acceder a las propiedades
            SplitInfo info = (SplitInfo)SplitInfo;

            try
            {
                int count = 0; // contador de segmentos

                string Filename = Path.GetFileName(info.FilePath); 
                        //Tomamos el nombre del archivo

                //Creamos una entrada en el log  
                info.LogCallback("Abriendo el archivo a dividir", DateTime.Now);

                //Abrimos el archivo a dividir
                FileToSplit = new FileStream(info.FilePath, FileMode.Open, FileAccess.Read);

                //Creamos una entrada en el log  
                info.LogCallback("Creando Buffer de lectura/escritura", DateTime.Now);

                //Creamos un buffer del tamaño en bytes del segmento
                byte[] buffer = new byte[info.SegmentSize-1];

                //Iteraremos hasta haber alcanzado el fin del fichero a dividir
                //Teniendo en cuenta que cada proceso de lectura (y escritura)
                // hace avanzar segun el numero de bytes leidos/escritos
                //la posición del puntero sobre archivo en cuestion ,
                // este puntero no tiene nada que ver con los
                //punteros de c++
                while (FileToSplit.Position != FileToSplit.Length)
                {

                    //Construimos el path del segmento
                    string SegmentPath = info.FolderDestiny + @"\" + Filename + "_" + 
                                count + SegmentExtension;

                    //Creamos el segmento
                    Segment = new FileStream(SegmentPath, FileMode.Create, FileAccess.Write);

                    //Leemos en origen y escribimos en destino
                    int n = FileToSplit.Read(buffer, 0, buffer.Length-1);
                    Segment.Write(buffer, 0, n);

                    //Cerramos el segmento
                    info.LogCallback("Creando segmento " + count, DateTime.Now);
                    Segment.Close();

                    //Notificamos el avance del proceso
                    info.ProcessCallback(n);

                    //Aumentamos el contador de pedazos
                    count++;

                }

                //Creamos el archivo manifiesto
                info.LogCallback("Creando manifiesto", DateTime.Now);
                string ManifiestPath = info.FolderDestiny + @"\" + Filename + 
                        ManifiestExtension;

                //escribimos la información necesaria para poder regrupar
                // los archivos posteriormente
                ManifiestWriter = new StreamWriter(ManifiestPath, false);
                ManifiestWriter.Write(Filename);
                ManifiestWriter.Write(ManifiestWriter.NewLine);
                ManifiestWriter.Write(info.SegmentSize);
                ManifiestWriter.Write(ManifiestWriter.NewLine);
                ManifiestWriter.Write(count);
                ManifiestWriter.Write(ManifiestWriter.NewLine);
                ManifiestWriter.Write(FileToSplit.Length);

                //Creamos una entrada en el log
                info.LogCallback("El proceso a finalizado, el archivo se ha dividido en " + 
                            count + " segmentos", DateTime.Now);

                //Notificamos que el proceso ha terminado correctamente
                info.FinnishCallback();

            }
            catch (Exception ex)
            {
                //En caso de error, liberamos todos los recursos
                //verificando antes que no sean referencias nulas

                if (FileToSplit!=null) FileToSplit.Close();

                if (ManifiestWriter != null) ManifiestWriter.Close();

                if (Segment != null) Segment.Close();


                //Creamos una entrada en el log, expecificando el error
                info.LogCallback("El proceso a terminado por un error: " + ex.Message, 
                        DateTime.Now);

                //Notificamos que el proceso ha terminado debido a un error
                info.ErrorCallback();
            }
        }

    }
}
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.IO
Imports System.Windows.Forms
Imports System.Diagnostics

Namespace Spliter
    Class SplitBoss
        'Delegados
        Public Delegate Sub NewLogCallback(ByVal Message As String, ByVal Time As DateTime)
            'Una nueva entrada en log
            Public Delegate Sub ProcessChangesCallback(ByVal Value As Integer)
                'Avance del progreso   
                Public Delegate Sub FinishProcessBecauseErrorCallback()
                    'Terminado el proceso por error
                    Public Delegate Sub FinishProcessCallback()
                        'Terminado correctamente
                        'Definimos las extensiones
                        Shared SegmentExtension As String = ".spt"
                        Shared ManifiestExtension As String = ".mspt"

                        'Clases que representan la información para llevar a cabo los procesos
                        Public Class SplitInfo
                            Private m_filepath As String
                            'Archivo a dividir
                            Public ReadOnly Property FilePath() As String
                                Get
                                    Return m_filepath
                                End Get
                            End Property

                            Private m_segmentsize As Integer
                            'Tamaño maximo que puede tener un pedazo
                            Public ReadOnly Property SegmentSize() As Integer
                                Get
                                    Return m_segmentsize
                                End Get
                            End Property

                            Private m_folderdestiny As String
                            'Carpeta donde depositaremos los pedazos de archivo
                            Public ReadOnly Property FolderDestiny() As String
                                Get
                                    Return m_folderdestiny
                                End Get
                            End Property

                            Private m_logcallback As NewLogCallback
                            Public ReadOnly Property LogCallback() As NewLogCallback
                                Get
                                    Return m_logcallback
                                End Get
                            End Property

                            Private m_processcallback As ProcessChangesCallback
                            Public ReadOnly Property ProcessCallback() As ProcessChangesCallback
                                Get
                                    Return m_processcallback
                                End Get
                            End Property

                            Private finishcallback As FinishProcessCallback
                            Public ReadOnly Property FinnishCallback() As FinishProcessCallback
                                Get
                                    Return finishcallback
                                End Get
                            End Property

                            Private m_errorcallback As FinishProcessBecauseErrorCallback
                            Public ReadOnly Property ErrorCallback() As _
                                                FinishProcessBecauseErrorCallback
                                Get
                                    Return m_errorcallback
                                End Get
                            End Property

                            Public Sub New(ByVal FilePath As String, _
                                ByVal FolderDestiny As String, _
                                ByVal SegmentSize As Integer, _
                                ByVal LogCallback As NewLogCallback, _
                                ByVal ProcessCallback As ProcessChangesCallback, _
                                ByVal FinishCallback As FinishProcessCallback, _
                                ByVal errorCallback As FinishProcessBecauseErrorCallback)
                                m_filepath = FilePath
                                m_folderdestiny = FolderDestiny
                                m_segmentsize = SegmentSize
                                m_logcallback = LogCallback
                                m_processcallback = ProcessCallback
                                finishcallback = FinishCallback
                                m_errorcallback = ErrorCallback
                            End Sub


                        End Class

                        Public Class JoinInfo

                            Private m_manifiestpath As String
                            'Path del archivo manifiesto,
                            ' los pedazos tienen que estar en la misma carpeta
                            Public ReadOnly Property ManifiestPath() As String
                                Get
                                    Return m_manifiestpath
                                End Get
                            End Property

                            Private m_folderdestiny As String
                            'Carpeta donde se reagruparan loa archivos
                            Public ReadOnly Property FolderDestiny() As String
                                Get
                                    Return m_folderdestiny
                                End Get
                            End Property

                            Private m_logcallback As NewLogCallback
                            Public ReadOnly Property LogCallback() As NewLogCallback
                                Get
                                    Return m_logcallback
                                End Get
                            End Property

                            Private m_processcallback As ProcessChangesCallback
                            Public ReadOnly Property ProcessCallback() As ProcessChangesCallback
                                Get
                                    Return m_processcallback
                                End Get
                            End Property

                            Private finishcallback As FinishProcessCallback
                            Public ReadOnly Property FinnishCallback() As FinishProcessCallback
                                Get
                                    Return finishcallback
                                End Get
                            End Property

                            Private m_errorcallback As FinishProcessBecauseErrorCallback
                            Public ReadOnly Property ErrorCallback() As _
                                    FinishProcessBecauseErrorCallback
                                Get
                                    Return m_errorcallback
                                End Get
                            End Property

                            Public Sub New(ByVal ManifiestPath As String, _
                                ByVal FolderDestiny As String, _
                                ByVal LogCallback As NewLogCallback, _
                                ByVal ProcessCallback As ProcessChangesCallback, _
                                ByVal FinishCallback As FinishProcessCallback, _
                                ByVal ErrorCallback As FinishProcessBecauseErrorCallback)
                                m_manifiestpath = ManifiestPath
                                m_folderdestiny = FolderDestiny
                                m_logcallback = LogCallback
                                m_processcallback = ProcessCallback
                                finishcallback = FinishCallback
                                m_errorcallback = ErrorCallback
                            End Sub
                        End Class


                        Public Shared Sub JoinFile(ByVal JoinInfo As Object)
                            'definimos estas variables para que esten visible en el bloque finally
                            'pudiendo liberar los recursos en caso de error
                            Dim ManifiestReader As StreamReader = Nothing
                            Dim FileToJoin As FileStream = Nothing
                            Dim Segment As FileStream = Nothing

                            'hacemos un cast para poder acceder a las propiedades
                            Dim info As JoinInfo = DirectCast(JoinInfo, JoinInfo)

                            Try
                                'Creamos una entrada en el log
                                info.LogCallback("Obteniendo información del manifiesto", _
                                    DateTime.Now)

                                'Abrimos el archivo manifiesto
                                ManifiestReader = New StreamReader(info.ManifiestPath)

                                'Leemos los datos incluidos en el manifiesto
                                Dim Filename As String = ManifiestReader.ReadLine()
                                'Nombre del archivo original, 
                                Dim SegmentSize As Integer = _
                                    System.Convert.ToInt32(ManifiestReader.ReadLine())
                                'Tamaño maximo que pueden tener los pedazos
                                Dim count As Integer = _
                                    System.Convert.ToInt32(ManifiestReader.ReadLine())
                                'Numero de pedazos
                                'Liberamos los recursos relaciónados con el archivo manifiesto
                                ManifiestReader.Close()

                                'Creamos una entrada en el log
                                info.LogCallback("Creando el archivo", DateTime.Now)

                                'Creamos el archivo donde reagruparemos los pedazos,
                                ' recreando el archivo original
                                Dim FilePath As String = info.FolderDestiny + "\" + Filename
                                FileToJoin = New FileStream(FilePath, FileMode.Create, _
                                                FileAccess.Write)

                                'Creamos el buffer
                                'Un arreglo de bytes 
                                Dim buffer As Byte() = New Byte(SegmentSize - 1) {}
                                For i As Integer = 0 To count - 1


                                    'iteraremos extrayendo los bytes de cada pedazo y
                                    ' los escribiremos en el nuevo archivo
                                    'Teniendo en cuenta que cada proceso de lectura (y escritura)
                                    ' hace avanzar
                                    'la posición del puntero en el archivo en cuestion,
                                    ' este puntero no tiene nada que ver con los
                                    'punteros de c++
                                    'Abrimos el pedazo
                                    Dim SegmentPath As String = _
                                                Path.GetDirectoryName(info.ManifiestPath) + "\" + _
                                                Filename + "_" + i + SegmentExtension
                                    Segment = New FileStream(SegmentPath, FileMode.Open, _
                                                FileAccess.Read)

                                    'Leemos en origen y escribimos en destino
                                    Dim n As Integer = Segment.Read(buffer, 0, buffer.Length - 1)
                                    FileToJoin.Write(buffer, 0, n)

                                    'Notificamos el avance del proceso
                                    info.ProcessCallback(n)

                                    'Creamos una entrada en el log
                                    info.LogCallback("Añadido segmento " + i, DateTime.Now)

                                    'liberamos los recursos relaciónados con el pedazo
                                    Segment.Close()
                                Next

                                'liberamos los recursos
                                FileToJoin.Close()

                                'Creamos una entrada en el log
                                info.LogCallback("El proceso ha finalizado", DateTime.Now)

                                'Notificamos que el proceso ha terminado correctamente
                                info.FinnishCallback()
                            Catch ex As Exception

                                'liberamos todos los recursos
                                'verificando antes que no sean referencias nulas

                                If FileToJoin IsNot Nothing Then
                                    FileToJoin.Close()
                                End If

                                If ManifiestReader IsNot Nothing Then
                                    ManifiestReader.Close()
                                End If

                                If Segment IsNot Nothing Then
                                    Segment.Close()
                                End If

                                'Creamos una entrada en el log, expecificando el error
                                info.LogCallback("El proceso a terminado por un error: " + _
                                                ex.Message, DateTime.Now)

                                'Notificamos que el proceso ha terminado debido a un error
                                info.ErrorCallback()
                            End Try
                        End Sub

                        Public Shared Sub SplitFile(ByVal SplitInfo As Object)

                            'definimos estas variables para que esten visible en el bloque finally
                            'pudiendo liberar los recursos en caso de error
                            Dim FileToSplit As FileStream = Nothing
                            Dim Segment As FileStream = Nothing
                            Dim ManifiestWriter As StreamWriter = Nothing

                            'hacemos un cast para poder acceder a las propiedades
                            Dim info As SplitInfo = DirectCast(SplitInfo, SplitInfo)

                            Try
                                Dim count As Integer = 0
                                ' contador de segmentos
                                Dim Filename As String = Path.GetFileName(info.FilePath)
                                'Tomamo el nombre del archivo
                                'Creamos una entrada en el log  
                                info.LogCallback("Abriendo el archivo a dividir", DateTime.Now)

                                'Abrimos el archivo a dividir
                                FileToSplit = New FileStream(info.FilePath, FileMode.Open, _
                                    FileAccess.Read)

                                'Creamos una entrada en el log  
                                info.LogCallback("Creando Buffer de lectura/escritura", _
                                    DateTime.Now)

                                'Creamos un buffer del tamaño en bytes del segmento
                                Dim buffer As Byte() = New Byte(info.SegmentSize - 1) {}

                                'Iteraremos hasta haber alcanzado el fin del fichero a dividir
                                'Teniendo en cuenta que cada proceso de lectura (y escritura)
                                ' hace avanzar segun el numero de bytes leidos/escritos
                                'la posición del puntero sobre archivo en cuestion ,
                                ' este puntero no tiene nada que ver con los
                                'punteros de c++
                                While FileToSplit.Position <> FileToSplit.Length

                                    'Construimos el path del segmento
                                    Dim SegmentPath As String = info.FolderDestiny + "\" + _
                                            Filename + "_" + count + SegmentExtension

                                    'Creamos el segmento
                                    Segment = New FileStream(SegmentPath, FileMode.Create, _
                                                FileAccess.Write)

                                    'Leemos en origen y escribimos en destino
                                    Dim n As Integer = FileToSplit.Read(buffer, 0, _
                                                buffer.Length - 1)
                                    Segment.Write(buffer, 0, n)

                                    'Cerramos el segmento
                                    info.LogCallback("Creando segmento " + count, DateTime.Now)
                                    Segment.Close()

                                    'Notificamos el avance del proceso
                                    info.ProcessCallback(n)

                                    'Aumentamos el contador de pedazos

                                    count += 1
                                End While

                                'Creamos el archivo manifiesto
                                info.LogCallback("Creando manifiesto", DateTime.Now)
                                Dim ManifiestPath As String = info.FolderDestiny + "\" + _
                                                Filename + ManifiestExtension

                                'escribimos la información necesaria para poder
                                ' regrupar los archivos posteriormente
                                ManifiestWriter = New StreamWriter(ManifiestPath, False)
                                ManifiestWriter.Write(Filename)
                                ManifiestWriter.Write(ManifiestWriter.NewLine)
                                ManifiestWriter.Write(info.SegmentSize)
                                ManifiestWriter.Write(ManifiestWriter.NewLine)
                                ManifiestWriter.Write(count)
                                ManifiestWriter.Write(ManifiestWriter.NewLine)
                                ManifiestWriter.Write(FileToSplit.Length)

                                'Creamos una entrada en el log
                                info.LogCallback( _
                                    "El proceso a finalizado, el archivo se ha dividido en " + _
                                    count + " segmentos", DateTime.Now)

                                'Notificamos que el proceso ha terminado correctamente

                                info.FinnishCallback()
                            Catch ex As Exception
                                'En caso de error, liberamos todos los recursos
                                'verificando antes que no sean referencias nulas

                                If FileToSplit IsNot Nothing Then
                                    FileToSplit.Close()
                                End If

                                If ManifiestWriter IsNot Nothing Then
                                    ManifiestWriter.Close()
                                End If

                                If Segment IsNot Nothing Then
                                    Segment.Close()
                                End If


                                'Creamos una entrada en el log, expecificando el error
                                info.LogCallback("El proceso a terminado por un error: " + _
                                    ex.Message, DateTime.Now)

                                'Notificamos que el proceso ha terminado debido a un error
                                info.ErrorCallback()
                            End Try
                        End Sub

                    End Class
                End Namespace

Bueno, esta un poco largo, pero solo viéndolo completo podemos entender la otra parte, que es ya la interfaz de la aplicación. Para no extendernos veamos solamente el formulario para Segmentar:

formulario segmentar

El código del botón comenzar es el siguiente, obsérvese como se crea un hilo, y al ejecutar el método Start le pasamos un objeto de tipo SplitInfo:

private void btnComenzar_Click(object sender, EventArgs e)
        {
            this.gpbOpciónes.SendToBack();
            this.gpbProceso.BringToFront();
            this.pgbProceso.Maximum = (int)tamañoArchivo;
            this.btnComenzar.Visible = false;
            if (unHilo == null) unHilo = new Thread(SplitBoss.SplitFile);


            unHilo.Start(new SplitBoss.SplitInfo(this.txtOrigen.Text, txtDestino.Text, 
                        (int)this.nudTamañoSegmento.Value,
            new SplitBoss.NewLogCallback(EscribirLog),
            new SplitBoss.ProcessChangesCallback(AumentarProceso),
            new SplitBoss.FinishProcessCallback(EmitirFinDeProcesoExitoso),
            new SplitBoss.FinishProcessBecauseErrorCallback(EmitirFinConError)));

            this.btnComenzar.Enabled = false;
        }
        Private Sub btnComenzar_Click(ByVal sender As Object, ByVal e As EventArgs)
            Me.gpbOpciónes.SendToBack()
            Me.gpbProceso.BringToFront()
            Me.pgbProceso.Maximum = DirectCast(tamañoArchivo, Integer)
            Me.btnComenzar.Visible = False
            If unHilo Is Nothing Then
                unHilo = New Thread(SplitBoss.SplitFile)
            End If


            unHilo.Start(New SplitBoss.SplitInfo(Me.txtOrigen.Text, txtDestino.Text, _
                        DirectCast(Me.nudTamañoSegmento.Value, Integer), _
                        New SplitBoss.NewLogCallback(EscribirLog), _
                        New SplitBoss.ProcessChangesCallback(AumentarProceso), _
                        New SplitBoss.FinishProcessCallback(EmitirFinDeProcesoExitoso), _
            New SplitBoss.FinishProcessBecauseErrorCallback(EmitirFinConError)))

            Me.btnComenzar.Enabled = False
        End Sub

Terminando

Bueno, espero que este articulo les haya sido de utilidad, y cualquier critica o sugerencia me la pueden hacer al correo, un salu2 a todos


Espacios de nombres usados en el código de este artículo:

System.Windows.Forms
System.IO
System.Threading

 


Código de ejemplo (ZIP):

 

Fichero con el código de ejemplo: horacio_splitter_cs.zip - 97.5 KB

(MD5 checksum: 86A1AB43A11F57E3FC91C9988821D7C7

 


ir al índice principal del Guille