Archivo de la categoría: Programación

Adobe Air II – Aplicaciones en la barra de tareas

Después de ver cómo crear los distintos tipos de ventana con los que se puede crear una aplicación AIR, veremos hoy como hacer una de esas cosas tan chulas que se minimizan en un icono al lado del reloj de Windows.

Es muy sencillo una vez, de nuevo, se entiende el concepto. No todos los sistemas operativos permiten lo que queremos hacer, Windows, Mac OSX y algunos Linux. Para saber si lo soporta AIR dispone de dos métodos, supportsSystemTrayIcon, para entornos tipo Windows, y supportsDockIcon, para entornos tipo Mac.

Antes de meternos en berenjenales necesitaremos, al menos, dos iconos para nuestra aplicación (gif o png si es transparente) a 16×16 píxeles y a 128×128.

En este código veremos cómo asignar un icono en la barra de tareas o en el Dock y crear un menú contextual en este icono con la opción “Salir”, puedes añadir tantas opciones como estimes oportuno.

private function appTray():void{
    var icon:Loader = new Loader();
    var iconMenu:NativeMenu = new NativeMenu();
    var exitMenu:NativeMenuItem = iconMenu.addItem(new NativeMenuItem("Salir"));
    exitMenu.addEventListener(Event.SELECT, function(event:Event):void {
        NativeApplication.nativeApplication.icon.bitmaps = [];
        NativeApplication.nativeApplication.exit();
    });

    if (NativeApplication.supportsSystemTrayIcon) {
        icon.contentLoaderInfo.addEventListener(Event.COMPLETE, iconLoadComplete);
        icon.load(new URLRequest("icons/icono16.png"));

        var systray:SystemTrayIcon = NativeApplication.nativeApplication.icon as SystemTrayIcon;
        systray.tooltip = "Mi Aplicación";
        systray.menu = iconMenu;
        systray.addEventListener(ScreenMouseEvent.CLICK, restauraVentana);
    }

    if (NativeApplication.supportsDockIcon){
        icon.contentLoaderInfo.addEventListener(Event.COMPLETE,iconLoadComplete);
        icon.load(new URLRequest("icons/icono128.png"));
        var dock:DockIcon = NativeApplication.nativeApplication.icon as DockIcon;
        dock.menu = iconMenu;
    }
}

private function restauraVentana(e:ScreenMouseEvent):void{
	stage.nativeWindow.visible=true;
	stage.nativeWindow.orderToFront();
}

Como veis, creamos el menú que le vamos a asignar y cargamos el icono adecuado dependiendo del soporte del sistema operativo. Eso es todo.

Simplemente llamaríamos a esta función en el evento creationComplete de la aplicación y ya la tendríamos integrada en la barra de tareas o en el Dock.

Éste es el resultado.

Aplicacion AIR en la barra de tareas

Sencillo ¿no?. En el siguiente capítulo, aplicaciones que se actualizan automáticamente si hay una versión nueva.

Adobe Air I – Tipos de ventanas y cómo hacer aplicaciones con nuestro propio chrome

Aprovechando que los últimos días me he metido de lleno a desarrollar una pequeña aplicación con Adobe Air, comienzo una serie de artículos dónde iré contando algunos de los entresijos de este sistema ya que parece muy sencillo pero cuando quieres hacer cosas concretas parece que se complica un poco.

Hoy veremos la parte fundamental de una aplicación, las ventanas en sí mismas. Se conoce como “chrome” al entorno sobre el que se crea la ventana. Habitualmente es el propio sistema operativo el que lo hace, son las ventanas clásicas de la mayoría de aplicaciones. En AIR se crean como “WindowedApplication” en la propia aplicación (veremos más adelante que éste es un detalle importante) y en el xml descriptor de la aplicación se configuran estos parámetros:

<initialwindow>
    <systemChrome>standard</systemChrome>
    <transparent>false</transparent>
</initialWindow>

El resultado, bajo Windows, sería la clásica ventana con la estética del sistema operativo.

Ventana Standard en Adobe Air

Vale, pero yo quiero crear mi ventana sin el chrome del sistema operativo.

La primera opción es configurar así los parámetros:

<initialwindow>
    <systemChrome>none</systemChrome>
    <transparent>true</transparent>
</initialWindow>

Y obtenemos una ventana con un chrome AIR.

Venatana none en Adobe Air

Queda muy bonita, pero yo lo que quiero es que no tenga nada de chrome, ni barra superior ni barra inferior, quiero definir yo toda la estética y funcionalidad de mi aplicación. Bienvenidos a la madre del cordero. Esto, que debería estar bien explicado en la documentación, no hay manera de entenderlo, de hecho creo que no se llega a explicar la manera concreta de conseguirlo.

La clave del problema es que, en este caso, la aplicación en el MXML no se definde como  “WindowedApplication” sino como “Application” estándar, el de cualquier aplicación Flex, y en el descriptor definimos:

<initialwindow>
    <systemChrome>none</systemChrome>
    <transparent>true</transparent>
    <visible>true</visible>
</initialWindow>

Y conseguimos el resultado esperado, sólo nuestra ventana sin adornos.

Ventana con chromless personal en Adobe Air

Es importante remarcar que en este caso hay que ponerle el parámetro visible=true dentro del xml ya que si no lo hacemos no se verá nada hasta que se ponga a true, perdí varias horas hasta averiguar porqué no veía nada.

Esto es todo por hoy. En el siguiente capítulo veremos cómo crear aplicaciones que se quedan como un icono al lado del reloj en Windows o en el Dock de Mac OSX.

Funciones útiles en XSLT

Una de las tecnologías que más utilizo en el desarrollo de proyectos para móviles son las transformaciones XSL (XSLT) ya que me dan la versatilidad adecuada para adaptar la capa de presentación a las compatibilidades del terminal del cliente (xHTML Mobile, WML, iMode…) o de la operadora (PML de Vodafone por ejemplo).

A lo largo de estos años he ido recopilando algunas funciones (en realidad templates) XSLT que me facilitan la vida a la hora preparar determinadas funcionalidades dentro de un portal wap. Os las dejo por si a alguien le vienen bien.

Capitalize

Un clásico. Convierte una cadena de texto de manera que devuelve el primer carácter en mayúsculas y el resto de la cadena en minúsculas.

<xsl:variable name="lcletters">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="ucletters">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>

<xsl:template name="capitalize">
    <xsl:param name="arg"/>
    <xsl:value-of select="concat(translate(substring($arg,1,1), $lcletters, $ucletters), substring($arg,2))"/>
</xsl:template>

Ejemplo de uso:

<xsl:call-template name="capitalize">
     <xsl:with-param name="arg" select="/ruta/al/texto"/>
</xsl:call-template>

 

URL-Encode

Lo que su propio nombre indica. Codifica una cadena de texto para ser enviada como parámetro de una URL. Muy útil en situaciones de integración con sistemas de billing.

<xsl:variable name="ascii"> !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~</xsl:variable>
<xsl:variable name="latin1"> ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ</xsl:variable>
<xsl:variable name="safe">!'()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~</xsl:variable>
<xsl:variable name="hex" >0123456789ABCDEF</xsl:variable>
<xsl:variable name="thestring" select="null" />

