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