Hace unos días me vi en la necesidad de implementar a uno de mis clientes acceso a google maps y a la creación de eventos en google calendar. El tema de los mapas lo pude resolver fácilmente gracias a la estupenta aportación que Bingen hizo en su momento.
Para integración del calendario no me quedó más remedio que calzarme las botas de explorador y ponerme a investigar cómo podía hacerlo. Un ratico en google y en seguida me di cuenta de que atacar a la mayoría de los servicios de google (calendario, contactos, youtube, etc..) es francamente fácil… siempre y cuando utilices .net
Xailer por un lado, .net por el otro, ¿Cómo resolverlo?
En un principio pensé en crear una aplicación de consola en VB y hacer una llamada con ShellExecute, pasándole los datos como parámetros. Lo probé y funcionaba, pero fui incapaz de hacer que el programa en VB le devolviera a Xailer si la operación había tenido éxito o no.
Necesitaba una forma en que los dos programas se comunicaran entre sí… Una especie de cliente servidor básico… ¡¡Los Sockets!!
A partir de un artículo de José Alfonso en chochurro, me puse a investigar cómo establecer comunicación entre distintas aplicaciones y aquí está el resultado.
Vamos a ver cómo podemos generar nuevas entradas en el calendario de google asociado a una cuenta de correo gmail o google apps. Además, en el caso de que tengamos un nº de teléfono móvil asociado al calendario, nos permitirá recibir alertas por sms gratuitas.
Nuestro ejemplo nos va a permitir establecer conexión desde xailer (actuando como cliente) con una aplicación diseñada en VB.net (que actúa como servidor) y cuyo único objetivo es crear un nuevo evento en google calendar de acuerdo con los datos proporcionados por el cliente (xailer) y devolver si el proceso ha tenido éxito o no.
Para el proyecto de VB necesitaremos descargar Google Data API Setup, que contine las dll de conexión necesarias para conertar VB con los servicios de Google .
Para empezar nos crearemos un formulario para capturar la información que vamos a enviar al servirdor,esto es: el usuario y su contraseña y los datos referentes a la cita que queremos crear.
Completamos el formulario con un botón para enviar la información y un pequeño TPanel en el centro del formulario, que inicialmente estará oculto y nos servirá para mostrar información al usuario cuando pulse el botón de envío.
//------------------------------------------------------------------------------
METHOD FormInitialize( oSender ) CLASS TForm1
::oSocket := ClientSock2():New( "127.0.0.1", 2000 )
::oDateEdit1:Value := Date()
::oDateEdit2:Value := Date()
::oTimePicker1:cTime := Time()
::oTimePicker2:cTime := Time()
WITH OBJECT ::oDatos := TExsTruct():New()
:AddMember( "Usuario")
:AddMember( "Clave")
:AddMember( "FechaInicio")
:AddMember( "FechaFinal")
:AddMember( "Avisar")
:AddMember( "Texto")
:AddMember( "Lugar")
:AddMember( "Comentario")
END
RETURN Nil
//------------------------------------------------------------------------------
En él inicializaremos el cliente socket para conectarnos con el servidor, los controles de fecha y hora del formulario y nos creamos una estructura de datos llamada oDatos. Esta estructura contiene la información que le pasaremos al servidor de una forma mucho más organizada que si utilizáramos datas independientes.
Como podéis ver, al crear el objeto de la clase ClientSock le decimos la dirección Ip del servidor y el puerto al que conectarnos. En nuestro ejemplo, nos conectamos a la misma máquina en la que tenemos el ejecutable xailer, pero cambiando la Ip podríamos alojar el servidor Gcal en cualquier equipo de una red, o incluso, en un servidor de internet.
Vamos a ver ahora qué ocurre cuando el usuario pulsa el botón de enviar:
//------------------------------------------------------------------------------
METHOD BtnBmp1Click( oSender ) CLASS TForm1
Local cResult
oSender:Disable()
::oPanel1:Show()
ProcessMessages()
::oSocket:Verificar()
Primero desactivamos el botón para evitar que un usuario impaciente pulse dos veces y genere dos entradas en el calendario.
A continuación, hacemos visible la pantallita de espera y forzamos un processmessages para que se actualice la pantalla.
Luego, llamamos al método verificar() de la clase Socket, que se encargará de comprabar que el servidor está instalado y funcionando.
El siguiente paso es cargar la estructura oDatos con los valores que recibirá el servidor.
WITH OBJECT ::oDatos
:Usuario := ::oEditBanner1:cText
:Clave := ::oEditBanner2:cText
:FechaInicio := dTos(::oDateEdit1:Value) + ;
Left(::oTimePicker1:cTime,2) + ;
SubStr(::oTimePicker1:cTime,4,2)
:FechaFinal := dTos(::oDateEdit2:Value) +;
Left(::oTimePicker2:cTime,2) +;
SubStr(::oTimePicker2:cTime,4,2)
:Avisar := If( Empty( ::oCombobox1:cText),"0",::oCombobox1:cText)
:Texto := ::oEdit1:Value
:Lugar := ::oEdit2:Value
:Comentario := ::oMemo1:Value
END
y enviamos la información al servidor a través del socket mediante el evento enviar(), con el identificador “N:” (de nuevo dato) y los datos de la agenda.
IF ::oSocket:Enviar("N:"+ ::oDatos:Usuario +"||"+;
::oDatos:Clave +"||"+;
::oDatos:Texto +"||"+;
::oDatos:FechaInicio +"||"+;
::oDatos:FechaFinal +"||"+;
::oDatos:Comentario +"||"+;
::oDatos:Lugar +"||"+;
::oDatos:Avisar +"¬" )
Como separador de campo usamos el código “||” . También usaremos la terminación “¬” para que el servidor seconozca el fin de la cadena transmitida
Ya solo nos queda ponernos a “escuchar” y esperar a que el servidor nos diga algo.
::oSocket:Recibir( 256 )
cResult := ::oSocket:crespuesta
If cResult = "Conectado"
cResult = ""
END IF
DO WHILE Empty( cResult )
::oSocket:Recibir( 256 )
cResult := ::oSocket:crespuesta
END
Una vez nos ha respondido, mostramos al usuario el mensaje de confirmación o de error, según el caso, y volvemos a empezar el proceso.
::oPanel1:Hide()
IF cResult = "OK"
MsgInfo("Datos enviados correctamente")
::oEdit1:Value := ""
::oEdit2:Value := ""
::oMemo1:Value := ""
ELSE
MsgInfo("Error:"+AllTrim(cResult))
ENDIF
En el caso de que la conexión ::oSocket:Enviar() falle, saltamos a este ELSE, que nos informa del error y verifica la conexión, lanzando el servidor si es preciso.
ELSE
MsgStop("Imposible enviar datos"+CRLF+"Servidor desconectado","ERROR")
::oSocket:Verificar()
END IF
Por último, sólo nos queda no olvidarnos de activar nuevamente el botón de envío, ser educados y cerrar la conexión del socket para que lo puedan utilizar otros procesos, y ceder el foco al control oDateEdit, para crear otra entrada en el calendario
oSender:Enable()
::oSocket:Cerrar()
::oDateedit1:SetFocus()
RETURN Nil
//------------------------------------------------------------------------------
Vamos a ver ahora la clase ClientSock2, que contiene algunas modificaciones que he realizado sobre la clase ClientSock original.
Fijaros que gracias a las herencia, uno de los fundamentos de la programación orientada a objetos, he podido adaptar su funcionamiento a las necesidades de mi programa sin tener que modificar una sola línea de código de la clase original
Para ello tan sólo tenemos que crear nuestra clase «heredando» (FROM) todas las características de la clase original.
CLASS ClientSock2 FROM ClientSock
A continuación indicamos los nuevos métodos (verificar) y los que se van a modificar (error), y cerramos nuestra nueva clase
METHOD Error()
METHOD Verificar()
ENDCLASS
El método error ha sido modificado para que NO me avise con un mensaje de alerta cuando se produce el error 2 (no se puede conectar) y así no interfiera con el método verificar y se pueda reconectar el servidor sin intervención del usuario
//------------------------------------------------------------------------------
METHOD Error() CLASS ClientSock2
IF ::nError != 0 .and. ::nError != 2
MsgInfo( "Error Socket " + Str( ::nError ) )
END IF
RETURN ::nError
Y el método verificar() se encarga de comprobar que efectivamente tenemos conexión con el servidor Gcal, y en caso contrario lanzar el ejecutable correspondiente al servidor y esperar a que éste esté disponible.
Para que la carga del servidor se haga un poco más amena, también cambio el cursor a modo «reloj de arena» y muestro una ventanita en la parte inferior derecha de la pantalla avisando de que se está cargando del servidor.
//------------------------------------------------------------------------------
METHOD Verificar( ) CLASS ClientSock2
LOCAL oForm
InetInit()
Application:lBusy:=.T.
IF !::Conectar()
DEFINE FORM oForm OF GetActiveform() SIZE 170, 45 BORDERSTYLE bsSPLASH Color CLR_BLACK,CLR_YELLOW
@ 15, 18 LABEL " ACTIVANDO SERVIDOR" OF oForm
oForm:nLeft=Screen:nWidth - 170
oForm:nTop=Screen:nClientHeight - 45
ACTIVATE FORM oForm
EXECUTE("gcalserver.exe",NIL,NIL,6)
DO WHILE !::Conectar()
ProcessMessages()
END
oForm:Close()
ENDIF
Application:lBusy:=.F.
//------------------------------------------------------------------------------
En cuanto al servidor VB, su explicación detallada se sale de la temática del blog, pero a modo resumen tan sólo tenemos que crear un proyecto de tipo aplicacion de consola y agregar las referencias las dll aportadas por google. A continuación nos queda crear el sistema de escucha socket y actuar en consecuencia según los datos recibidos. En nuestro caso, cuando la cadena recibida empieza por “N:” , generar una nueva entrada en google calendar.
En el fichero con el ejemplo tenéis tanto el código fuente de xailer, como el código fuente y el ejecutable VB y las dll necesarias para su funcionamiento.
Este ejemplo es una primera aproximación a lo que podemos hacer integrando varios sistemas de programación. Ampliando un poco el servidor VB seremos capaces de que nuestro sistema también pueda modificar eventos creados, borrarlos, etc…
Algunas referencias interesantes
Descripción general de las API de Google Apps
Herramientas y API de Google Calendar
Data API Developer’s Guide: .NET
http://code.google.com/p/google-gdata/
Utilizando Sockets en VB .NET
Si bien la solución a través de sockets funciona, creo que en este caso sería mucho más bonito y elegante poder utilizar una dll creada en .net y llamarla directamente desde xailer.
Pero eso se escapa de mis conocimientos de VB, y además, algo he de dejar para vosotros! . Así que si alguien tiene ganas, puede empezar en
http://msdn.microsoft.com/en-us/library/zsfww439(v=VS.71).aspx
Como siempre, el ejemplo en el área de downloads.