<xsl:template name="url-encode">
    <xsl:param name="str"/>
    <xsl:if test="$str">
        <xsl:variable name="first-char" select="substring($str,1,1)"/>
        <xsl:choose>
            <xsl:when test="contains($safe,$first-char)">
                      <xsl:value-of select="$first-char"/>
           </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="codepoint">
                    <xsl:choose>
                           <xsl:when test="contains($ascii,$first-char)">
                                 <xsl:value-of select="string-length(substring-before($ascii,$first-char)) + 32"/>
                           </xsl:when>
                           <xsl:when test="contains($latin1,$first-char)">
                               <xsl:value-of select="string-length(substring-before($latin1,$first-char)) + 160"/>
                           </xsl:when>
                           <xsl:otherwise>
                               <xsl:message terminate="no">Warning: string contains a character that is out of range! Substituting "?".</xsl:message>
                               <xsl:text>63</xsl:text>
                           </xsl:otherwise>
                       </xsl:choose>
                </xsl:variable>
                <xsl:variable name="hex-digit1" select="substring($hex,floor($codepoint div 16) + 1,1)"/>
                <xsl:variable name="hex-digit2" select="substring($hex,$codepoint mod 16 + 1,1)"/>
                <xsl:value-of select="concat('%',$hex-digit1,$hex-digit2)"/>
            </xsl:otherwise>
        </xsl:choose>
        <xsl:if test="string-length($str) > 1">
            <xsl:call-template name="url-encode">
                <xsl:with-param name="str" select="substring($str,2)"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:if>
</xsl:template>

Ejemplo de uso:

<xsl:call-template name="url-encode">
    <xsl:with-param name="str" select="/ruta/al/texto"/>
</xsl:call-template>

 

Search and Replace

Otra vez lo que su propio nombre indica. Reemplaza un texto por otro dentro de una cadena.

<xsl:template name="SEARCH-AND-REPLACE">
	<xsl:param name="string" />
	<xsl:param name="search-for" />
	<xsl:param name="replace-with" />
	<xsl:choose>
		<xsl:when test='contains($string,$search-for)'>
			<xsl:value-of select="substring-before($string,$search-for)"/>
			<xsl:value-of select="$replace-with"/>
			<xsl:call-template name="SEARCH-AND-REPLACE">
				<xsl:with-param name="string" select="substring-after($string,$search-for)" />
				<xsl:with-param name="search-for" select="$search-for" />
				<xsl:with-param name="replace-with" select="$replace-with" />
			</xsl:call-template>
		</xsl:when>
		<xsl:otherwise>
			<xsl:value-of select="$string" />
		</xsl:otherwise>
	</xsl:choose>
</xsl:template>

Ejemplo:

<xsl:call-template name="url-encode">
    <xsl:with-param name="string" select="/ruta/al/texto/original"/>
    <xsl:with-param name="search-for" select="texto a buscar"/>
    <xsl:with-param name="replace-with" select="texto por el que se reemplaza"/>
</xsl:call-template>

Si habéis utilizado XSLT en alguna ocasión os habréis dado cuenta de que se echan en falta ciertas funciones “habituales” y que, tal cual, estás muy limitado. Sin embargo XSLT tienes las funcionalidades básicas a partir de las cuales se puede generar casi cualquier otra cosa que se te ocurra, sólo tienes que tener muy claro con lo que cuentas y qué quieres hacer.

Quiero aclarar que estas funciones NO las he hecho yo, las he ido recopilando los últimos años googleando un poco, a veces algo más que un poco, lástima no recordar de donde las fui sacando.

De momento estas tres son las que he localizado. Tengo otra más que suelo utilizar en integración con sistemas de billing para convertir a Base64, pero no la encuentro por ningún lado.

Buscar en tu web desde el buscador integrado de los navegadores

Hoy veremos cómo añadir una nueva opción a los motores de búsqueda integrados de los navegadores, los que aparecen habitualmente arriba a la derecha. La utilidad puede ser dudosa y discutible teniendo en cuenta que la mayoría de usuarios “normales” ni utiliza la caja de buscar de su navegador ni sabe cambiar de motor de búsqueda, pero la opción está ahí. Obviamente el objetivo no es añadirlo nosotros únicamente sino proveer el mecanismo para que nuestros visitantes puedan automáticamente buscar en nuestro site y añadir la opción permanentemente. Si despliegas los motores disponibles al visitar este artículo verás que te sale una nueva opción. Si añades el motor tendrás este resultado.

buscar_firefox.gif

Ahora que sabemos qué es lo que queremos hacer, veamos cómo hacerlo. Los dos navegadores mayoritarios, Internet Explorer y Firefox, basan su caja de búsqueda en el proyecto OpenSearch con lo que el mismo método nos sirve para los dos.

Lo primeroque tendremos que hacer será crear un archivo XML con los datos de nuestro site y guardarlo en nuestro servidor. Yo lo he guardado en el directorio raíz, /opensearch.xml.

<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
    <ShortName>Buscar en Cerebro en la Sombra</ShortName>
    <Description>Búsqueda de artículos en el blog Cerebro en la Sombra</Description>
    <Tags>php flex linux viajes</Tags>
    <Image height="16" width="16" type="image/gif">http://blog.osusnet.com/favicon.ico</Image>
    <Url type="text/html" method="GET" template="http://blog.osusnet.com/?s={searchTerms}" />
    <InputEncoding>UTF-8</InputEncoding>
    <AdultContent>false</AdultContent>
    <Language>es-es</Language>
</OpenSearchDescription>

Todos los parámetros son claramente explicativos y no creo que sean necesarias más aclaraciones excepto con <Url>. En este parámetro debemos añadir la URL que nuestra web utiliza para buscar. Por ejemplo. WordPress utiliza algo como

http://tudominio.com/?s=loquebuscas

En Google sería algo así

http://www.google.es/search?q=casa

Cómo se puede ver, el formato es similar, una URL con un parámetro y las palabras clave sobre las que se busca. Éso es exactamente lo que debemos poner en el parámetro <Url> sustituyendo las palabras clave del ejemplo por {searchTerms}, que será reemplazado en el momento de buscar por las palabras que el usuario haya introducido en la caja de búsqueda.

Si todo ha ido bien ya tienes la parte más complicada terminada. Ahora habrá que indicarle a nuestra web que ése es nuestro motor de búsqueda OpenSearch. Esto lo haremos añadiendo un nuevo tag html en la cabecera de nuestra web.

<head>
...
<link rel="search" href="http://blog.osusnet.com/opensearch.xml" type="application/opensearchdescription+xml" title="Buscar en Cerebro en la Sombra"/>
...
</head>

Siendo href la URL al archivo XML que creamos antes. Ya está todo hecho. Si ahora visitas tu web con Firefox y despliegas la lista de buscadores verás algo como esto.

buscar_add_firefox.gif

De manera que automáticamente puedes añadir tu motor de búsqueda a la lista de motores disponibles.

Si visitas tu web con Explorer verás esto otro.

buscar_explorer.gif

Explorer da, además,  la opción de usar el buscador directamente sin añadirlo a la lista.

Como véis es un proceso muy sencillo el de automatizar la agregación de proveedores a los buscadores integrados de los navegadores. Como decía al principio, lo dudoso es su utilidad real ya que ¿cuantos usuarios lo saben utilizar? 😉 .

Otras utilidades para un servidor de correo

A veces un servidor de correo puede sernos de muchísima utilidad si sabemos cómo manejarlo correctamente. En el artículo de hoy veremos como utilizar nuestro MTA para ejecutar automáticamente acciones cuando se recibe un determinado email o con los parámetros que definamos. Una vez conozcamos la teoría plantearemos dos casos prácticos como ejemplo.

Para comenzar necesitaremos un servidor Linux con Qmail como MTA. Supongo que cualquier otro servidor de correo servirá (Sendmail, Postfix), yo lo personalizo en Qmail porque es el que conozco y utilizo, pero estoy seguro de que con los demás se puede hacer lo mismo.

La teoría

Para entender cómo funciona la idea debemos entender primero cómo Qmail realiza la entrega de mensajes en los buzones locales. Es un tema sobre el que hay bastante literatura buscando en Google pero que puede no quedar muy claro en una lectura rápida. Es el famoso lío de los archivos .qmail.

Cada vez que se crea un usuario del sistema se debe crear, si va a recibir emails, un archivo .qmail-default en su directorio de usuario.

