Planteamos el siguiente escenario. Tenemos nuestra aplicación VFP que es un ejecutable llamado main.exe.
Este reside en los 40 puestos de trabajo de la red de nuestro cliente y accede a una base de datos (no importa cual) en su servidor central.
Ok, esta es un arquitectura tipo, pero que pasa cuando liberamos una nueva versión de nuestro software y hay que distribuirla a los 40 puestos de trabajo ?
Soluciones hay varias, en esta entrega les mostramos como actualizar nuestra aplicación directamente de Internet sin intervención del usuario, algo similar a como trabaja (salvando las distancias) un antivirus.
El primer truco
Lo primero que debemos hacer es partir nuestra aplicación en dos partes, una que lance a main.exe y antes controle la versión y si es necesario la actualice automáticamente. Por que esto ? simplemente por que cuando nuestro ejecutable este cargado en memoria no lo podremos sobrescribir con la nueva versión.
Una idea es renombrar a nuestro main.exe a main.bin, esta continuará siendo nuestra aplicación "camuflada" y usaremos un programa para lanzar a main.bin
También podríamos hacer de nuestra aplicación una DLL, pero no veremos este tema aquí.
Gestionando las versiones
Lo primero que tenemos que hacer es compilar nuestra aplicación con un versionado. Esto es vital para poder controlar que versión de nuestra aplicación estamos usando.
Luego compilamos normalmente nuestro proyecto y renombramos el archivo resultante de main.exe a main.bin
Controlando el número de versión
Si hacemos click derecho en el explorador de Windows al archivo main.bin veremos dentro de sus propiedades, la versión. No se olviden que continua siendo un archivo ejecutable y por eso se puede leer su cabezal y tomar estos datos.
¿Como leer estos datos desde VFP?
Para los que usan la versión 9 usen la función AGETFILEVERSION(miArray,"main.bin")
Los que que tiene VFP8 o menor, pueden usar la función GetFileVersion(miArray,"main.bin") que esta incluida en la librería FOXTOOLS.FLL que viene con el VFP.
La estrategia de actualización
Ahora que manejamos los versionados de nuestra aplicación veamos como implementar esta solución.
Hablamos de actualizar automáticamente desde Internet (vamos a ver mas adelante que pasa si no tenemos Internet) , por lo cual debemos minimizar el tráfico de información. Para eso tendremos dos archivos: la nueva versión de main.bin colgada en nuestro sitio de Internet y un archivo con formato XML llamado MainInfo.xml que tiene la siguiente estructura:
<?xml version="1.0" encoding="Windows-1252" standalone="yes" ?> <VFPData> <MainInfo> <ver>3.5.2</ver> <date>2006-06-03</date> <url>http://www.c-dev.net/Test/update.htm</url> <status>00</status> </MainInfo> </VFPData
- ver: es el número de versión de nuestra aplicación que esta disponible para actualizar
- date: es la fecha en que fue liberada
- url: es la página web que tiene toda la historia de cambios de nuestra aplicación
- status: es un código numérico que indica el estado de la actualización, por ejemplo 0=normal, 1=crítico, etc, sirve para que podamos tomar una descición al actualizar, lo obligo al usuario a descargar la nueva versión o puede trabajar con una versión mas antigua.
Creando nuestro programa lanzador de main.bin
La idea básicamente es llamar a nuestro programa lanzador que lo llamaremos SuperMain, que este siga los siguientes pasos:
- Instancie la clase que tiene toda la lógica de actualización automática
- Cargue las propiedades de esta clase.
- Controle que versión esta usando el usuario actualmente de main.bin
- Descargue el archivo XML con información de la versión mas reciente que haya.
- Compare ambas versiones y si la actual es menor, comience la descarga desde Internet automáticamente del nuevo main.bin
- Si no hubo problemas de descarga, llamar a nuestra aplicación (main.bin)
1) Si no tenemos acceso a Internet, le podemos cambiar el parámetro de la función open del objeto XMLhttp. Esta se carga en las propiedades oVersion.cNewBinURL y oVersion.cXMLinfoURL
Por ejemplo si el archivo a actualizar reside en el servidor central de la empresa podría quedar de la siguiente manera:
oVersion.cNewBinURL = "file://Servidor/Downloads/main.bin" oVersion.cXMLinfoURL = "file://Servidor/Downloads/MainInfo.xml"note que cambiamos el protocolo http por el protocolo file
2) En este ejemplo cambiamos la extensión de nuestra aplicación de .EXE a .BIN por dos cosas, una, para diferenciar claramente que no se pude ejecutar directamente y la otra es para prevenir trancas en la descarga en caso de que un firewall impida descargar archivos ejecutables.
También se podría compilar nuestra aplicación principal como un archivo DLL de tipo COM server, pero no era el objetivo de esta entrega.
SuperMain.prg
LOCAL oVersion, lnNewVer, lnCurrentVer, lcCurrentVer, lnStatus oVersion = NEWOBJECT("ControlVer","ControlVer.prg") oVersion.cMyBinFileName = "MAIN.bin" oVersion.cXMLinfoUpdate = "MainInfo.XML" oVersion.cNewBinURL = "http://c-dev.net/Test/MAIN.bin" oVersion.cXMLinfoURL = "http://c-dev.net/Test/MainInfo.XML" IF oVersion.getUpdateInfo() oVersion.ReadUpdateInfo() lnNewVer = VAL(STRTRAN(oVersion.getNewBinInfo(),".","")) lnCurrentVer = VAL(STRTRAN(oVersion.getMyBinInfo(),".","")) lnStatus = oVersion.getStatus() IF lnNewVer > lnCurrentVer ? "Existe una nueva versión Y su STATUS es: " + TRANSFORM(lnStatus) ? "Ver actual " + oVersion.getNewBinInfo() ? "Nueva versión " + oVersion.getMyBinInfo() ? "Descargando nueva versión...." IF oVersion.downloadUpdate() ? "Actualizo correctamente" ELSE ? "ERROR. No pudo actualizar" ENDIF ELSE ? "No hay actualizaciones" ENDIF ENDIF IF FILE("MAIN.bin") RENAME MAIN.bin TO MAIN.EXE DO MAIN.EXE RENAME MAIN.EXE TO MAIN.bin ENDIF
La lógica de actualización
Esta la escribimos en una clase llamada ControlVer.prg y el código es el siguiente:
DEFINE CLASS ControlVer AS CUSTOM PROTECTED cMyBinVersion AS STRING && STRING con la versión del bin actual (myBin) PROTECTED cNewBinVersion AS STRING && STRING con la versión del nuevo bin (newBin) PROTECTED dNewBinDate AS DATE && fecha de actualización del nuevo bin PROTECTED nStatusUpdate AS INTEGER && estado de la actualización (0=NORMAL, 1=crítica) cMyBinFileName = "" && nombre del archivo que buscaremos actualizar, ej: MAIN.bin cXMLinfoUpdate = "" && nombre del archivo XML con información de la nueva versión cNewBinURL = "" && URL del archivo a descargar, && ej: http://miservidor/downloads/MAIN.bin cXMLinfoURL = "" && URL del archivo XML con INFO sobre la nueva versión a descargar, && ej: http://miservidor/downloads/udMain.XML cURLhistoryInfo = "" && URL con el historial de cambios del archivo MAIN PROCEDURE INIT THIS.cMyBinVersion = "" THIS.cNewBinVersion = "" THIS.dNewBinDate = {} THIS.cNewBinURL = "" THIS.nStatusUpdate = 0 SET SAFETY OFF SET TALK OFF ENDPROC *[ descargamos a nuestro disco del archivo XML *[ con la información del bin para actualizar *[ de esta manera minimizamos el tráfico y solo *[ descargamos el nuevo bin solo si es necesario FUNCTION getUpdateInfo() AS Boolean LOCAL llRet LOCAL loXMLHTTP AS "MSXML2.XMLHTTP" loXMLHTTP = CREATEOBJECT("MSXML2.XMLHTTP") llRet = .T. TRY loXMLHTTP.OPEN("GET", THIS.cXMLinfoURL, .F.) loXMLHTTP.SEND() STRTOFILE(loXMLHTTP.responseBody,THIS.cXMLinfoUpdate) CATCH TO loErr MESSAGEBOX("ERROR en getUpdateInfo: " + loErr.MESSAGE,64,"ERROR") llRet = .F. ENDTRY RETURN llRet ENDFUNC *[ una vez que bajamos el XML lo leemos para *[ asignar a las propiedad de esta clase *[ la información sobre el bin a actualizar PROCEDURE ReadUpdateInfo XMLTOCURSOR(THIS.cXMLinfoUpdate,"cur_Info",512) SELECT cur_Info THIS.cNewBinVersion = cur_Info.ver THIS.dNewBinDate = cur_Info.DATE THIS.cURLhistoryInfo = cur_Info.url THIS.nStatusUpdate = cur_Info.STATUS USE IN cur_Info ERASE (THIS.cXMLinfoUpdate) ENDPROC *[ Obtiene la versión del archivo main.bin actual FUNCTION getMyBinInfo() AS STRING LOCAL laFile[1] AGETFILEVERSION(laFile, THIS.cMyBinFileName) THIS.cMyBinVersion = laFile[4] RETURN laFile[4] ENDPROC *[ descarga a nuestro disco el archivo a actualizar FUNCTION downloadUpdate AS Boolean LOCAL llRet LOCAL loXMLHTTP AS "MSXML2.XMLHTTP" loXMLHTTP = CREATEOBJECT("MSXML2.XMLHTTP") llret = .T. TRY loXMLHTTP.OPEN("GET", THIS.cNewBinURL,.F.) loXMLHTTP.SEND() STRTOFILE(loXMLHTTP.responseBody, THIS.cMyBinFileName) CATCH TO loErr MESSAGEBOX("ERROR en downloadUpdate: " + loErr.MESSAGE + CHR(13) + ; "# " + STR(loErr.ErrorNo) + CHR(13) + ; "LINE: " + STR(loErr.LINENO),64,"ERROR") llRet = .F. ENDTRY RETURN llRet ENDPROC FUNCTION getNewBinInfo() AS STRING RETURN THIS.cNewBinVersion ENDFUNC FUNCTION getStatus() AS STRING RETURN THIS.nStatusUpdate ENDFUNC ENDDEFINE
no esta el link en linea donde puedo bajar el archivo
ResponderBorrarDIMENSION verificarversionlocl(1000,100)
BorrarDIMENSION verificarversionsrvr(1000,100)
AGETFILEVERSION(verificarversionlocl,"C:\directorio\aplicacion.exe")
AGETFILEVERSION(verificarversionsrvr,"\\servidor\directorio\aplicacion.exe")
IF verificarversionlocl(1,4) <> verificarversionsrvr(1,4)
CLEAR
?'Versión Local: ',verificarversionlocl(1,4)
?'Versión Servidor: ',verificarversionsrvr(1,4)
MESSAGEBOX('Actualizar Versión para continuar...',0+16+0,'')
QUIT
ENDIF
yo a mi manera lo he hecho del siguiente modo, así controlo que los usuarios de la red puedan utilizar la ultima versión del sistema y que todos obtengan los reportes que necesitan, y lo mas importante que todos sean iguales:
ResponderBorrarDIMENSION verificarversionlocl(1000,100)
DIMENSION verificarversionsrvr(1000,100)
AGETFILEVERSION(verificarversionlocl,"EJECUTABLE A COMPARAR PUEDE ESTAR EN UNA UNIDAD DEL DISCO DURO LOCAL")
&& Ejemplo: c:\app\aplication.exe
AGETFILEVERSION(verificarversionsrvr,"\\EJECUTABLE A COMPARAR EN EL SERVIDOR DE LA RED DONDE SE TRABAJA ACTUALMENTE")
&& Ejemplo: \\srvr\app\aplication.exe
IF verificarversionlocl(1,4) <> verificarversionsrvr(1,4)
CLEAR
?'Versión Local: ',verificarversionlocl(1,4)
?'Versión Servidor: ',verificarversionsrvr(1,4)
MESSAGEBOX('Actualizar Versión para continuar...',0+16+0,'')
QUIT
ENDIF