Me falta crear un fichero reg, que sirva de apoyo al ejemplo, para hacer el registro de las claves que se van a necesitar. Este lo pondré a la noche cuando regrese del curro...
Para el ejemplo vamos a considerar que se tratara de un programa que puede comprimir y descomprimir (que suelen tener muchos parámetros disponibles y por ello más complejos de lo habitual)... debe servir para entender ampliamente las formas de proceder (que no son fijas, ni inalterables, solo instructivas). Y supuestamente... dicho programa de ejemplo generaría ficheros con extensión *.comp
Al comienzo hay que considerar que acciones vamos a necesitar y qué parámetros precisa cada una.
Si tenemos ya el código que hace cada acción, los parámetros pueden ser los mismos que toman las funciones, y solo resta ver de escoger la letra/s más propicia para cada acción. Si son pocas acciones las que vamos a registrar, no hace falta comerse mucho la cabeza pero sin muchas, habrá que pararse en ello, más tiempo y meditarlo bien....
Algunas aclaraciones previas para no liarnos:
Los parámetros que aceptamos, no distinguimos mayúsculas de minúsculas pero eso al gusto, si el programa hubiera de proveer tropecientos parámetros, quizás haya que repensarlo, aunque es fácil que los usuarios no reparen en la distinción de tal circunstancia... es preferible evitar parecidos confusos.
Cuando solo lleva un parámetro o dos, no hace falta mucho 'guiso' con ellos, pero cuando van a ser varios, es preferible que tengan un formato que podamos entender y manipular fácilmente.
Como en las líneas de comando, está a la orden del día que se incluyan rutas, debes parar a considerar que ciertos caracteres no son válidos para las rutas, precisamente por que se utilizan para acciones sobre las rutas... por ejemplo nunca podrás poner un '*' al nombre de un fichero o carpeta, porque el asterisco es un 'comodín' para nombres de ficheros... esos caracteres prohibidos en las rutas, son precisamente los más aptos y los que nos han de servir para realizar nuestro formato. Los que programan en 'C', tienen el gran incordio de las dobles barras '\\' como separadores de rutas, precisamente por ser un escollo... harmeos uso del carácter ":" que como también foramn parte de las rutas, y por elegir hacer 'splits', igualmente nos vemos forzados s poner 2... podríamos haber elegido otro modelo para el formato, pero así se aprenden más cosas por el camino.
Y ahora al grano, cada parámetro irá antecedido de la barra ' /' y detrás irá la letra o letras que dan nombre al parámetro y que será lo que el programa reconozca. Cuando un parámetro deba llevar adjunto además de un valor, irá en la forma:
/parametro::valor
Ejemplos:
/ruta::C:/program files/compresor/ejemplo.txt
/T::3
/S::<<<--->>>
Luego en el programa se escruta la línea de comandos recibida...
- Iniciamente se hace un split, generando así un array con todos los parámetros y ya libres de la barra, porque cortamos justamente por ella (fijarse que lleva un espacio delante, para impedir que se corte por la barra de las rutas). Por tanto se exige, que cada parámetro vaya antecedido de " /"...
- Los parámetros son flexibles, no se exige que haya un orden concreto en la entrada de los parámetros, excepto para el parámetro que marca la orden-acción a ejecutar, que debe ser el primer parámetro.
De no ser asi, complica el asunto (innecesariamente pués antes que nada, habría que recorrer todos los parámetros para buscar la orden y retirarla, para luego investigar que parámetro acompañan a dicha orden).
- El primer parámetro se exige que sea la acción-orden que se reclama llevar a cabo. Cada tipo de orden llevará asociado sus propios parámetros, e ignorará los que no le competen (no marcará error si aparece un parámetro que no entiende, una vez quede clara la orden a ejecutar).
Al diseñar los parámetros (el string dedicado a cada uno), sin embargo los específicos de una orden no deberían interferir con los de otra, es decir, los usados para una orden o significan lo mismo para otra orden o mejor usar otra 'letra-palabra', para no confundir al usuario del programa ni producirle amargas experiencias...
Es decir si /t significa Tipo, para una acción conviene que signifiqwue Tipo para todas las acciones, hacer que signifique 'Todo' para otra acción facilita que un posible usuario cometa errores y no se los aprenda.
Como decía, el programa de ejemplo consideramos que se tratara de un programa que puede comprimir y descomprimir, estas son las órdenes admitidas (evidentemente las que uno quiera tratar), yo he elegido estas de ejemplo y nótese que aunque en el registro se pondrá solo una letra, si se invoca por ejemplo desde un bat o desde una ventana de scripts (cmd, powershell, etc...) puede pasarse cualquiera de los valores señalados para la acción:
/h, /?, /Help, /Ayuda
Propósito: mostrar info de ayuda
Si aparece, se ignora el resto de parámetros que hubiere...
La ayuda se invoca también si aparece un parámetro como orden, que no se reconoce...(pero solo si aparece como ORDEN)
...o si faltan parámetros obligatorios para una orden específica. En esete caso se invoca justo desde donde se escruta la orden.
/i, /Info
Propósito: Reclama información sobre un fichero comprimido.
Razonablemente exige que la ruta recibida sea un fichero de tipo ".comp"
Parámetros adicionales para esta orden:
/r::ruta /Ruta::ruta
Obligatorio, Especifica la ruta de entrada, debe ser un fichero de tipo '.comp'...
Ejemplo: /r::c:/Fotos/primavera 2020/primavera.comp
/k, /Comment, /Comentario
Opcional, Señala que se muestre el comentario del archivo si lo tiene.
/t, /Test, /v, /Verificar, /Verify
Propósito: Reclama verificar si el archivo está bien o resulta dañado.
Puede solicitarse distintos niveles de verificación.
Parámetros adicionales para esta orden:
/r::ruta /Ruta::ruta
Obligatorio, Especifica la ruta de entrada, debe ser un fichero de tipo '.comp'...
Ejemplo: /r::c:/Fotos/primavera 2020/primavera.comp
/n::numero /Nivel::numero
Opcional, señala el nivel de verificación que se lleva a cabo. Por defecto, solo evalúa la cabecera.
/l, /Listar
Propósito: Reclama listar el contenido de un fichero comp.
La ruta de entrada es obligatoria, el separador y la ruta de destino opcionales (si no se acompaña de ruta de destino, se vuelca en la pantalla, si la trae se vuelca al fichero).
/r::ruta /Ruta::ruta
Obligatorio, Especifica la ruta de entrada, debe ser un fichero de tipo '.comp'...
Ejemplo: /r::c:/Fotos/invierno 2020/invierno.comp
/d::ruta, /Destino::ruta
Opcional, Especifica una ruta de destino donde volcar el listado. No debe existir.
Ejemplo: /r::c:/Fotos/invierno 2020/invierno.txt
/s::string, /separador::string
Opcional, Separador usado para generar el listado, si se omite se usa 'CR+LF'
/e, /Extraer, /Extract
Propósito: Reclama la extracción de todo o parte del contenido.
/r::ruta /Ruta::ruta
Obligatorio, Especifica la ruta de entrada, debe ser un fichero de tipo '.comp'...
Ejemplo: /r::c:/Fotos/invierno 2020/invierno.comp
/d::carpeta, /Destino::carpeta
Obligatorio, Especifica una Carpeta de destino donde extraer el contenido. No debe existir (no sobrescribe).
Ejemplo: /r::c:/Fotos/invierno 2020/
/p::patron, /Pattern::patron, /Patron::patron
Opcional, especifica un patrón que señala que se va a extraer.
Ejemplo: /p::*.jpg (apunta a todas las imágenes jpg)
Ejemplo: /p::*vacaciones*.jpg /apunta las imágenes jpg, que contengan 'vacaciones' en el nombre)
Ejemplo: /p::-20 (extrae las 20 últimos ficheros, si hay menos pués todos).
/a, /Add, /Añadir
Proposito: Reclama comprimir y añadir ficheros a un archivo ya existente.
Podría llevar más parámetros para indicar otras opciones, pero es solo un ejemplo...
/r::ruta /Ruta::ruta
Obligatorio, Especifica la ruta de entrada, debe ser un fichero de tipo '.comp'...
Ejemplo: /r::c:/Fotos/invierno 2020/invierno.comp
/f::ruta /File::ruta, /Fichero::ruta
Obligatorio, Especifica la ruta del fichero a comprimir, debe existir...
Ejemplo: /r::c:/Fotos/invierno 2020/vacaciones en Marte 1254.jpg
/t::numero, /Type::numero, /Tipo::numero
Opcional, Determina que tipo de compresión quiere usarse... (un valor 0, no comprime solo archiva)
/c, /Compress, /Comprimir
Propósito: Reclama crear un nuevo fichero comprimido, partiendo de la ruta recibida.
Puede ser un fichero o una carpeta. Si es una carpeta, comprime (supuestamente) todos los ficheros en la raíz de dicha carpeta (por defecto), y un parámetro adicional señala si comprime recursivamente.
Es común que un archivo comprimido pueda incorporar un comentario, pero no es normal que dicho comentario esté contenido sobre el registro (sería el mismo para todos los archivos). Pero si podría crearse otra acción para investigar si un archivo *.comp no tiene comentario y en tal caso abrir una ventanita para añadirle uno... pero.... es solo un ejemplo.
/r::ruta, /Ruta::ruta
Obligatorio. Especifica la ruta de entrada, donde está el fichero/s que se van a comprimir (puede ser un fichero o una carpeta).
Ejemplo: /r::c:/Fotos/invierno 2020/
Ejemplo: /r::c:/Fotos/invierno 2020/vacaciones en Marte 1254.jpg
/t::x, /Type::x, /Tipo::x
Opcional, Determina que tipo de compresión quiere usarse...
/n, /Nest, /Recursivo, /Anidado
Opcional, Si es una carpeta, indica si también se comprimirá todo el contenido de las subcarpetas que contenga.
Y listo... ese será todo lo que el programa habrá de reconocer... 7 órdenes-acciones distintas, cada una con sus propios parámetros, en el registro habran de introducirse todas esas accciones... muchas irán solamente sobre ficheros *.comp, y otras sobre cualquier fichero y carpeta...
Ahora pasamos a la parte del código... yo había elegido windows forms, pero como al final no se hará con la interfaz, lo cambié a una aplicación de consola y como bien dice Elektro, no estoy muy puesto en WPF.
WPF lo veo más bien como la evolución dentro de las empresas de 'superespecialización', donde al final habrá programadores especializados solo en shell, solo en menús, solo en interfaces, solo en red, solo en seguridad, solo en... yo soy contrario a ese ideal empresarial, creo que un programador debe ser todoterreno, completo, no localizado en una porción minúscula dentro de un región, dentro de... y debe saber de casi todo, dominar al menos varios temas (todos es imposible), y tener amplios y profundos conocimientos en otros varios temas más, aunque también habrá otros temas que a uno no le parezcan nada atractivos y le queden lejanos... es razonable.
También te aclaro, que el espacio de nombre My (en Visual Basic), ofrece mayor funcionalidad de cara a operar con la línea de comandos que recibe el programa, sin embargo... cuanto a menor nivel de objetos se dé el ejemplo, más fácil será de trasladar a cualquier otro lenguaje, especialmente si no se tiene gran conocimiento de los objetos específicos de un lenguaje... así que predomina el uso del string... y los pocos métodos que uso son autoentendibles aunque varíen ligeramente en otro lenguaje.
He elegido una aplicación de consola, toda vez que no vamos a hacer nada con una interfaz, si se plasma el código en una aplicación windows forms, el código sobre Main, debe trasladarse al Sub New del form1, y con lo mismo las llamadas a 'console.writeline' pueden ser derivados hacia 'messagebox.show()'... y listo...
Por lo demás mientras se prueba mejor en modo debug, y meter la línea de comandos sobre "opciones de inicio, en la ficha "depurar" de la ventana de "Propiedades del proyecto" (el punto donde se localice puede variar según la versión de VS y lenguaje específico que uno tenga, pero vamos bucenado sobre el IDE, debe uno acabar por encontrarlo. Adjuntaré imágenes de todos modos...
Código
Sub Main() ' Si no hay línea de comandos, y fuera un programa con interfaz gráfica, debería abrir la aplicación por la ventana por defecto... If (Command.Length > 0) Then Call ProcesarLineaDeComandos() ' si fuera una aplicación de windows forms, 'End (y retirar el END de más abajo). End If End En sub
En Main, lo dejamos claro y simple... si la línea d ecomandos ha recibido algo, se manda procesar y al término da por finalizada la sesión.
Ahora el código en dicha función que es la que evalúa todo el trabajo de proceso de la línea de comandos (pero no ejecuta ninguna orden ahí, cada orden se deriva a la función específica...
Básicamente esta es la función de la que te debes empapar bien... y tratar de comprenderla al 100%.
Date cuenta que se podría optimizar mucho, pero posiblemente a riesgo de quedar más oscuro para los que no dominen VB... así creo que incluso alguien que apenas sepa nada de Vb, sería capaz de entenderlo todo o casi todo...
Código
Como se puede ver cada acción realiza básicamente lo mismo... chequea cada parámetro aparecido en un bucle donde se pone como trampa cada parámetro que dicha acción admite, si aparece toma el valor que contenga si aparece un parámetro 'extraño' qued aignorado...
Private Sub ProcesarLineaDeComandos() Dim sepCommand(0) As String Dim cmd() As String Dim SubParam() As String Dim k As Integer, n As Integer = 0 Dim Param As String, Valor As String = "" Dim faltaParam As String = "No se puede procesar el comando. Falta uno o más parámetros obligatorios " Dim rutaOrg As String = "", rutaDst As String = "" Dim separador As String = vbCrLf, Comentario As String = "", patron As String = "" Dim nivel As Integer = 0, tipo As Integer = 0 Dim tComentario As Boolean = False, recursivo As Boolean = False sepCommand(0) = " /" If Command.StartsWith(" ") = False Then Param = " " & Command() Else Param = Command() End If cmd = Param.Split(sepCommand, StringSplitOptions.RemoveEmptyEntries) sepCommand(0) = "::" ' Puede limitarse los parámetros a usar un único carácter en vez de todo un texto, remplazando ' las líneas: If (Param.Contains(sepCommand)) Then ' por la siguiente If (Param.Length > 1) Then Param = cmd(0).ToUpper Select Case Param ' la orden, se exige que sea el primer parámetro, y no lleva valor detrás. Case "I", "INFO" For k = 1 To cmd.GetUpperBound(0) Param = cmd(k).ToUpper If (Param.Contains("::")) Then SubParam = Param.Split(sepCommand, StringSplitOptions.RemoveEmptyEntries) If (SubParam.Length > 1) Then Param = SubParam(0) : Valor = SubParam(1) Else Param = "-" End If End If Select Case Param Case "R", "RUTA" rutaOrg = Valor Case "K", "COMMENT", "COMENTARIO" tComentario = True End Select Next ' Esta orden, contiene todo lo necesario? If (rutaOrg.Length > 0) Then Call Informar(rutaOrg, tComentario) Else Call ShowAyuda(faltaParam & " /Ruta, es obligatorio para /Info") End If Case "L", "LISTAR", "LIST" For k = 1 To cmd.GetUpperBound(0) Param = cmd(k).ToUpper If (Param.Contains("::")) Then SubParam = Param.Split(sepCommand, StringSplitOptions.RemoveEmptyEntries) If (SubParam.Length > 1) Then Param = SubParam(0) : Valor = SubParam(1) Else Param = "-" End If End If Select Case Param Case "R", "RUTA" rutaOrg = Valor Case "D", "DESTINO" rutaDst = Valor Case "S", "SEPARADOR" separador = Valor End Select Valor = "" Next ' Esta orden, contiene todo lo necesario? If (rutaOrg.Length > 0) Then Call ListarContenido(rutaOrg, separador, rutaDst) Else Call ShowAyuda(faltaParam & " /Ruta, es obligatorio para /Listar") End If Case "T", "V", "TEST", "VERIFY", "VERIFICAR" For k = 1 To cmd.GetUpperBound(0) Param = cmd(k).ToUpper If (Param.Contains("::")) Then SubParam = Param.Split(sepCommand, StringSplitOptions.RemoveEmptyEntries) If (SubParam.Length > 1) Then Param = SubParam(0) : Valor = SubParam(1) Else Param = "-" End If End If Select Case Param Case Is = "R", "RUTA" rutaOrg = Valor Case "N", "NIVEL" nivel = Integer.Parse(Valor) End Select Next ' Esta orden, contiene todo lo necesario? If (rutaOrg.Length > 0) Then Call Verificar(rutaOrg, nivel) Else Call ShowAyuda(faltaParam & " /Ruta, es obligatorio para /Verificar") End If Case "E", "EXTRAER", "EXTRACT" For k = 1 To cmd.GetUpperBound(0) Param = cmd(k).ToUpper If (Param.Contains("::")) Then SubParam = Param.Split(sepCommand, StringSplitOptions.RemoveEmptyEntries) If (SubParam.Length > 1) Then Param = SubParam(0) : Valor = SubParam(1) Else Param = "-" End If End If Select Case Param Case "R", "RUTA" rutaOrg = Valor Case "D", "DESTINO" rutaDst = Valor Case "P", "PATTERN", "PATRON" patron = Valor End Select Next ' Esta orden, contiene todo lo necesario? If ((rutaOrg.Length > 0) And (rutaDst.Length > 0)) Then Call Extraer(rutaOrg, rutaDst, patron) Else Call ShowAyuda(faltaParam & " /Ruta o /Destino (ó ambas) son obligatorias para /Extraer uno o más ficheros") End If Case "C", "COMPRIMIR", "COMPRESS" For k = 1 To cmd.GetUpperBound(0) Param = cmd(k).ToUpper If (Param.Contains("::")) Then SubParam = Param.Split(sepCommand, StringSplitOptions.RemoveEmptyEntries) If (SubParam.Length > 1) Then Param = SubParam(0) : Valor = SubParam(1) Else Param = "-" End If End If Select Case Param Case "R", "RUTA" rutaOrg = Valor Case "T", "Tipo", "TYPE" tipo = Valor Case "N", "RECURSIVO", "ANIDADO", "NEST" recursivo = True End Select Next ' Esta orden, contiene todo lo necesario? If (rutaOrg.Length > 0) Then Call Comprimir(rutaOrg, tipo, recursivo) Else Call ShowAyuda(faltaParam & " /Ruta, es obligatorio para /Comprimir") End If Case "A", "ADD", "AÑADIR" For k = 1 To cmd.GetUpperBound(0) Param = cmd(k).ToUpper If (Param.Contains("::")) Then SubParam = Param.Split(sepCommand, StringSplitOptions.RemoveEmptyEntries) If (SubParam.Length > 1) Then Param = SubParam(0) : Valor = SubParam(1) Else Param = "-" End If End If Select Case Param Case "R", "RUTA" rutaOrg = Valor Case "F", "FILE", "FICHERO" rutaDst = Valor Case "T", "Tipo", "TYPE" tipo = Valor End Select Next ' Esta orden, contiene todo lo necesario? If ((rutaOrg.Length > 0) And (rutaDst.Length > 0)) Then Call Añadir(rutaOrg, rutaDst, tipo) Else Call ShowAyuda(faltaParam & " /Ruta o /Fichero (ó ambas) son obligatorias para /Añadir un nuevo fichero al archivo comprimido.") End If Case "H", "?", "HELP", "AYUDA" Call ShowAyuda("") Case Else ' una orden con la que el programa no tiene 'compromiso' (quizás si en una versión más actual). Call ShowAyuda("No se reconoce el comando recibido (para la versión actual), se muestra la forma correcta de invocar el programa...") End Select End Sub
Luego se verifica que los parámetros obligatorios para la orden solicitada existan todos, si no se aborta y se lanza el 'manual' de la ayuda...
Si todo va bien, se invoca la función que ha de ejecutar la acción, junto con todos los parámetros.
OJO: Para parámetros opcionales, siempre debe inicializarse a un valor por defecto, de modo que si tal parámetro no comparece, sea el valor por defecto el que recibe la función.
A veces la aparición de un parámetro inutiliza el valor de otro, es decir hay parámetros con prioridad... en el ejemplo solo se dan 2 casos, uno con la ruta para la acción 'Comprimir', que permite añadir un único fichero o todos los que tenga una carpeta...
El otro caso se da en listar, en teoría se volcará el listado por consola, pero si aparece un parámetro de ruta de destino para un fichero, se vuelva la salida a dicho fichero...
A veces se dan casos, donde un parámetro que recibe un valor siguen siendo válido el parámetro pero otro parámetro con más prioridad invalida su valor... es un caso más complejo, y esos casos se deben tratar mejor ya dentro de la función que ha de ejecutar la acción.
Solo queda añadir las funciones (de pega, la única a medio completar es la de ayuda), para que haciendo un copy-paste, y modificando lo que proceda (si se adapta a otro lenguaje), pueda ejecutarse hasta el crítico momento de entrar a la función que acabaría realizando la ejecución de la acción solicitada.
Código
#Region "Procesado de Ordenes..." ''' <summary> ''' Orden ayuda (o cuando hay error en los parámetros de entradas). ''' </summary> ''' <param name="TextoExtra"></param> ''' <remarks></remarks> Private Sub ShowAyuda(ByRef TextoExtra As String) If (TextoExtra.Length > 0) Then Console.WriteLine(TextoExtra & vbCrLf) Console.WriteLine(Strings.StrDup(80, "=")) Console.WriteLine("") End If Console.WriteLine("Uso:" & vbTab & "compresor.exe /orden <resto de parametros para dicha orden>") Console.WriteLine(Strings.StrDup(80, "=")) Console.WriteLine("") Console.WriteLine("Ordenes disponibles: Añadir, Ayuda, Extraer, Comprimir, Info, Listar, Verificar, ") Console.WriteLine(" Comprimir: Crea un nuevo fichero comprimido, partiendo de la ruta recibida. Fichero o carpeta.") Console.WriteLine(" Parametros: /c /r:ruta [/t:x] [/n]") Console.WriteLine(" /c" & vbTab & "Orden para comprimir (tambien puede indicarse /Compress o /Comprimir)") Console.WriteLine(" /r:ruta" & vbTab & "Obligatorio. Especifica la ruta de entrada, donde esta el fichero/s que se van a comprimir (puede ser un fichero o una carpeta).") Console.WriteLine(" /t:numero" & vbTab & "Opcional. Determina que tipo de compresion quiere usarse... (un valor 0, no comprime solo archiva)") Console.WriteLine(" /n" & vbTab & "Opcional. Si es una carpeta, indica si tambien se comprimira todo el contenido de las subcarpetas que contenga.") Console.WriteLine("") Console.WriteLine(" Listar: Enumera el contenido de un fichero comp. Si no se indica una ruta de destino, se vuelca en la pantalla.") Console.WriteLine(" Parametros: /l /r:ruta [/d:ruta] [/s:string]") Console.WriteLine(" /l" & vbTab & "Orden para listar, también se acepta /List y /Listar") Console.WriteLine(" /r:ruta" & vbTab & "Obligatorio. Especifica la ruta de entrada, debe ser un fichero de tipo '.comp'...") Console.WriteLine(" /d:ruta" & vbTab & "Opcional. Especifica una ruta de destino donde volcar el listado. No debe existir.") Console.WriteLine(" /s:separador" & vbTab & "Opcional. Separador usado para generar el listado, si se omite se usa 'CR+LF'.") Console.WriteLine("") Console.WriteLine(" Ayuda: Muestra detalles del uso de la línea de comandos del programa.") Console.WriteLine(" Parametros: /h") Console.WriteLine(" /h" & vbTab & "Orden para mostrar este mensaje de ayuda (también se acepta /? /Help y /Ayuda)") Console.WriteLine("") Console.WriteLine("etc... para otras ordenes.") End Sub ''' <summary> ''' Orden para ofrecer información sencilla sobre un archivo comprimido. ''' </summary> ''' <param name="Ruta"></param> ''' <param name="Comentario"></param> ''' <remarks></remarks> Private Sub Informar(ByVal Ruta As String, ByVal Comentario As Boolean) Dim fsI As IO.FileStream If GetFileStreamSiEsFormatoValido(Ruta, fsI) = True Then ' leer cabecera, tomar ciertos datos informativos ' y mostrarlos en alguna ventanita... ' versión del programa con que fue creada el archivo, cuantos ficheros contiene ' tamaño del archivo y tamaño que tendría el contenido descomprimido...etc... If Comentario = True Then ' si el archivo contiene un comentario sobre el mismo, tomar para mostrarlo... End If fsI.Close() : fsI = Nothing End If End Sub ''' <summary> ''' Orden listar: ''' </summary> ''' <param name="Ruta"></param> ''' <param name="Separador"></param> ''' <param name="Destino"></param> ''' <remarks></remarks> Private Sub ListarContenido(ByRef Ruta As String, ByRef Separador As String, Optional ByRef Destino As String = "") Dim fsI As IO.FileStream, fsO As IO.FileStream Dim lista() As String ' o cualquier otro objeto que facilite el añadido... según diseño del programa. If GetFileStreamSiEsFormatoValido(Ruta, fsI) = True Then ' leer la cabecera. ' mover el puntero de lectura al punto donde consta la lista de ficheros contenidos ' Ir tomando la lista de ficheros que contiene (si es una lista plana, si no ir saltando entre el contenido con el par direccion + size). ' recordar añadir el separador tras cada nombre. If (Destino.Length > 0) Then ' crear fichero y volcar el contenido. ' en este caso es preferible ir escribiendo (append), directamente al fichero mejor que añadirlo a un array para después volcarlo a fichero... '.Close() : fsO = Nothing Else ' mensaje: error, el fichero de destino ya existe, la lista debe crearse en un fichero nuevo... End If Else ' volcarla por pantalla ReDim lista(0 To 5) Console.WriteLine("Éste es el contenido del archivo: " & vbCrLf & (lista.ToString)) ', "Contenido del archivo:") End If fsI = Nothing End If End Sub ''' <summary> ''' Orden para verificar ''' </summary> ''' <param name="Ruta"></param> ''' <param name="Nivel"></param> ''' <remarks></remarks> Private Sub Verificar(ByVal Ruta As String, Optional ByVal Nivel As Integer = 0) Dim fsI As IO.FileStream If GetFileStreamSiEsFormatoValido(Ruta, fsI) = True Then ' (revisión mínima) leer cabecera y verificar que es correcta. esto siempre If RevisionMinima(fsI) And Nivel > 0 Then ' si nivel=1,(revisión simple) revisar por ejemplo que los índices de comienzo de extracción corresponden con la dirección guardada y tamaño del comprimido. ' Si nivel=2 (revisión media) podría señalar revisión completa, en tal caso (por ejemplo), calcular hash, para todo el archivo (excepto la cabecera) y ver que coincide ' con el hash guardado en la cabecera. ' Si nivel>2 (revisión completa: a todo lo previo, toca ir extrayendo de uno en uno cada fichero comprimido y calcular su hash y comprobar que coincide con el alamcenado en la estructura ligada a dicho fichero) End If ' informar de modo similar al chkdisk... por ejemplo... ' ir informando sobre la marcha, para las revisiones 2 y mayor que 2... ' informar al final con un resumen... todo ok, o falló tal cosa... End If End Sub ''' <summary> ''' Orden para extraer contenido, todo o parcial. ''' </summary> ''' <param name="Ruta"></param> ''' <param name="Destino"></param> ''' <param name="Patron"></param> ''' <remarks></remarks> Private Sub Extraer(ByVal Ruta As String, ByVal Destino As String, ByVal Patron As String) Dim fsI As IO.FileStream ' Si no existe la carpeta de destino, ni siquiera nos molestamos en abrir el archivo. ' opcionalmente podría crearse la carpeta (solo si la ruta padre para ella existe) ' pero como es un ejemplo, basta así... If IO.Directory.Exists(Destino) Then If GetFileStreamSiEsFormatoValido(Ruta, fsI) = True Then ' Evaluar el patrón: ' Si es "" implica que se extrae todo. ' Si es en la forma *.extension" ó ".extension" , se extraen todos los ficheros que coincidan con dicha extensión... ' Si es en la forma *algo* se extraen todos los ficheros que contengan ese 'algo'... ' si es en la forma 'Xnumero-', se extraen los primeros x ficheros del comeinzo del archivo. ' si es en la forma '-Xnumero', se extraen los últimos x ficheros del final del archivo. ' si es en la forma 'Ynumero-Xnumero', se extren los x ficheros a partir de yº fichero comprimido. ' si es en la forma 'nombre.extension' se extrae el fichero cuyo nombre coincide (si existe). ' también podrían indicarse patrones por fecha..."mm/dd/AAAA' por ejemplo, para extrer los ficheros cuya fecha de compresión/creacion/modificacion/etc... coincidan con la indicada... ' incluyendo fechas parciales como indicar solo el mes o año, o un rango entre fechas... ' etc... cosa de imaginación. ' informar al final de la cantidad de ficheros extraídos... y abrir la carpeta donde se extrajo (resulta útil). End If End If End Sub ''' <summary> ''' Orden para comprimir un fichero y añadirlo al archivo comprimido. ''' </summary> ''' <param name="Ruta"></param> ''' <param name="Fichero"></param> ''' <remarks></remarks> Private Sub Añadir(ByVal Ruta As String, ByVal Fichero As String, ByVal Tipo As Integer) ' verifica que el fichero comprimido existe (se supone que sí) y el de origen ' luego Más de lo mismo, comprime el fichero, añadiéndolo al final del archivo, ' y actualiza la cabecera, cantidad de ficheros, y hash de la cabecera. ' etc.. '... End Sub ''' <summary> ''' Orden para crear un nuevo archivo comprimido (no debe existir) ''' </summary> ''' <param name="Ruta"></param> ''' <param name="Tipo"></param> ''' <param name="Recursivo"></param> ''' <remarks></remarks> Private Sub Comprimir(ByVal Ruta As String, ByVal Tipo As Integer, Optional ByVal Recursivo As Boolean = False) Dim nombre As String Dim fsO As IO.FileStream Dim fi As IO.FileInfo, di As IO.DirectoryInfo Dim puntero As Int64 Dim numfiles As Integer ' no sabemos a priori si origen es la ruta a un fichero o a una carpeta ' si es una carpeta, se comprimirá todo su contenido. If IO.Directory.Exists(Ruta) Then di = FileIO.FileSystem.GetDirectoryInfo(Ruta) nombre = (Ruta & "\" & di.Name & ".comp") puntero = ComprimirAnidado(fsO, Ruta, numfiles, Tipo, Recursivo) ElseIf FileIO.FileSystem.FileExists(Ruta) Then fi = FileIO.FileSystem.GetFileInfo(Ruta) nombre = Ruta.Replace(fi.Extension.ToUpper, ".comp") puntero = ComprimirFichero(fsO, Tipo, Ruta) numfiles = 1 Else ' error ya existe el archivo de salida, debe crearse uno nuevo... no puede existir. Exit Sub End If End If If puntero = -1 Then ' mensaje: ha fallado la compresión para el fichero 'nombre' ' se puede solicitar ignorar y continuar, o bien abortar, ' como es un ejemplo se aborta, pero se informa... 'fsO.Close() ' eliminar archivo. Else ' mensaje: archivo creado... total de 'numfiles' comprimidos... End If fsO = Nothing End Sub #End Region
Y finalmente otras pequeñas funciones (tamvbién de 'papel-cartón') que aparecen invocadas en el código, para que no cante error...
Código
#Region "Las funciones que realizan todo el trabajo (ejecutan las órdenes)" Private Function ComprimirAnidado(ByRef Fs As IO.FileStream, ByRef Carpeta As String, ByRef NumFiles As Integer, ByVal Tipo As Integer, ByRef Recursivo As Boolean) As Int64 Dim fi As IO.FileInfo, di As IO.DirectoryInfo, d As IO.DirectoryInfo Dim puntero As Int64 di = FileIO.FileSystem.GetDirectoryInfo(Carpeta) puntero = ComprimirFichero(Fs, Tipo, fi.FullName) If (puntero = -1) Then Return -1 Else ' puntero se usa como dirección, punto de inicio de la compresión del siguiente fichero ' para la estructura del mismo NumFiles += 1 End If Next If (Recursivo = True) Then puntero = ComprimirAnidado(Fs, d.FullName, NumFiles, Tipo, Recursivo) If puntero = -1 Then Return -1 End If Next End If Return Fs.Position End Function Private Function ComprimirFichero(ByRef Fs As IO.FileStream, ByVal Tipo As Integer, ByRef Fichero As String) As Int64 Dim fsI As IO.FileStream ' según el tipo de compresión debería derivarse a la función específica... '...crear un búffer de lectura, y sobre un bucle ir leyendo y comprimiendo y volcando al destino. ' al tiempo ir calculando un hash para sobre el buffer. ' escribir los datos de cabecera de info del fichero (tamaño original, hash, fechas, etc...). ' actualizar en destino ' si falla devolver -1. Return Fs.Position End Function Private Function GetFileStreamSiEsFormatoValido(ByRef Ruta As String, ByRef fichero As IO.FileStream) As Boolean Try ' lo(abre), lee la cabecera para verificar que es del tipo esperado ' y quizás un breve chequeo de que no está corrupto... ' Si todo conforme devuelve el filestream y Return True ' si no conforme ' fichero.Close() : fichero = Nothing ' mensaje: el fichero 'ruta' no es del formato que éste programa está capacitado para tratar. ' o mensaje: el fichero 'ruta', es del tipo esperado, pero parece estar dañada la cabecera. ' o mensaje: el fichero 'ruta' es del tipo esperado, pero la versión que señala no puede ser atendida por la versión actual del programa, por favor actualice a una versión más actual." ' return false Catch ex As Exception Console.WriteLine("Ha ocurrido un error no controlado:" & vbCrLf & ex.Message) Return False End Try Else 'si no existe: ' mensaje: el fichero 'ruta' no parece existir (se ha movido, eliminado, renombrado?), o quizás no tenga permisos suficientes Return False End If End Function ' Trata de ver que como mínimo la cabecera no está corrupta ' (ya se sabe previamente que es del tipo adecuado y puede leerse) Private Function RevisionMinima(ByRef FS As IO.FileStream) As Boolean ' leer la cabecera y verificar que esta bien... ' en general primero se computa un hash a la cabecera y luego se revisa con el hash contenido en la misma cabecera. a estos efectos, el propio hash no debe formar parte de ese cómputo, por lo que suele ponerse al final de la cabecera. Return True End Function #End Region
Y listo, debiera ser suficiente (si no soy muy optimista), para que te sirva de aprendizaje e inspiración...
Me queda eso si, poner alguna imagen y el contenido de un fichero reg, que guardara todas las acciones en el registro... aún así, para probarlo puede hacerse sin shell, haciendo uso de scripts... algún bat-cmd, por ejemplo. Aunuqe para eso entonce shay que compilarlo.
Si desde debug, se meten las opciones de línea de comando se puede probar "paso a paso" (en vbnet es la tecla F11, no recuerdo ahora mismo cual es la de C#).