[jiglesias@lerez ~]# cat /home/jiglesias/.qmail-default
./Maildir/

Normalmente este archivo se crea automáticamente ya que al instalar qmail se habrá creado una copia de este archivo en el esqueleto de creación de usuarios /etc/skel:

[jiglesias@lerez ~]# ls -la /etc/skel
total 56
drwxr-xr-x  4 root root  4096 Jul 29 18:29 .
drwxr-xr-x 97 root root 12288 Dec 30 04:08 ..
-rw-r--r--  1 root root    33 Feb  1  2008 .bash_logout
-rw-r--r--  1 root root   176 Feb  1  2008 .bash_profile
-rw-r--r--  1 root root   124 Feb  1  2008 .bashrc
drwx------  5 root root  4096 Aug 29  2007 Maildir
-rw-r--r--  1 root root    12 Jan  2  2008 .qmail-default
-rw-r--r--  1 root root   658 Sep 12  2006 .zshrc

Para el que no lo sepa, el esqueleto son los archivos que se copiarán al directorio de usuario (con los permisos de éste) cada vez que se crea uno nuevo. Si quieres que todos tus usuarios tengan algún archivo automáticamente, éste es tu sitio. En nuestro caso vemos que además del .qmail-default está el directorio Maildir, el de entrega por defecto del correo en qmail. Teniendo un usuario este archivo y este directorio, podrá recibir correo.

Vale vale, vas muy deprisa. Todavía no has explicado para que sirve el .qmail-default ese.  Cierto. Los archivos .qmail indican las reglas de entrega de los mensajes en base a dos parámetros:

  • El nombre del archivo .qmail-xx hace referencia a la cuenta de correo sobre la que actúa.
  • El contenido indica qué hacer con el correo.

Supongamos un usuario (jiglesias)  que recibe el correo de dos cuentas distintas (jiglesias@… y osus@…).

Por defecto todo su correo irá a su buzón ya que es lo que indica el archivo .qmail-default. Queremos ahora que el comportamiento sea distinto dependiendo de la cuenta a la que vaya dirigido, creamos entonces los archivos .qmail para las direcciones:

  • .qmail-jiglesias : controla el correo que vaya a jiglesias@…
  • .qmail-osus : controla el correo que vaya a osus@…

Podemos incluso ir un poco más lejos con un archivo .qmail-jiglesias-default, y controlaríamos el correo que vaya a cualquier dirección del tipo jiglesias-XXXX@…, es decir, cualquier dirección que comience por jiglesias- será controlada por este archivo .qmail.

En el caso básico, que es el que veíamos, la entrega se realiza al buzón de correo del usuario (el directorio Maildir) pero podríamos hacer otras cosas en función del contenido del archivo .qmail encargado de procesar la entrega del correo:

Reenvío a otra cuenta:

[jiglesias@lerez ~]# cat /home/jiglesias/.qmail-jiglesias
[email protected]

Reenvío a un programa/script:

[jiglesias@lerez ~]# cat /home/jiglesias/.qmail-osus
|preline /usr/bin/programa

Combinación de los anteriores

[jiglesias@lerez ~]# cat /home/jiglesias/.qmail-jiglesias
./Maildir/
[email protected]
|preline /usr/bin/programa

El caso que nos interesa es el segundo, es decir, pasar la entrega del email a un script que se encargará de analizar el email y tomar decisiones.

Te habrá llamado la atención el |preline del archivo .qmail. Es el sistema que prepara un email para ser procesado y entregado a otro script añadiendo algunas cabeceras. La salida del script que reciba el email es importante ya que de ella depende el resultado de la entrega final de email, en concreto es importante saber que si queremos rechazar un email habrá que terminar el script con un exit (100), esto indicará a qmail que debe rechazar ese mensaje. Veremos más adelante la utilidad de esta salida.

La práctica

Una vez sabemos cómo pasar el control de un email recibido a un script, veamos como tratarlo. Lo haremos con un script PHP. En nuestro archivo .qmail haremos algo como:

[jiglesias@lerez ~]# more .qmail-jiglesias
|preline /usr/bin/php /home/jiglesias/prueba.php

Con esto hemos terminado el trabajo en el servidor de correo. Veamos ahora como parsear el email desde PHP.

Lo primero que debemos hacer es recoger el contenido del email desde el script a través de la entrada estándar,  después ya podemos procesar el email como una cadena de texto.

<?php
$email=file("php://stdin");
$email=implode("", $email);
?>

Con estas sencillas dos líneas de código tendremos en nuestro script el contenido del email. Ahora sólamente debemos procesarlo. Podemos hacerlo línea por línea por nuestra cuenta o apoyarnos en alguna librería. Yo utilizo Mail_mimeDecode de Pear. La ventaja de esta librería es que podemos obtener, además del texto del email, los archivos adjuntos.

<?php
$email=file("php://stdin");
$email=implode("", $email);
$params['include_bodies'] = true;
$params['decode_bodies'] = true;
$params['decode_headers'] = true;
$params['input'] = $email;
$structure = Mail_mimeDecode::decode($params);
$subject = trim($structure->headers['subject']);
$ddate = trim($structure->headers['date']);
$from = addslashes(trim($structure->headers['from']));
if(ereg("<(.*)>", $from, $p)) $from=$p[1];
if(ereg(""(.*)"", trim($structure->headers['from']), $pp))
    $nombre=$pp[1];
?>

Así podemos ya procesar el email y tomar las decisiones que consideremos oportunas. Podremos insertarlo en una base de datos, lanzar otros procesos automatizados, enviar avisos por SMS… lo que se nos ocurra.

Casos prácticos

La pregunta clave, después de ver la teoría, sería ¿para qué me sirve esto?.  Os propongo dos aplicaciones que yo he hecho.

Sistema de soporte

El típico sistema de tickets de soporte. En el asunto del email se arrastra el identificador del ticket, por ejemplo [#123445]. Tendremos que analizar el asunto y comprobar si aparece el patrón predefinido. Si no existe estamos ante un nuevo ticket e insertamos los datos en nuestra base de datos, en caso contrario es una respuesta a una incidencia anterior y ahí tendremos el identificador. Sencillo ¿no?. Podemos incluso adjuntar a nuestras indicendias archivos que puedan llegar en el email.

Envío de archivos desde el movil

El segundo ejemplo sería semejante al anterior técnicamente pero distinto conceptualmente. La mayoría de los móviles (salvo los de gama alta) no pueden enviar archivos desde los formularios wap (<input type=”file”>). La alternativa es que el usuario envie un email o un MMS (la mayoría de operadoras permiten el envío a direcciones de email) con sus archivos. Nuestro script procesará el contenido del mail recibido, decodificará los archivos y los tratará como sea oportuno.

Son dos sencillos ejemplos de cómo utilizar el email para automatizar tareas, pero, como he comentado, podríamos hacer todo lo que se nos ocurra, desde enviar un SMS de aviso hasta incluso reiniciar nuestro servidor o lanzar cualquier otra tarea.

Fotomatón, o haciendo fotos instantáneas online

La idea es sencilla, conectas con la webcam de tus usuarios, les haces una foto y la envías a una galería donde se pueden ver los caretos de tus visitantes. La implementación es casi tan sencilla como la teoría y el resultado es increíble y original.

El sistema es el mismo que utilizaba BloggerSnap, un servicio que tuvo un increíble éxito en su lanzamiento y que parece cerrado desde abril. Hace un año estaba por encima del puesto 40.000 en Alexa, seguramente estuvo incluso más alto. Imagino que sería muy complicado mantener el coste del ancho de banda con tanta popularidad. Hace un par de años estuvimos a punto de lanzar un servicio similar y lo desestimamos ya que no veíamos la manera de monetizarlo mientras que, a poco que tuviese éxito, los gastos se dispararían.

En un escenario normal dependerías de software que el usuario tendría que instalar para que se hiciese la foto y posteriormente te la enviase, con lo cual no conseguirías el objetivo de la instantaneidad, pero existe una solución que permite hacerlo todo mediante una aplicación online: Flash, en nuestro caso Flex 😛 .

La potencia de la plataforma Flash en el tratamiento de audio y vídeo va mucho más allá de lo que pueda parecer y permite crear aplicaciones multimedia avanzadas con participación del usuario de manera bastante rápida. Si además dispones de dinero suficiente, puedes combinarlo con Flash Media Server y los límites los pondrá tu imaginación. Otro día crearemos un videochat 😉 .

La clave es el acceso a la cámara y al micrófono del equipo que permite la plataforma Flash previa autorización del usuario, obviamente. Si lo autorizas, el player conectará con tu webcam y comenzará a mostrar el vídeo de la misma y, si la aplicación lo requiere, enviarlo a otros usuarios. Lo mismo se puede hacer con el audio.

En el taller de hoy haremos una pequeña aplicación para crear una galería de fotos instantáneas en web/blog. Los usuarios que lo deseen pueden hacerse la foto con su propia webcam y ésta quedará alojada en la galería. Simple pero llamativo.

La aplicación constará de dos partes:

  • Zona de capturas: una aplicación desarrollada en Flex que permitirá a los usuarios hacerse fotos sin más necesidad que tener una webcam.
  • Galería: una sencilla página html donde se listan todas las fotos que se han hecho los usuarios.

Capturando imágenes

Aunque pueda parecer algo muy complicado veremos a continuación que la facilidad de conectar con la webcam en Flex es impresionante.

Nuestra aplicación consta de dos únicos componentes, una imagen y un botón. La imagen nos aportará simplemente algo de estilo y el botón lo utilizaremos de disparador de nuestra cámara de fotos online.

Hay tres tareas bien diferenciadas:

  • Conectar con la webcam.
  • Capturar la foto.
  • Enviarla a nuestro servidor.

Conectar con la webcam

Con este sencillo código estaremos visualizando en nuestra aplicación la webcam que el usuario tenga conectada a su equipo.

private function insertWebcamVideo():void{
	var videoHolder:UIComponent = new UIComponent();
	if(Camera.names.length>0){
	   camera = Camera.getCamera();
	   if(camera==null || camera.width==-1 || camera.height==-1 || camera.fps==-1){
	   		Alert.show("Lo sentimos, no tienes cámara", "Error", 4, null, null, iconoAlerta);
	   		captura.enabled=false;
	   		camera=null;
	   }else{
		   camera.addEventListener(StatusEvent.STATUS, cerrar);
		   video = new Video(camera.width, camera.height);
		   video.attachCamera(camera);
		   videoHolder.addChild(video);
		   videoHolder.width=video.width;
		   videoHolder.height=video.height;
		   videoHolder.visible=true;
		   videoHolder.y=0;
		   caja.addChildAt(videoHolder, 0);
		   caja.removeChild(fotillo);
	   }
	}else{
   		Alert.show("Lo sentimos, no tienes cámara", "Error", 4, null, null, iconoAlerta);
   		captura.enabled=false;
	}
	//Security.showSettings(SecurityPanel.CAMERA);
}

Tan fácil como comprobar primero si tiene webcam (sino, obviamente, no puede hacerse fotos 😛 ) y posteriormente añadirla a un componente de vídeo que habremos incorporado a un UIComponent para mostrarlo en la aplicación. En este momento ya tenemos la cámara del usuario en vivo en la aplicación.

Capturar la foto

Teniendo el flujo de vídeo en la aplicación, queremos que cuando el usuario presione el botón “Capturar” se le haga la foto.

private function getSnapshot():void{
	var snapshot:BitmapData = new BitmapData(video.width, video.height, true);
	var m : Matrix = new Matrix();
	snapshot.draw(video, m);
	var jpegEnc:JPGEncoder=new JPGEncoder(75);
	var jpegDat:ByteArray = jpegEnc.encode(snapshot);
	var img_src:String = base64Encode(jpegDat);

	if(img_src.length>1600){
		//imagen correctamente capturada
	}else{
		Alert.show("La imagen capturada parece estar vacía", "Error", 4, null, null, iconoAlerta);
	}
}

Con este otro código conseguimos este efecto. Se obtiene una captura bit a bit del objeto de vídeo y se comprime en JPEG para enviarla al servidor. ¡Ya tenemos la foto hecha!

Enviar la foto

Utilizaremos un HTTPService para enviar por POST la foto que hemos realizado y que guardaremos en una base de datos. Añadimos a la función anterior el código para enviar la foto.

private function getSnapshot():void{
	var snapshot:BitmapData = new BitmapData(video.width, video.height, true);
	var m : Matrix = new Matrix();
	snapshot.draw(video, m);
	var jpegEnc:JPGEncoder=new JPGEncoder(75);
	var jpegDat:ByteArray = jpegEnc.encode(snapshot);
	var img_src:String = base64Encode(jpegDat);

	if(img_src.length>1600){
		var temp:Object=new Object();
		temp.imagen=img_src;
		httpService.send(temp);
		cargandoWindow = ventanaEspera(PopUpManager.createPopUp(this as DisplayObject, ventanaEspera, true));
	}else{
		Alert.show("La imagen capturada parece estar vacía", "Error", 4, null, null, iconoAlerta);
	}
}

<mx:httpservice id="httpService" showbusycursor="false" useproxy="false" method="POST" resultformat="text" url="upload.php" result="onResult()"
fault="onHTTPFault(event)" />

Ya está. El script upload.php de tu servidor recibirá por POST la variable “imagen” con el contenido de tu foto. Sólo habrá que guardarla en un archivo y habrás terminado la parta más ¿complicada? del proyecto.

if($_POST['imagen'] & strlen($_POST['imagen'])!=0 & strlen($_POST['imagen'])>1600){
        $bmd=base64_decode($_POST['imagen']);
        $im = fopen("fotos/foto.jpg",'w');
        fwrite($im,$bmd);
        fclose($im);
}

Antes de guardar la imagen en disco podrías añadir un registro a una base de datos que te permita generar después la galería. Esa parte queda a tu elección.

Generando la galería

Esta es la parte fácil, simplemente creas un script PHP (o en el lenguaje que prefieras) que liste las fotos que has ido guardando en la base de datos paginando como creas oportuno. Creo que sobran más comentarios para algo tan fácil.

Conclusiones

Hoy hemos visto cómo de una manera extremadamente rápida y sencilla se puede conectar con la webcam (y el micrófono) de los usuarios. Sólo hemos visto como hacer una fotografía instantánea de lo que se está viendo a través de la cámara, pero el sistema abre un inmenso mundo de posibilidades.

Descárgate aquí el código del proyecto.

Primeros pasos con Flash Media Server (2), autentificando usuarios del chat

Hace unos días veíamos como crear un sencillo chat con FMS.

Hoy, a petición popular, vamos a añadir la primera mejora a nuestra aplicación. Por defecto habíamos diseñado un chat público, cualquiera podía conectarse. Sin embargo, puede que deseemos que sólo nuestros usuarios registrados puedan utilizarlo. Necesitamos entonces que nuestros clientes se puedan autentificar contra nuestra base de datos pero no podemos dejar esa responsabilidad en manos de la aplicación cliente, cualquiera podría hacer otra que se conecte a nuestro servidor sin requerir autentificación.

FMS no puede conectarse con ningún tipo de bases de datos, no es su función, aunque cada vez hay más gente solicitando esta funcionalidad y se espera que en la próxima versión (FMS4) haya alguna novedad al respecto. Sea como fuera, de momento no hay métodos nativos para acceder a bases de datos así que sólo se puede recurrir a lo que un día fue Flash Remoting y hoy son los servicios RPC bajo AMF, el método estándar de acceso a datos desde aplicaciones Flash (con el permiso de los webservices SOAP). Con el tiempo fueron apareciendo versiones en los distintos lenguajes para utilizar el protocolo AMF fuera del amparo de Macromedia (ahora Adobe), pero sin duda uno de los pioneros fue amfphp, el que nos permite comunicar aplicaciones Flash con el servidor a través de PHP.

AMF es un formato para serialización de datos e invocación de métodos remotos y es independiente al lenguaje con el que trabaja el servidor, simplemente debe cumplir las especificaciones. FMS, como no podía ser de otra forma, puede comunicarse con nuestro servidor gracias a este protocolo AMF y esto es precisamente lo que vamos a utilizar para autentificar a nuestros usuarios:

dibujo1.jpg

El proceso es mucho más sencillo de lo que parece.

Comenzaremos por descargar amfphp desde la web oficial.  Lo descomprimes y subes la carpeta “amfphp” a tu servidor en la ruta que desees. Ya tienes lo que es el cerebro del sistema listo, para comprobarlo prueba la URL:

http://tuservidor/amfphp/gateway.php

Si todo va bien, prueba ahora el navegador de servicios, herramienta imprescindible para probar tus métodos remotos:

http://tuservidor/amfphp/browser/

Si no has visto ningún error es que todo va bien. Veamos ahora como crear tu servicio de autentificación.

Dentro de la carpeta amfphp que has subido a tu servidor encontrarás la carpeta services. Crea dentro de ella un nuevo directorio con el nombre de tu aplicación, por ejemplo chat. Creamos ahora dentro de esa carpeta las clases y métodos que se necesiten para crear el servicio de autentificación. Por ejemplo, en mi caso creo Chat.php:

<?
class Chat{
    var $conn;    function Chat(){
      $this->conn=mysql_connect("host", "usuario", "clave");
      mysql_select_db('db', $this->conn);
    }
function login($nick, $pass){
        $rs=mysql_query("select * from usuarios where nick='$nick' AND pass='$pass'");
        if(mysql_num_rows($rs)>0)
          return 1;
        else
          return 0;
    }
}
?>

Sencillo ¿no?. Nuestra clase tiene un método login que nos dice si la pareja nick/clave existe en nuestra base de datos. Obviamente esto es sólo un ejemplo, en tu aplicación haz la clase con todos los métodos y la lógica que necesites.

Si ahora vas de nuevo al Service Browser verás que te aparece la nueva aplicación chat con la clase que has creado. Si accedes a ella puedes probar el método login pasándole los parámetros necesarios. Más sencillo imposible.

Ya tenemos la lógica de autentificación lista. ¿Cómo le decimos ahora a FMS que la utilice? Modificamos el archivo main.asc que habíamos hecho en el capítulo anterior para que antes de aceptar la conexión de un usuario la autentifique.

application.onConnect = function(client, nick, clave, sala){
    var valido=1;
    for( i in this.userList){
        if(String(this.userList[i].nick).toLowerCase()==String(nick).toLowerCase()){
            valido=0;
            break;
        }
    }
    if(valido==1){
        var myServer = NetServices.createGatewayConnection("http://tudominio/amfphp/gateway.php");
        var servicio = myServer.getService("chat.Chat", this);
        var callserver=servicio.login(email, clave);

        this.login_Result=function(result){
            //trace("RESULTADO LOGIN"+result);
            if(result>0){
                client.uid = this.uid;
                client.nick = nick;
                client.sala = sala;
                this.acceptConnection(client);
                this.userList[client.uid] = client;
                this.users_so.setProperty(client.uid, client);
                var msg = "*** Entra " + client.nick + " ***";
                this.chat_so.send("onMsgSend", msg);
                client.call("onUserid", null, client.uid, client.nick);
                this.uid++;
            }else{
                var err = new Object();
                err.message = "Email o contraseña incorrectos";
                application.rejectConnection(client, err);
            }
        }
    }else{
        var err = new Object();
        err.message = "Ya hay un usuario con ese nick";
        trace(err.message);
        application.rejectConnection(client, err);
    }
}

Es tan simple como añadir la comprobación del usuario y clave de nuestro cliente. Indicamos la url de nuestro gateway (amfphp) y llamamos al método login con los datos correspondientes. Lo más importante es la forma en que obtenemos los datos que devuelve el metodo. La llamada es asíncrona, no se paraliza la actividad esperando por un resultado sino que se continua la actividad esperando a recibir la respuesta. Para eso se supone que debe haber definida una función nombremetodoremoto_Result, en nuestro caso login_Result ya que login es el método remoto al que llamamos. Cuando se llame a esta función sabremos si el usuario existe en nuestra base de datos, con lo que aceptamos su conexión al chat, o no existe y la rechazamos.

Como habéis visto, es muy sencillo llamar a métodos remotos desde FMS de manera que aumenten las posibilidades de tu aplicación ya que puedes hacer uso de bases de datos y de sistemas avanzados y siempre de manera muy simple.

Yo te he dado las pistas de cómo hacerlo, el resto queda a tu imaginación.

Y seguimos con la falta de personal técnico cualificado

Tenía pendiente hacer un comentario desde la semana pasada.

¿Por qué se empeña Enrique Dans en contradecir a cientos de programadores? ¿En serio es creíble el argumento de que no hay personal adecuadamente cualificado en España? Suelo leer a Enrique y creo que es un tío normalmente serio y coherente en sus argumentaciones (estés o no de acuerdo con ellas), pero es que con ésta no puedo, no la veo reflejada por ningún lado en el mundo real.

Mi experiencia me dice lo contrario. Claro que ya no se reciben cientos de curriculums para una oferta de trabajo, pero es que aquello era lo anormal. ¿Acaso recibes docenas y docenas de currilums para un puesto importante de departamentos tradicionales (comercial, marketing, rrhh…)? ¿Por qué esperas entonces recibirlos para un puesto de programador? Un programador es, desde la base de su formación, personal altamente cualificado y han estado puteados durante muchos años. Resulta que ahora cuando comienza a verse un poco de dignidad en el sector, no hay gente, curioso ¿no?, es decir, ahora que queremos jornadas normales y salarios decentes, faltan programadores. Recuerda que, ante todo, buscas a la/s persona/s que diseñarán y construirán tu producto…

Lo he comentado ya en otras ocasiones pero creo que mis razones siguen siendo completamente válidas:

Sobran más palabras ¿no?.

Generador de códigos de barras en AS3

Hoy vamos a ver un ejemplo sobre cómo generar códigos de barras al vuelo utilizando ActionScript3. Un código de barras es la representación mediante líneas paralelas de distinto grosor de un código numérico o alfanumérico. Esta representación lineal los hace de fácil interpretación para un lector láser. No creo que se necesiten más explicaciones, todo el mundo sabe lo que es un código de barras.

Los códigos de barras lineales pueden ser de distintos tipos, en el ejemplo de hoy generaremos EAN y C128. EAN es el más utilizado a nivel mundial y el utilizado en España, así que será el que nos ocupe hoy. Mi trabajo está basado en la clase para generar códigos C128 de Friedrich Dimmel (lo siento, no localizo el link original). La clase que genera el código EAN no es ni más ni menos que la reescritura en AS3 de la que trae phpBarcode  y adaptada para generar la secuencia de líneas que se dibujan bajo la lógica de Friedrich Dimmel en vez de generar una imagen con GD como hace la clase original. Como casi siempre, este pequeño desarrollo lo hice hace un par de años para un sistema de control de stocks, no me critiquéis mucho el código :P.
swfobject.embedSWF(“/wp-content/uploads/2008/10/codigosdebarras1.swf”, “codigosdebarras”, “470”, “350”, “9.0.000”);

Flash del generador

Un código EAN son 13 dígitos, 12 de información (código de país, empresa y producto) y un dígito de control que ayuda a verificar que la decodificación posterior es correcta.
Para crear la imagen de nuestro código simplemente debemos llamar a la clase generadora  con los parámetros:

  • Código: la cadena que queremos convertir a código de barras.
  • Alto de la imagen generada.
  • Tipo de código (EAN o C128).
  • Mostrar el texto del código (opcional).
  • Color de fondo (opcional).

Por ejemplo:

BarcodeGenerator.generateBarcode("3456789012345", 50, "EAN");

Como hemos visto, el último dígito es de control, con lo que con introducir los 12 dígitos con valor será suficiente, la clase generará el treceavo automáticamente. No voy a explicar detalladamente como generar los códigos EAN, hay suficiente literatura acerca del algoritmo googleando un poco y para ver el resultado lo mejor es que examinéis el código fuente que os dejo más abajo. Creo que lo más curioso e importante es ver cómo generamos la imagen una vez tenemos generado el código de líneas.

public static function generateBarcode(code:String="0", height:Number=60, tipo:String="EAN", showText:Boolean=true, backgroundColor:uint=0xffffff):Canvas {
    var can:Canvas = new Canvas();
    can.horizontalScrollPolicy = "off";
    can.verticalScrollPolicy = "off";
    can.height = height;
    can.setStyle("backgroundColor", backgroundColor);
    var sBarcode:Shape;
    var rect:Rectangle;
    var bd:BitmapData;
    var img:Image;
    var bitmap:Bitmap;
    if(tipo=="EAN"){
      var barcode:BarcodeEAN = new BarcodeEAN();
      barcode.code = code;
      barcode.barHeight = height;
      if(showText) {
        barcode.barHeight -= 15;
      }
      var k:Object=barcode.barcode_encode_ean(code);
      if(!k.bars) return new Canvas();
      var bars:String=k.bars;
      var text:String=k.text;
      sBarcode = barcode.generateBarcode();
      rect = new Rectangle(0, 0, (sBarcode.width / 2) + 1, barcode.barHeight);
      bd = getBitmapData(sBarcode, rect);
      img = new Image();
      bitmap = new Bitmap(bd);
      img.source = bitmap;
      can.addChild(img);
      var chars:Array=text.split(" ");
      var n:String;
      var v:String;
      var total_y:Number=40;
      for(n in chars){
        v=chars[n];
        if (StringUtil.trim(v)){
          var inf:Array=v.split(":");
          var fontsize:Number=inf[1]/1.8;
          var fontheight:Number=30;
          var label:Label = new Label();
          label.text = inf[2];
          label.x = Number(inf[0])-2;
          label.y = fontheight;
          label.setStyle("fontSize", 9);
          label.setStyle("paddingBottom", 0);
          label.setStyle("paddingLeft", 0);
          label.setStyle("paddingRight", 0);
          label.setStyle("paddingTop", 0);
          can.addChild(label);
        }
      }
    }
    return can;
}

En realidad es tan sencillo como crear una imagen a partir de lo que nos devuelve la clase generadora EAN (un objeto de tipo Shape). Finalmente si queremos imprimir el código en la propia imagen tendremos que poner cada letra en el sitio que le toca, no van en cualquier lado.

¿Qué tal si hacemos el proceso contrario con un lector casero?

Os dejo el código fuente del proyecto.

Primeros pasos con Flash Media Server, creando un chat

Hoy vamos a ver cómo crear una sencilla aplicación de chat con Flex. No debe tomarse este ejemplo como una aplicación completa sino simplemente como un método para comprender cómo funcionan los distintos elementos que intervienen. Tampoco debe compararse este proyecto con el cliente IRC que os presenté hace unos meses, aunque ámbos son aplicaciones de chat,  son sistemas distintos, como veréis más adelante.

Chat en Flex con FMS

Flash Media Server (FMS, antiguamente conocido Flash Communication Server) es el software de Adobe que permite crear aplicaciones multiusuario de una manera sencilla y bajo tecnología Flash. FMS permite no sólo intercambiar y sincronizar mensajes de texto entre los clientes sino tambien audio y vídeo. Si flv es el sistema de vídeo por excelencia en Internet, FMS es la tecnología para crear sistemas multimedia multiusuario. La principal pega de FMS es que es de pago, y no precisamente barato, sin embargo existe una licencia “developer” que te permite utilizar el 100% de las posibilidades del servidor, la única limitación es la cantidad de usuarios concurrentes que puedes tener, diez en este caso. Existe una alternativa Open Source a FMS, Red5, pero no lo he utilizado nunca así que no puedo opinar. La teoría con Red5 es exactamente la misma que con FMS, sólo cambia el medio (FMS se programa con ActionScript y Red5 con Java). Como ejemplo de aplicaciones que utilizan FMS está Yahoo Live, la aplicación de grabación directa desde tu webcam de Youtube o todos, absolutamente todos los videochats que puedes encontrar en Internet.

Como lo más probable es que no tengas una licencia de FMS, trabajaremos con la versión developer que hemos indicado ya que nos permitirá poner en práctica todos los conceptos necesarios.  Lo primero que debes hacer es, por tanto, descargar e instalar FMS, hay versiones para servidores Linux y Windows, escoge la que mejor te venga. La instalación no tiene ningún secreto así que lo dejaré en vuestras manos. En mi caso, como siempre, un CentOS5.

Los proyectos creados sobre FMS son sistemas cliente/servidor y necesitan dos aplicaciones para poder funcionar:

  1. Aplicación cliente, sería la parte Flash pura y dura, la que utilizarán los usuarios.
  2. Aplicación servidor, sería lo que corre bajo FMS y a lo que se conecta la aplicación cliente.

Digamos que FMS es la varita mágina del conjunto ya que hace completamente transparente al desarrollador la conexión entre usuarios y la inyección de mensajes entre ellos. Para entenderlo pensemos en un chat donde los usuarios se conectan al servidor y cada vez que uno de ellos escribe un mensaje éste se envía automáticamente a los demás usuarios. Imagina, además, que no sólo hablamos de texto sino que cuando un usuario enciende su webcam los demás usuarios pueden verla. Todo esto lo hace FMS por ti, sólo debes preocuparte de construir tu aplicación.

Programando una aplicación FMS

Empezaremos la parte práctica creando el código necesario en el servidor. Por defecto las aplicaciones FMS de instalan bajo /opt/adobe/fms/applications creando un nuevo directorio con el nombre de tu aplicación, en nuestro caso la llamaremos textchat.

Las aplicaciones se cargan y se mantienen en memoria una vez algún usuario accede por primera vez a la misma y, cuando pasado un tiempo prudencial no hay usuarios, se descarga para liberar recursos esperando a que otro usuario vuelva a acceder. Cada vez que se hacen cambios a la parte servidor de la aplicación hay que reiniciarla, pero no es necesario reiniciar el servidor FMS para reiniciar una aplicación, basta con descargar y volver a cargar esa aplicación desde la consola de administración como veremos más adelante. Desde esta misma consola podremos ver también la salida de los comandos trace que ocurran en el servidor y los objetos creados y su contenido, ayuda imprescindible mientras estás desarrollándola. Veremos también como activar la salida de estas opciones puesto que por defecto (por seguridad) vienen deshabilitadas.

Por defecto todas las aplicaciones deben tener un archivo main.asc con el código necesario.  Crea tu main.asc de esta manera:

load("netservices.asc");

application.onAppStart = function(){
  trace("inicia text chat");
  this.users_so = SharedObject.get("users_so", false);
  this.chat_so = SharedObject.get("chat_so", false);
  this.userList = {};
  this.uid = 1;
}

application.onConnect = function(client, nick, clave, sala){
  trace(nick+" "+clave+" "+sala);
  var valido=1;
  for( i in this.userList){
    if(String(this.userList[i].nick).toLowerCase()==String(nick).toLowerCase()){
      valido=0;
      break;
    }
  }
  if(valido==1){
    client.uid = this.uid;
    client.nick = nick;
    client.sala = sala;
    this.acceptConnection(client);
    this.userList[client.uid] = client;
    this.users_so.setProperty(client.uid, client);
    var msg = "<font color='#666666'><b>*** Entra " + client.nick + " ***</b></font><br>";
    this.chat_so.send("onMsgSend", msg);
    client.call("onUserid", null, client.uid, client.nick);
    this.uid++;
  }else{
    var err = new Object();
    err.message = "Ya hay un usuario con ese nick";
    trace(err.message);
    application.rejectConnection(client, err);
  }
}

application.onDisconnect = function(oldClient){
  if(oldClient.uid!=undefined){
    trace("Descontecta cliente. UID:  " +oldClient.uid);
    var msg = "<font color='#666666'><b>*** " + oldClient.nick + " sale ***</b></font><br>";
    this.chat_so.send("onMsgSend", msg);
    for( i in application.userList){
      if(Number(application.userList[i].uid)==Number(oldClient.uid)){
        application.userList[i]=undefined;
      }
      k++;
    }
    application.disconnect(oldClient);
    application.users_so.setProperty(oldClient.uid, null);
  }
}

El funcionamiento es muy sencillo. Nuestra aplicación maneja tres eventos:

  1. onAppStart: Se dispara cuando la aplicación se carga en memoria por primera vez.
  2. onConnect: Se lanza cuando un usuario se conecta a la aplicación.
  3. onDisconnect:Cuando un usuario se desconecta.

Veamos la teoría de un chat y comprenderemos rápidamente como funciona FMS en su lado servidor. En una aplicación chat típica, el servidor debe manejar una lista de los usuarios conectados actualmente (los nicks) y debe poder ser capaz de enviar mensajes a todos los usuarios. Por ejemplo, cuando un nuevo usuario se conecta y sin que éste escriba nada, el servidor tiene que poder enviar un mensaje tipo “Nick se ha conectado al chat” a todos los demás usuarios.  Para solucionar todo esto FMS tiene lo que se conocen como objetos compartidos (SharedObjects), objetos a los que todos los clientes pueden acceder.

Si analizas ahora el código de nuestro main.asc verás que el evento onAppStart crea las variables que se utilizarán en la aplicación:

  • userList: array que contendrá la lista de usuarios conectados al sistema.
  • uid:  identificador del último usuario conectado para poder localizar de manera inequívoca a un cliente.
  • users_so: objeto compartido de la lista de usuarios
  • chat_so: objeto compartido del chat propiamente dicho, a él se envíen los mensajes que escriben los usuarios.

Creo que sobran más explicaciones ya que es tan sencillo que se entiende sólo.

Veamos ahora la teoría de qué ocurre cuando se conecta un usuario, evento onConnect. Cada vez que un cliente accede a nuestro chat debemos comprobar, antes de nada, siya hay otro usuario con el mismo nick, en cuyo caso rechazamos su conexión. Si todo va bien y su nick es único debemos guardar al usuario en la lista de usuarios y avisar a todos los clientes ya conectados que hay un nuevo usuario en el chat.

Compara ahora la teoría con el código del evento. El parámetro cliente es automático y representa al cliente que se conecta. Es un objeto con distintos parámetros predefinidos. Los demás parámetros del evento se los pasaremos nosotros desde nuestro cliente flash. Para ver si un nick ya existe simplemente lo comparamos contra todos los demás del array userList que ya hemos visto que contiene a todos los usuarios conectados. El evento onConnect puede aceptar un cliente (acceptConnection) o rechazarlo (rejectConnection).

Si el usuario ha sido admitido, le damos el siguiente UID y tendremos entonces que avisar a los demás usuarios de que tenemos un usuario nuevo. Si recordamos las variables que teníamos, había un objeto compartido al que tienen acceso todos los clientes, users_so, solo tendremos que añadir nuestro nuevo cliente a este objeto:

this.users_so.setProperty(client.uid, client);

Cuando veamos la aplicación cliente veremos como detectamos los cambios en ese objeto, pero es sumamente sencillo.

Espera!. Nos falta enviar el mensaje de “Nick se ha conectado al chat”. Tan sencillo como:

this.chat_so.send("onMsgSend", msg);

Magia!. Como chat_so era un objeto compartido, todos los clientes y el servidor tienen acceso al mismo y lo que uno escriba lo recibirán automáticamente todos los demás. Esta es la verdadera magia de FMS, no tienes que preocuparte de cómo enviar los mensajes a los usuarios uno a uno, se sincronizan sólos.

Como último detalle, cuando aceptamos la conexión de un cliente llamamos remotamente al método onUserid de la aplicación cliente. Si! has leído bien!, FMS llama a una función de tu SWF!. Más adelante veremos para qué, simplemente recuerda que cuando te conectas al chat el servidor llama a un método de la aplicación pasándole su propio UID.

Finalmente veamos el último evento del servidor, bastante obvio si has seguido lo explicado anteriormente y lo has entendido, la desconexión de un usuario.

Sin pensar mucho más entenderemos que habrá que eliminar al cliente de userList y de users_so. Pues eso mismo, no hay mucha más ciencia.

Ya tenemos la parte del servidor terminada. Sencillo ¿no?. Es posible que aún no entiendas algunas cosas. Tranquilo, cuando veamos el cliente lo entenderás todo y verás que realmente es muy sencillo crear aplicaciones de este tipo con FMS.

La consola de administración

Es una aplicación Flash desde la que podemos conectarnos como administradores al servidor FMS y realizar operaciones de mantenimiento, entre ellas cargar y descargar instancias de aplicación, ver el log del sistema (los trace), examinar objetos (en modo debug)…

Cuando instalaste FMS te pidió un usuario y una clave además de un puerto donde instalar esta consola de administración. No tienes más que conectarte a ella y comenzarás a ver estos datos.

Consola de administración de FMS

Hay, sin embargo, algunas particularidades de administración que conviene tener en cuenta. Para habilitar las opciones de debug de objetos habrá que modificar el fichero conf/_defaultRoot_/_defaultVHost_/Application.xml cambiando:

<AllowDebugDefault>false</AllowDebugDefault>

a true. Reinicias el servidor para que el cambio se active y verás algo como esto que te servirá para depurar tu aplicación.

Consola de administración de FMS

En la barra de opciones de aplicación (Live log, clients…) tienes dos pequeños botones que te permitirán recargar una instancia de la aplicación o descargarla por completo, las opciones que comentábamos antes que te ayudarán durante el desarrollo para no tener que reiniciar todo el servidor.

La aplicación cliente

Y llegamos al último punto. La aplicación, el chat, lo que ve el usuario.

Ya os he puesto una captura del resultado final. Veremos ahora los entresijos. Abajo de todo tenéis el código fuente de todo el proyecto de demostración.

Me meteré directamente al meollo de conectar con FMS y enviar y recibir datos, la interfaz de usuario y la pantalla inicial de login la dejo para vosotros. Como veréis hay una clase Chat.as que centraliza casi todo el proceso de comunicación con el servidor.

chat = new Chat();
chat.addEventListener(UserIDEvent.USER_ID, onUID);
chat.addEventListener(NCEvent.STATUS, onStatus);
chat.addEventListener(NCEvent.NONICK, onStatus);

salaSeleccionada=sala.selectedItem.toString();
chat.init("rtmp://"+host+"/textchat/"+salaSeleccionada, StringUtil.trim(nick.text), StringUtil.trim(clave.text), salaSeleccionada);

private function onUID( event:UserIDEvent ):void{
    miuid=event.userid;
    currentState = "chatState";
    msg.setFocus();
    CursorManager.removeBusyCursor();
    this.addEventListener(KeyboardEvent.KEY_UP,keyHandler);
    picker.addEventListener(DropdownEvent.CLOSE, pickerClose);   
}

private function onStatus( event:NCEvent ):void{
    CursorManager.removeBusyCursor();
    if (event.status == "closed"){
        status.text = "Desconexión completa.";
    }
    if (event.status == "nickexiste"){
        status.text = "Usuario o contraseña incorrectos.";
    }
    currentState = "";
}

Al crear la instancia de Chat haremos que se detecten un par de eventos que ejecutarán:

  •  onUID: se dispara cuando el servidor, como hemos visto antes, llama al método cliente onUserid. Nos sirve para cambiar el estado de la interfaz de usuario desde la ventana de login a la de chat ya que sólo si hemos conectado recibiremos el UID.
  • onStatus: nos ayudará adetectar conexiones rechazadas y tu propia desconexión.

Para conectar véis que llamamos al método init de Chat con cuatro parámetros, la url de la instancia de nuestra aplicación (definida como host+aplicacion+sala de chat), el nick, la clave (para un futuro) y la propia sala de chat. Si examinas este método verás qué sencillo es iniciar una conexión. El método más importante es

private function netStatus( event:NetStatusEvent ):void{
    var info:Object = event.info;
    switch(info.code)
    {
    	case "NetConnection.Connect.Success":
    	    users_so = SharedObject.getRemote("users_so", nc.uri, false);
    	    users_so.addEventListener(SyncEvent.SYNC, usersSync);
    	    users_so.addEventListener(NetStatusEvent.NET_STATUS, onSOnetStatus);
    	    users_so.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSOSecurityError);
    	    users_so.client = this;
    	    users_so.connect(nc);
    	    chat_so = SharedObject.getRemote("chat_so", nc.uri, false);
    	    chat_so.addEventListener(NetStatusEvent.NET_STATUS, onSOnetStatus);
    	    chat_so.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSOSecurityError);
    	    chat_so.client = this;
    	    chat_so.connect(nc);
    	    break;
    	case "NetConnection.Connect.Rejected":
    	    dispatchEvent( new NCEvent(NCEvent.NONICK, "nickexiste"));
    	    break;
    	case "NetConnection.Connect.Closed":
    	    dispatchEvent( new NCEvent(NCEvent.STATUS, "closed"));
    	    break;
    }
}

Es el que detecta el estado de la conexión, si el servidor nos acepta o nos rechaza tal como veíamos antes e incluso si nos desconecta. Fíjate que si somos aceptados estaremos conectándonos a los SharedObjects que creamos antes en el servidor 😉 . Ahora es Chat quien hará absolutamente todo el trabajo, la aplicacion mxml es la interface de usuario que transmite a Chat los comandos necesarios. Veamos por ejemplo como indicamos al chat que envíe a los demás usuarios un mensaje que hayamos escrito nosotros.

private function sendMsg():void{
    if ( msg.text.length > 0 ){
        chat.sendMsg(msg.text);
        msg.text = "";
    }
}

Vale sí, pero has hecho trampas, aquí estás dejando el meollo en manos de Chat 😛 . Bien, veamos qué ocurre en sendMsg.

public function sendMsg( color:String, msg:String ):void{    
    var outmsg:String = "";    
    outmsg = "<font color='#" + color + "'><b>" + this.username+":</b> " + msg + "</font><br>";
    chat_so.send("onMsgSend", outmsg);
}

¡ Sólo eso ! Como ya estamos conectados al SharedObject chat_so, le decimos a éste que llame al método onMsgSend de sus clientes y le pase el texto que hemos escrito.

De acuerdo, entonces ¿cómo leemos los mensajes que escriben los demás usuarios?. Te lo acabo de decir 😛 . Cuando un usuario escribe algo, llama (da igual cómo) a un método local de cada usuario que se encarga de escribir el texto en su ventana de chat.

public function onMsgSend( msg:String ):void{
    chatHistory += msg;        
    dispatchEvent(new Event("histChange"));
}

Obviamente este método tiene que ser público. Al despachar ese evento simplemente hace que se actualice el texto del textarea.

Hasta ahora creo que lo voy entendiendo, pero no has explicado como detectamos las entradas y salidas de usuarios. Cierto, veámoslo pues. Acabamos de ver que cuando entramos al chat y somos admitidos conectamos con los SharedObjects que creamos en el servidor. Si te has fijado (y si no hazlo ahora), al de usuarios le poníamos un listener distinto:

users_so.addEventListener(SyncEvent.SYNC, usersSync);

Esto significa que cada vez que haya un cambio en el objeto users_so todos los clientes serán notificados de ello. Por ejemplo, cada vez que entra o sale un usuario vimos que añadíamos o quitábamos un elemento de este objeto que representaba a ése usuario. Cada una de estas operaciones hará que nuestra aplicación cliente reciba una llamada a usersSync.

private function usersSync(event:SyncEvent):void{
    var results:Object = event.target.data;
    var usersArray:Array = new Array();
    var existe:Boolean=false;
    //debo actualiza solo los datos
    for( var i:String in results ){
        if(results[i].sala==this.sala){
            existe=false;
            for(var j:Number=0; j<dpUsers.length; j++){
                if(dpUsers[j].uid==results[i].uid){
                    existe=true;
                    break;
                }
            }

            if(existe){
                //por si se ha cambiado el nick
                dpUsers[j].nick=results[i].nick;
            }else{
                var clientObj:Object=new Object();   
                clientObj.nick = results[i].nick;
                clientObj.uid = results[i].uid;
                dpUsers.addItem(clientObj);
            }
        }
    }
    //ahora tengo que eliminar las bajas
    //elimino empezando por la ultima para no cambiar los indices
    for(j=dpUsers.length-1; j>=0; j--){
        existe=false;
        for(i in results ){
            if(dpUsers[j].uid==results[i].uid && results[i].sala==this.sala){
                existe=true;
            }
        }

        if(!existe){
            dpUsers.removeItemAt(j);
        }
    }
    //finalmente ordenamos los usuarios
    var sort:Sort = new Sort();
    sort.fields = [new SortField("nick",true)];
    dpUsers.sort = sort;
    dpUsers.refresh();
    dispatchEvent(new Event("dpChange"));           
}

Esta método repasará todos los usuarios del SharedObject comparándolos con los de la lista de usuarios del canal donde nos encontramos chateando y añadiendo los nuevos usuarios y eliminando los que hayan salido.  Quizás no sea el mejor método para manejar los usuarios y habría sido mucho más sencillo tener una pareja de métodos addUsuario y delUsuario que manejen todo automáticamente con llamadas remotas igual que con los mensajes, pero quería que viésemos el modo de recibir avisos de cambios en un SharedObject.

Conclusiones

Hemos visto cómo de una manera sencillísima podemos crear nuestro propio chat sin excesivas complicaciones. Piensa además que esto es, seguramente, lo más sencillo, el primer paso con FMS, las posibilidades reales son infinitas.

Quizás en otros artículos ampliemos las posibilidades de este pequeño ejemplo añadiendo vídeo y audio o, incluso, hagamos un pequeño sistema de grabación de vídeo con tu webcam similar al de Youtube.

Como siempre, aquí os dejo el código completo del proyecto. Recuerda que es un ejemplo de lo que se puede hacer, no debes usarlo tal cual sino aplicar lo que hemos explicado. Del mismo modo habrá cosas que no sean perfectas y se puedan hacer mejor, no lo dudo, mi intención era explicar los conceptos básicos de FMS y que se vea su potencial.