Tivion, TV en streaming para Ubuntu

Es viernes, viernes de perder el tiempo, y para hacerlo os voy a recomendar un programa para ver televisiones, de todo el mundo, vía streaming (oséase, emisiones abiertas a través de Internet), obra de
Angel Guzman Maeso.

No es muy estable, ni todos los canales de su listado funcionan, pero aún así es muy recomendable.

Para versiones recientes de Ubuntu (9.10 y posteriores), podéis agregar el repositorio con add-app-repository.

sudo add-apt-repository ppa:shakaran/ppa

Si usáis debian (recordad usar el mplayer de debian-multimedia.org) o versiones anteriores de Ubuntu, podéis añadir los repositorios manualmente a /etc/apt/sources.list.

deb http://ppa.launchpad.net/shakaran/ppa/ubuntu karmic main
deb-src http://ppa.launchpad.net/shakaran/ppa/ubuntu karmic main source

Y para instalarlo, usáis vuestro gestor de paquetes favorito o apt-get.

sudo apt-get update
sudo apt-get install tivion

Más información en el blog del autor o en su archivo en el launchpad.

Python: Sistema de sesiones (Google App Engine)

Primero toca explicar qué es un sistema de sesiones. Cuando se programa para web, se debe tener en cuenta que, puesto que el cliente y el servidor, son entidades bien diferenciadas, que sólo se ponen en contacto cuando el cliente (en este caso el navegador del usuario) hace una petición.

Esta cosa tan simple, base del protocolo TCP/IP, sobre el cual que se erige el HTTP, es obviado por mucha gente que programa usando las herramientas inadecuadas, pero esa es otra historia1.

La pregunta surge entonces ¿cómo identificamos eficazmente al usuario que navega por nuestras páginas? Puede que por el navegador, el sistema operativo, la IP, o todo a la vez. Pero no es suficiente, porque toda esa información no es de fiar pues puede falsearse, y en el caso de la IP, suele pasar que hayan miles de personas con la misma a la vez.

¿No hay un identificador único para el usuario? La respuesta es rotundamente no, por motivos de privacidad, a menos que uses Google Chrome (no es un bug, es una feature).

Pero no estéis tristes, todavía queda por probar algo: las cookies2. Sí, esas pequeñas ilegibles y endemoniadas cadenas de texto que web sí, y otra también, van llenando poco a poco, lenta pero incansablemente, tu disco duro, hasta explotar en un maremágnum de bytes a cascoporro.

Por todo ello, tan sólo deberíamos mantener una cookie con un id único de sesión, para saber quien nos está pidiendo cada cosa. Dicho id debería ser aleatorio y volátil, y debería ser el servidor, y no la cookie, quien decide cuando caduca para evitar suplantaciones de sesión. La información relativa a la sesión, al usuario, y demás se guardaría en el servidor, lejos de todo peligro.

Sin embargo, puede ser que queramos guardar algún tipo de información en las cookies del cliente, ya sea por comodidad o para validar la sesión con información extra. Dicha información, obviamente no debería ser "muy peligrosa", porque aunque la cifremos (y guardemos la clave del cifrado en el servidor junto con la id de sesión) existe la amenaza potencial de que alguien rompa el cifrado. Se podría decir que aplicamos el cifrado solo para evitar los script kiddies.

Para guardar y obtener datos de esta segunda cookie lo oṕtimo es usar un serializador seguro (recordemos que las cookies pueden ser manipuladas), como el presentado días atrás, y cifrándolo con una clave generada aleatoriamente y se guardaría en el servidor.

Teniendo todo esto en cuenta, he desarrollado una librería para Google App Engine, aunque podría ser adaptada fácilmente para otros usos simplemente cambiando la forma en la que las cabeceras son enviadas al cliente, y cómo son guardados los valores en el servidor (usa memcache). Podéis descargarla (46.2 KiB) desde las descargas del blog en Google Code.

Usarlo, con Google App Engine, resulta francamente fácil.

from google.appengine.ext.webapp.util import run_wsgi_app

from google.appengine.ext import webapp
from appSession import Session

class RequestWeb(webapp.RequestHandler):
def get(self):
session = Session(self)
if session["visited"]:
text = "Times visited %d." % session["visited"]
session["visited"] += 1
else:
text = "First time on page."
session["visited"] = 1

session["_foo"] = "This string will be stored on server."
session["bar"] = "This string will be stored on a cookie."

session.write()
self.response.out.write(
"<html><head></head><body><p>%s</p></body></html>" % text
)

application = webapp.WSGIApplication([
('/',RequestWeb)
],
debug=True)

run_wsgi_app(application)

EDITO 2011.08.30: Ejemplo corregido para evitar la llamada del /favicon.ico

En este ejemplo, el número de veces que se ha visitado la página, en lo que dura la sesión, es guardado en una cookie, junto con otra cadena de texto. Y otra cadena de texto es guardada en el servidor.

Espero que os sea útil.

  1. Si empiezo con el tema, no pararé hasta que se me agoten las pilas.
  2. No es realmente la última opción: la especificación del HTML5 añade al navegador la posibilidad de mantener una especie de base de datos en el navegador, pero su aceptación está tardando eones.

Programar Javascript eficientemente

Código sin errores

Puede parecer una obviedad, pero muchos programadores noveles cometen grandes errores de sintaxis en su código sin darse cuenta y, puesto que los intérpretes de javascript tienen cierta tolerancia a dichos errores, no se dan cuenta.

Dichos errores, aunque no frenen la ejecución en según que intérpretes, drenan en gran medida el rendimiento, y pueden convertir el algoritmo más eficiente del mundo en el código más lento jamás ejecutado.

// Errores comunes
var a = 5 // Omitir ;
b = 6; // No declarar variables apropiadamente
function c(){
return this; // this fuera de objetos (devolverá window)
}

Y por último un caso un poco complicado de entender.

var d = [ 1, 2, 3, 4, 5, 6 ], e = 0;
for( var f in d ){ e += f; }

La sintaxis for( x in y ) ejecuta el código dentro del bucle asignando a x todos y cada uno de los atributos de y. Es decir, en el caso anterior, no sólo recorrería todos los elementos del Array, sino sus métodos tales como splice, slice... o sus atributos como length. O al menos es así en intérpretes que respetan el estándar, no obstante la mayoría tolera este error y oculta deliberadamente las propiedades extras.

Para comprobar los errores del código recomiendo usar jslint.com.

Estructuras en tiempo de compilación

Muchos intérpretes de javascript precompilan el código para mejorar su eficiencia. No obstante, y dada la enorme flexibilidad de javascript, podemos declarar clases mediante prototipado en tiempo de ejecución, en cuyo caso la mejora del rendimiento será mucho menor.

// Declarada en tiempo de compilación
var objeto = {
foo : function( o ){ return o+1; },
bar : function( o ){ return 0; }
};

// Declarada en tiempo de ejecución
var objeto = ( function(){
var r = {};
r.foo = function( o ){ return o + 1; };
r.bar = function( o ){ return 0; };
return r;
}() );
Evitar ejecuciones repetitivas en los bucles

Esto es algo muy común a todos los lenguajes de programación, el evitar llamadas extras innecesarias. Recordemos que el código de dentro de un bucle se ejecutará en todas las iteraciones, de modo que si podemos reducir llamadas extras desde su interior aumentaremos el rendimiento notablemente.

function foo(){return 5;}
// Estos bucles ejecutarán una función en cada iteración.
var i = 0;
while( i < foo()){
i++;
}
for( var j = 0; j < foo(); j++ ){
i++;
}

// Cuando en realidad podrían reducir su ejecución a una única vez
var vfoo = foo(), i = 0;
while( i < vfoo ){
i++;
}
for( var f = 0; j < vfoo; j++ ){
i++;
}

Esta norma incluye una de las penalizaciones de rendimiento más comunes.

var a=[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], b=0;

// Este bucle obtiene, en cada iteración, al atributo length del Array
for( var i = 0; i < a.length; i++ ){
b += a[ i ];
}

// Cuando debería acceder a él una única vez
for( var i = 0, l = a.length; i < l; i++ ){
b += a[ i ];
}

Tened en cuenta que en este último caso, al estar tanto el Array como el bucle en el mismo ámbito de variables, la mejora no es perceptible, pero cuando el Array se encuentra en un ámbito más global, o en un objeto, la mejora es sustancial.

Evita los dinosaurios

Hay miles de frameworks pululando por la red, muchos de ellos demasiado grandes y pesados. Si bien es cierto que es un absurdo el reinventar la rueda con cada nuevo proyecto, también lo es el cargar un framework enorme para sólo utilizar determinada funcionalidad.

Siempre es preferible reutilizar porciones de código conforme se vayan necesitando o, si finalmente optamos por un framework, cerciorarse de que es modular, y evitar ciertas características extremadamente ineficientes que muchos incluyen.

Espero no haberme olvidado de demasiadas cosas, aunque probablemente mantenga el texto actualizado.

Minipost llorón de hoy

Advertencia: Este post no es más que otra pataleta infantil de nulo interés. No llaméis a la Buaaaaambulancia, ya viene de camino.

Perdonad que hoy no haya posteado nada, pero estoy bastante cansado del trabajo, cosas de lidiar contra el mierdiSQL del MAccess.

Por cierto, sabed que UNION implica DISTINCT (cosa que no dice en ningún lado en la documentación). Si no queremos omitir filas iguales, toca usar UNION ALL.

Por cierto, hasta los cojones estoy de "trabajar para Microsoft". ¿Tan difícil es encontrar un trabajo dónde se usen tecnologías decentes? ¿Tán horriblemente mal está el sector de las TIC en España? En fin, ya tendré la oportunidad de buscarlas cuando se me acabe el contrato.

Sistemas de derechos de autor

Sistema obsoleto, centrado en la distribución
Autores
Reciben dinero al hacer la obra, en base a las posibles ventas, ganan una ínfima cantidad por cada copia, reproducción, y demás. Casi nadie gana lo suficiente, muy pocos ganan algo para vivir, y poquísimos amasan algo de dinero. No obstante, dado el inmenso gasto en publicidad, muchos ganan una fama efímera. En cuanto a los conciertos, conforman la mayor parte de los beneficios del autor, pero su discográfica, entidades de gestión, y demás, drenan sus beneficios apropiándose de un porcentaje escandaloso de su propia recaudación.
Copiadores
Pagan al autor por la obra una miseria, la copian y alimentan los canales de distribución. A veces el autor incluso debe de pagarles. Reciben una cantidad exagerada de dinero directamente de las ventas.
Entidades de gestión
Amasan fortuna extorsionando a todo el mundo, a veces ilegalmente por derechos sobre obras que no poseen, y reclaman cantidades ingentes de dinero por cuestiones absurdas. Viven de la extorsión y del dinero que recaudan de los autores por, según ellos, proteger su obra.
Consumidor
Oferta muy reducida. Precio final del producto escandaloso. Ante la dificultad de adquirir el producto, o supuesto bien de interés cultural, muchos optan por realizar copias por sí mismos. Legal en España por poco tiempo. Ilegal en muchos otros países en los que los colectivos anteriormente mencionados han adquirido demasiado poder político.
Sistema clásico, centrado en la reproducción
Autores
El autor percibe la cantidad íntegra de sus conciertos, y puede optar por pagar o no a las entidades de gestión. Muchos optarán por no hacerlo o gestionar ellos mismos su obra, para evitar ser explotados. El autor se siente desprotegido, si bien en realidad lo que ocurre es que está libre de parásitos que dan falsa imagen de protección. Vende y distribuye sus propias copias.
Copiadores
Reciben dinero por realizar copias. Algunos, hoy en día, son extorsionados por las entidades de gestión para que establezcan ilegalmente, como requisito, el hecho de estar adscrito a dichas entidades.
Entidades de gestión
El autor puede elegir, reálmente, si quieren que las entidades de gestión reclamen dinero por su obra, tanto a ellos mismos como a todo el mundo. Recordemos que dado el actual enfoque de los derechos de autor, establecen que el mero hecho de crear una obra significa tener total control sobre los derechos de la misma, lo que hace, obviamente, innecesarias las entidades de gestión.
Consumidor
Los conciertos son menos costosos, por no estar sujetos a parásitos extras. La oferta es mayor, al no estar el mercado asfixiado por el exceso de distribución. Mejora la calidad de la oferta, pero su área de mercado se delimita muy comúnmente.
Sistema moderno, basado en distribución por internet
Autores
Controlan la distribución de su obra a través de internet, y el coste de hacerlo es nulo. Ellos deciden si venderlo en primera instancia (tarde o temprano acabará en canales gratuitos) o no. Pueden escoger entre una gran variedad de licencias apropiadas al medio. El público objetivo aumenta exponencialmente, lo que implica mucho más beneficios por conciertos. La venta del producto en ciertas plataformas puede significar, a su vez, en otro negocio muy lucrativo. Cobrar por el uso comercial de la obra hace prescindir de entidades de gestión para ganar dinero con los redistribuidores.
Copiadores
Prácticamente no existen, ya que todo el mundo puede realizar sus copias a un coste ínfimo.
Entidades de gestion
No existen, no extorsionan, su negocio mafioso no tiene cabida. Durante la transición, se opondrán con todas sus fuerzas, y para ello utilizarán todo su poder político, en contra del cambio que significaría su extinción.
Consumidor
Máxima oferta. Coste mínimo. No se acumulan soportes físicos innecesarios (CDs, DVDs...). Se libran del constante acoso de las entidades de gestión.

Ahora que alguien me explique ¿tan estúpidos son los autores famosillos como para apoyar el sistema que menos beneficios les da?

Javascript: Localizar nodos del DOM

Javascript dispone de tres funciones para localizar nodos.

getElementById(id)
Recibe el id del elemento que se quiere encontrar, y lo retorna.
getElementsByName(name)
Recibe un nombre, y retorna una lista de nodos con ese nombre en su atributo name.
getElementsByTagName(tagName)
Recibe el nombre de nodo (p.ej: input, div, img...), y retorna una lista de nodos de ese tipo.

Éstas son las estándares, pero muchos frameworks poseen los suyos propios (algunos extremadamente ineficientes) además de muchos otros algoritmos que rondan por foros y blogs más visitados que éste.

Pero ¿qué ocurre si quiero filtrar sus resultados según el valor de algún otro de sus atributos? Sencillo: buscamos usando alguno de los estándares y filtramos.

Podéis bajar el mismo código (0.5KiB) o la versión minificada (0.3KiB) de las descargas del blog en Google Code.

function require(a,o){
/* Filter a list of DOM nodes by theirs attributes
* a: hash with attribute name as key, and attributes value (or cmp function) as value
* b: list of DOM nodes
*/
var tr=[];
for(var j in a){if(typeof(a[j])!="function"){
a[j]=(function(a){return function(v){return v==a;}}(a[j]));}}
for(var i=0,l=o.length,j,k;i<l;i++){
k=1;
for(var j in a){
k=(typeof(o[i][j])!="undefined")&&(a[j](o[i][j]));}
if(k){tr[tr.length]=o[i];}}
return tr;
}

Tengo la buena costumbre de comentar el código en inglés, pero por si acaso explico, recibe un objeto hash (o diccionario) con los atributos a buscar y sus valores (o funciones de comparación), y una lista de nodos del DOM.

Siempre habrá que pasarle una lista de nodos puesto que se trata simplemente de un filtro. Pero puesto que los atributos posibles dependen directamente del tipo de nodo, usarlo en conjunción con getElementsByTagName es bastante trivial.

Usarlo es muy sencillo y potente, como ejemplo vamos a buscar todos los botones de envío de formulario.

var submits=require(
{type:"submit"},
document.getElementsByTagName("input"));

O buscamos los cuadros de selección que estén activados.

var checkedboxes=require(
{checked:"checked"},
document.getElementsByTagName("checkbox"));

Y también permite especificar filtros personalizados.

var divs=require(
{className:function(v){
return v.search("test")>-1;}},
document.getElementsByTagName("div"));

Como veis, es un método muy sencillo a la par que útil.

Brain Fuck Scheduler

Hace mucho tiempo, en el lejano país austral, un anestesista de profesión, nuestro amigo y héroe Con Kolivas, decidió compartir sus parches para mejorar el rendimiento de del núcleo Linux.

Su obra más famosa, el Brain Fuck Scheduler, es un innovador gestor de procesos del procesador. Entre sus creaciones encontramos el Fair Scheduler, en el cual de basa el Completely Fair Scheduler de Ingo Molnar, que es el usado actualmente, pero el BFS, tirando por tierra todos los planteamientos de lo que un gestor de hilos (de concurrencia optimista) debería de ser, consigue mejorar notablemente el rendimiento bajo ciertas circunstancias muy comunes: procesadores de menos de 16 hilos de procesamiento.

Es por esto último que algunas distribuciones como PCLinuxOS, algunos firmwares para móviles y , han decidido incorporarlo por defecto, si bien el propio Con Kolivas ha dejado claro que no es buena idea integrarlo en la rama principal del desarrollo de Linux por su poca escalabilidad porque, seamos francos, aunque ahora sólo se libren algunos servidores, es solo cuestión de tiempo que los ordenadores personales de conviertan también en un amasijo de procesadores y RAM, y discos duros y periféricos diminutos, todo ello alimentado por pilar nucleares, supongo.

Altamente recomendado para aquellos adictos a las aplicaciones ejecutándose en tiempo real... si esque los hay.

Si os interesa entender cómo funciona, el propio Con ha escrito una guía explicándolo, y respuestas a preguntas frecuentes (obviamente en inglés).

Bespin 0.4.4

Bespin es un proyecto de la gente de Mozilla, más bien un experimento, consistente en implementar un editor de código, online, utilizando HTML5 (y su canvas) y JavaScript.

A día de hoy soporta gestión de proyectos, SVN, subidas a SFTP, coloreado de sintaxis, y diferentes comportamientos dependiendo del lenguaje en cuanto a identación.

No es fácil de usar (tiene mucha funcionalidad, pero para hacer cosas demasiado básicas hay que recurrir a su peculiar línea de comandos), pero va en bien camino, y ya han corregido ciertos errores con el soporte a teclados internacionales, de modo que ahondando un poco en su documentación, ya se puede empezar a trabajar con él.

A día de hoy dan 15 MiB de espacio para tu código, puede parecer poco, pero 15 megas de código es muchísimo, y todavía está en pañales.

Si os pica la curiosidad, podéis registraros, e inmediatamente empezar a usarlo, en su página oficial.

Google Presentations

Para probar qué tal funciona la aplicación de presentaciones de Google Docs, he probado a hacer un tutorial sencillo de Inkscape, ya que aunque ya había hecho mis pruebas, está continuamente recibiendo mejoras y actualizaciones.

Aunque haya sufrido un poco por los errores y la lentitud del javascript de Mozilla Firefox, el resultado me parece excelente (teniendo en cuenta el poco tiempo invertido). Es la plataforma ideal para realizar presentaciones de diapositivas (de las serias, no de las de los efectos fluflú), en la nube, y sigue siendo colaborativo. Y lo mejor de todo esque todo lo hace nativamente, con HTML y Javascript. Nada de plugins inseguros, pesados, inestables o devoradores de recursos1.

Éste podría considerarse como una continuación de mi anterior Trabajar en la nube.

  1. Adobe Flash, guiño guiño.

GNU/Linux distro timeline 10.3

Desde el 4 de Marzo está disponible la última versión (10.3) del diagrama de árbol de distribuciones GNU/Linux más famoso. Contiene las distribuciones más conocidas (y otras no tanto) con sus divisiones, fusiones, cambios de nombre, etcétera.

Podéis descargarlo en formato PNG (746 KiB), SVG (227 KiB) o CSV (41 KiB), o consultar versiones anteriores en su página oficial.

El miedo al cambio y la tecnología

Hoy en día, ya sí, vivimos en una sociedad dominada por el uso de tecnologías impensables hace ya no treinta o veinte, sino ya diez años. Y hay demasiada gente que nunca estuvo preparada para ello.

Culpad a la tecnofobia, o incluso a la neofobia. El miedo al cambio, o a cualquier tipo de evolución, representa un gravísimo problema, a veces un insalvable obstáculo social. Y va empeorando, la evolución tecnológica sigue una progresión exponencial.

Lo vemos cada día, en todos lados, técnicos de marketing, a veces siendo incluso analfabetos digitales, inventando palabras cada vez más enrevesadas, acrónimos imposibles, y neologismos absurdos para llamar a cada nuevo conector, a cada nuevo cable, a cada nuevo botón, o incluso para decir que esa televisión tiene tal cual resolución. Y gente perdida entre millones de pegatinas, entre millones de siglas que no entienden, que se llenan la boca con todo lo que dicen que podrá hacer su futura compra. Al final todo se quedará en nada. Casi toda esa funcionalidad será olvidada y desperdiciada.

Tristes incautos que se compran toda una máquina de cientos o miles de euros, porque el vendedor de ésta o ésa gran superficie les ha convencido de que lo necesitan si quieren escribir o hablar con sus conocidos. Vendedor que es vendedor, pero no sabe lo que vende. Comprador que es comprador, y no sabe lo que compra. Usuario que no usará lo que ha pagado.

Mucha gente lo pasa mal, terriblemente mal, y lo veo continuamente: Se ven ante máquinas alienígenas, llenas de botones, sonidos, e imágenes extrañas, que les da miedo tocar por equivocarse y adentrarse en lo que no conocen. Ya no leen, ya no ven, sólo buscan el botón que les llevará a dónde ya han estado. Es el pánico a perderse. Y algunos, que se aprovechan de esto, tienen la culpa.

Muchas empresas, capitaneadas por ejecutivos decapitados por su ignorancia y avaricia, destrozan el futuro prometedor de la generalización de las máquinas de luces, obligan a sus clientes a elegirlos a ellos, sin ofrecerles lo que necesitan, utilizando mentiras, engaños, extorsión y muchísimo dinero en sobornos y publicidad. Y esos otros aterrados ignorantes, sus clientes, son realmente presos de todo un engranaje, de un móvil perpetuo, que cada día es más grande y monstruoso.

Muchos somos los que nos esforzamos en hacer cosas sencillas, fáciles de usar, que nada más verse te cuenten sin tapujos lo que pueden y lo que no pueden hacer. Pero intentar hacer cosas que no den miedo no es muy efectivo cuando aún hay personas que creen que el exceso es una virtud, desgraciadamente ubicados en la parte superior de los estrictos organigramas empresariales, y no se escandalizan de creerlo. Ellos provienen de la época de las cavernas en esto de la tecnología, y no se han molestado en reciclarse porque por encima de ellos sólo hay gente anclada aún más atrás en el tiempo.

El problema es que empresas antiguas, con planteamientos antiguos a la par de erróneos, intentan hacer lo que han hecho siempre, negocios a la antigua, cerrándose en banda a la innovación o el cambio. Tal vez solo aceptan cambiar las cosas de sitio e incrementar el precio.

Ellas tienen el dinero, ellos controlan la distribución, ellos pueden cercenar los derechos de sus clientes a voluntad, espiarlos, y torturarlos de mil millones de maneras diferentes. Y sus clientes aceptan, de buen grado, todo aquello que les llega. Porque les han enseñado a tener pánico por lo que no conocen, porque no les importa sufrir si ese sufrimiento es conocido, porque les han inculcado una neofobia exagerada.

Vivimos en un mundo cambiante, y nada puede frenarlo, ni siquiera el miedo, las guerras, o los poderosos del pasado. Si las cosas siguen como ahora mucha gente acabará muy mal en el camino, pero tarde o temprano se superará ese miedo, se verán las cosas como son, porque abrazarse a lo antiguo disfrazado de nuevo es una estupidez, y verán que muchas malas personas han estado jugando con ellos para exprimirlos.

No sé lo que pasará después, no tengo miedo al cambio, y personalmente me gustan las sorpresas.

¿Libertad de expresión? No en España.

Y no lo digo yo, lo dicen Nicole Wong y Peter Fleischer, ambos de Google, en sus comparecencias en la Cámara de Representantes del Congreso de los Estados Unidos, cito:

In the last two years, we have received reports that our blogging platform has been or is being blocked in at least seven countries China, Spain, India, Pakistan, Iran, Myanmar and Ethiopia.

Y traduzco libremente.

En los últimos dos años, hemos recibido informes de que nuestra plataforma de blogs ha estado siendo bloqueada en, al menos, siete países: China, España, India, Pakistán, Irán, Mianmar, y Etiopía.

Cuando se pone un país justo detrás de China en cuanto al tema de la censura en Internet, es el momento de empezar a buscar un trabajo en el extranjero. Porque supongo que nadie querrá vivir otra dictadura (me da igual si fascista, nacional-socialista o bananera) ¿o si?

Pero claro, esto no va a quedarse tal cual. Presiones gubernamentales ya han obligado a Google a retractarse. No, no fué un error, ni un lapsus, ni nada parecido. O si no, tiempo al tiempo.

Esto viene a cuento de los artículos España... ¿derechos humanos?,Cerramos en enero, reabrimos en febrero o Constitución Española, Artículo 20.

Vía El egoBlog de Enrique Dans

Ubuntu y Sixaxis (Joypad PS3)

EL sixaxis, al menos conectado por USB, es detectado como joystick ( /dev/input/jsN , siendo N un número empezando por 0 ), por lo que ya se puede usar como cualquier otro mando (aunque las luces parpadeen). El problema surge al intentar utilizarlo por Bluetooth.

También aviso que algunos de los programas a utilizar sirven también para otros mandos, si bien las instrucciones específicas no.

Lo primero es comprobar que tanto bluez, bluez-compat, como bluez-tools, se encuentran instalados.

sudo apt-get install bluez bluez-compat bluez-tools

El resto es bien sencillo, gracias al proyecto QtSixA, sólamente deberemos añadir sus repositorios para descargar los paquetes del demonio bluetooth, y la utilidad gráfica de administración de mandos.

sudo add-apt-repository ppa:falk-t-j/qtsixa
sudo apt-get update
sudo apt-get install qtsixa

Ejecutáis qtsixa para que os detecte el mando y listo.

Si además queréis mapear teclas del teclado a botones del mando, y así poder usarlo con juegos que no permitan ser controlados con mando, podréis hacerlo con una aplicación llamada QJoyPad (QtSixA permite hacer algo parecido desde sus preferencias, pero de manera mucho menos flexible), que aparecerá en la barra de notificación del sistema, y os permitirá seleccionar la tecla asignada a cada botón o sensor del mando, así como guardar o cargar configuraciones de este tipo.

Para instalar QJoyPad, podéis hacerlo desde los repositorios de playdeb.

echo "deb http://archive.getdeb.net/ubuntu karmic-getdeb games" | sudo tee -a /etc/apt/sources.list
wget -q -O- http://archive.getdeb.net/getdeb-archive.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install qjoypad

Por último, os dejo con la lista de sensores y botones del mando tal cual son reconocidos

  • Axis (sensores con detección de posición o presión)
    • 0 LX
    • 1 LY
    • 2 RX
    • 3 RY
    • 4 Rotación lateral (eje X)
    • 5 Rotación posicional (no sé que dirección)
    • 6 Rotación arriba abajo (eje Y)
    • 8 Arriba
    • 9 Derecha
    • 10 Abajo
    • 11 Izquierda
    • 12 L2
    • 13 R2
    • 14 L1
    • 15 R1
    • 16 Triángulo
    • 17 Círculo
    • 18 Equis
    • 19 Cuadrado
  • Botones
    • 0 Select
    • 1 L3
    • 2 R3
    • 3 Start
    • 4 Arriba
    • 5 Derecha
    • 6 Abajo
    • 7 Izquierda
    • 8 L2
    • 9 R2
    • 10 L1
    • 11 R1
    • 12 Triángulo
    • 13 Círculo
    • 14 Equis
    • 15 Cuadrado

Enlaces

Serializador recursivo para Python

Un serializador es un algoritmo capaz de convertir objetos, en este caso de Python, en cadenas de bits.

Para cierto proyecto en curso, me ha hecho falta serializar objetos complejos para guardarlos en cookies del navegador, y en este caso Pickle no es una opción, ya que es inseguro y permitiría ejecutar código arbitrario en el servidor a cualquiera que se le ocurriera modificar dicha cookie.

Existe otro serializador, Cerealizer, que permite elegir qué clases son las que permitimos coger del string serializado, además de los tipos básicos del lenguaje, pero alguna razón no me llegó a funcionar. De modo que he tenido que perder una semana de mi vida para crear mi propio algoritmo, recursivo, para tal menester.

Es bastante seguro, ya que obliga registrar todas las clases que puedan ser serializadas, y dichas clases deben definir sus propios métodos, __getstate__ y __setstate__, opcionales en Pickle, que definen qué datos son realmente serializados y cómo se deben recoger dichos datos por la clase cuando se carguen.

La cadena generada contiene únicamente caracteres imprimibles (a menos que alguna cadena serializada contenga otra cosa), escapa las cadenas unicode, es compacta (más que las cadenas de Pickle en sus protocolos 0 y 1, pero menos que su protocolo 2), tienen mejor ratio de compresión y el algoritmo en general es un 35% mas rápido que el mismo Pickle (cPickle sería mucho mas rápido, pero no está escrito en Python, sino en C).

Podéis bajarlo (14.1KiB) de las descargas del blog en Google Code.

Usarlo es muy sencillo

from PRSerializer import register, loads, dumps

class Clase(object):
atributo1 = True
atributo2 = None

def __init__(self):
self.atributo2 = xrange(1000)
# El serializador reconoce xrange nativamente
# por lo que es preferible usarlo frente a range

def __getstate__(self):
# Requerido. Retorna lo que realmente será serializado
return (self.atributo1, self.atributo2)

def __setstate__(self,o):
# Requerido. Recoge y utiliza los datos serializados
self.atributo1 = o[0]
self.atributo2 = o[1]

# Registramos la clase para el serializador
register(Clase)

# Creamos una instancia de la clase y trabajamos con ella normalmente
instancia = Clase()
instancia.atributo1 = "Cadena de texto"

# Serializamos la instancia
a = dumps(instancia)
print "Cadena:",a

# Deserializamos la instancia
b = loads(a)
print "Instancia:",repr(b)

Os dejo los resultados de una prueba de rendimiento, incluida en el módulo, en un dual core de 2.80Ghz.

Performance test: serializing, unserializing, zlib compression and pickle
1 dict
2000 lists (1000 are empty)
100000 integers (1000 are dictionary keys)
100000 strings
51000 objects
100000 booleans

Serializing time: 5.641000s ( 5463337 bytes )
Unserializing time: 3.328000s ( 1641627.670456 B/s )
Zlib compress time: 0.140000s ( 26580 bytes )

Pickling time: 9.703000s ( 5268354 bytes )
Unpickling time: 4.312000s ( 1221788.950771 B/s )
Zlib compress time: 1.344000s ( 506233 bytes )

Métodos estáticos en Python

Los ejemplos de la siguiente guía puede ejecutarse en el intérprete interactivo de Python (que aparece cuando llamamos a su ejecutable sin parámetros desde consola) o en Python IDLE, su intérprete interactivo con coloreado de sintaxis y autocompletado implementado con Tkinter (Tk/Tcl para Python).

Pongamos como ejemplo la siguiente clase.

class Clase(object):
atributo = True
def __init__(self):
self.atributo = False

def getAtributo(self):
return self.atributo

Para poder trabajar con ella, deberemos instanciarla y, al hacerlo, se ejecutará el método __init__

# Instanciamos, se ejecutará __init__, y atributo será False
instancia = Clase()
# Y el ejecutamos el método, que nos devolverá False, y lo mostramos
print instancia.getAtributo()

Y así funcionan normalmente las clases en Python, recibiendo su instancia como primer parámetro.

Sin embargo, en otros lenguajes (e incluso muchos módulos de Python) no se necesitan instanciar todas las clases para poder acceder a sus métodos, que en este caso se denominan estáticos. Si intentamos acceder al método del ejemplo anterior sin instanciar la clase, tal cual, nos encontraremos con que requiere que le especifiquemos la instancia, manualmente, para su primer parámetro self.

# Esto da error
print Clase.getAtributo()

Python para definir que métodos de una clase son estáticos, dispone de dos decoradores (una característica de su sintaxis): @staticmethod y @classmethod, con una serie de diferencias entre ambos.

class OtraClase(object):
atributo = True
def __init__(self):
self.atributo = False

@staticmethod
def getAtributoStaticmethod(self):
return self.atributo

@classmethod
def getAtributoClassmethod(self):
return self.atributo

La diferencia, entre el método estático y el método de clase en Python, es que mientras el método estático no recibe por defecto ningún parámetro, el método de clase recibe como primer argumento el objeto classtype, esto es, la referencia a la declaración de la clase.

# El método estático, recibiendo la clase, retorna True
print OtraClase.getAtributoStaticmethod(OtraClase)
# Mientras que el método de clase ya recibe la clase, y retorna True
print OtraClase.getAtributoClassmethod()

Como podréis ver, en el ejemplo anterior se retorna True, ya que el método __init__, que contiene la asignación a False, sólo se ejecuta cuando se instancia la clase.

Y por probar, veremos que si no adjuntamos la instancia de la clase al método estático, veremos que le falta un parámetro.

# Error, falta un parámetro
print OtraClase.getAtributoStaticmethod()

Sin entrar a saco con el resto de decoradores de Python, estos dos de los que hemos hablado, son sumamente útiles, sobretodo para los que os dediquéis a modularizar vuestros programas o estéis acostumbrados a usar métodos estáticos tal cual aparecen en otros lenguajes de programación menos flexibles.

DJL

DJL es un gestor de juegos para GNU/Linux, al más estilo Synaptic (sistema de búsqueda, gestor de descargas e instalador todo integrado), además de lanzador.

Está programado en Python, lo que garantiza su ágil desarrollo, y con él es tremendamente fácil descubrir e instalar una buena cantidad de títulos.

(No os preocupéis, también está en español)

A día de hoy, permite instalar:

  • 555-Boom!
  • A Tale in the Desert IV
  • A7Xpg
  • Alien Arena
  • Ardentryst
  • Armagetron Advanced
  • Assault Cube
  • AstroMenace
  • Auteria
  • Automanic
  • Awaker
  • Battle Jump
  • Battle Tanks
  • Biniax
  • Blob Wars - Blob And Conquer
  • Blood Frontier
  • BosWars
  • BygFoot
  • BZFlag
  • CounterStrike 2D
  • Coup de foot
  • Cube
  • Dark Horizons
  • Defcon
  • Digital Paint Paintball
  • Dwarf Fortress
  • Empty Clip
  • Endgame: Singularity
  • Excalibur
  • F-1 Spirit Remake
  • FooBillard
  • Freeciv
  • FreeCol
  • FreeDroidRPG
  • FreeSynd
  • Freetennis
  • FrozenBubble
  • Gargoyle
  • Glest
  • GlestAD
  • Globulation2
  • Gridwars
  • Gunroar
  • Hedgewars
  • Hive Rise
  • Kimboot
  • Landes eternelles
  • Legends
  • Lemming Ball Z
  • LinCityNG
  • Lupercalia
  • Ma princesse adoree
  • Machine ball
  • Mad Skills Motocross
  • Maniadrive
  • Monsterz
  • Morpyon
  • MTP target
  • NetPanzer
  • NeverBall
  • Nevernoid
  • NeverPutt
  • Nexuiz
  • Nimuh
  • Njam
  • Numptyphysics
  • Oolite
  • Open Sonic
  • OpenAlchemist
  • OpenAnno
  • OpenArena
  • OpenCity
  • Pang Zero
  • Parsec47
  • Planeshift
  • PokerTH
  • Postal 2: Share The Pain
  • Racer
  • Regnum online
  • RRootage
  • RTChess
  • Sauerbraten
  • Savage
  • Savage 2 : A Tortured Soul
  • Scorched3D
  • Scourge
  • SDL-Ball
  • Simutrans
  • Skulltag
  • Slime volley
  • Smokin guns
  • SoulFu
  • Spring
  • Stepmania
  • Stormbaancoureur
  • SuperTux
  • SuperTux Kart
  • Tarot Club
  • TeeWorlds
  • Tile Racer
  • Titanion
  • Toribash
  • Torus Trooper
  • Tremulous
  • Tumiki Fighters
  • UFO
  • Urban Terror
  • Vdrift
  • VegaStrike
  • Virtual Jay Peak
  • Warsow
  • Warzone 2100
  • Wesnoth
  • Widelands
  • Wolfenstein : Enemy Territory
  • World of Padman
  • Wormux
  • XMoto
  • Xrick
  • Yo Frankie!
  • Yoda Soccer
  • Z-Lock

Si habéis hecho scroll hasta aquí, os diré que podéis bajar el paquete desde playdeb.net o el instalador de la página oficial.

Y ahora, casi sin querer, me he topado con varios juegos de los que tengo un análisis pendiente.


Créditos: La captura de pantalla es de la página oficial.

Open-PC

Open-PC es un proyecto para comercializar un ordenador dotado únicamente con software libre, porque si bien cualquiera puede instalar Linux en un ordenador comprado (y pedir la devolución del impuesto del sistema por defecto), suelen incluir hardware cuyos fabricantes no han publicado drivers libres, o si lo han hecho no son funcionales.

El proyecto, promovido por la gente de OpenDesktop.org, pretende un PC suficiente para tareas cotidianas y de bajo consumo, OpenSUSE (modificado) con KDE4 listo para usar, por 364 euros, de los cuales 10 irán destinados al proyecto KDE.

Las especificaciones finales son:

  • Procesador Intel Atom N330 a 1.6 Ghz (procesador Atom de dos núcleos)
  • 3GiB de RAM
  • Disco duro de 160GiB
  • Intel GMA950 (la típica de Atom)
  • Carcasa mini-ITX y placa base ASRock con espacio para más componentes
  • Fuente de alimentación de 250 vatios

Ofrecen soporte técnico para la instalación a través de teléfono y email, pero sólo en inglés americano, al menos en principio.

El ordenador está listo para comercializar desde finales del mes pasado, si bien están buscando ensambladores que apoyen el proyecto. Pero por lo que veo, todavía no llevan muy bien lo de los distribuidores, así que no se sabe nada de envíos a Europa. Seguiré informando.

Más información en la página web del proyecto.

Abiword

Abiword es un potente procesador de texto, con soporte para complementos, diseñado especialmente para quienes estamos cansados de la mastodóntica proporción de OpenOffice en cuanto a su consumo de recursos. Como excusa, podríamos decir que OpenOffice está desarrollado en Java, cuyo intérprete es un devorador de RAM consumado, por motivos comerciales de Sun Microsystems, pero la verdad es que, si bien, usando GCJ (GNU Java) suele gastar alrededor de 20 megas de RAM menos, sigue siendo lento, pesado, y ciertamente feo.

Abiword por su lado, está desarrollado principalmente en C++, mucho más rápido y eficiente, tiene una interfaz sencilla, clara, y perfectamente integrada, es versátil y extensible con toda una serie de complementos, que añaden desde un imprescindible visor de fórmulas matemáticas, diccionarios, traductores, hasta edición colaborativa en línea usando el protocolo XMPP (que por cierto es estándar, en contra de ciertas cosas que hay por ahí).

Puede abrir y guardar en una gran cantidad de formatos, de echo, fué uno de los primeros en soportar OpenXML, famoso por ser el estándar ISO con más sobornos de la historia, y ciertos complementos agregan otros nuevos.

Si bien es cierto que en los repositorios oficiales de Ubuntu incluyen una versión realmente antigua, así como del cretácico superior, los desarrolladores de Abiword mantienen un repositorio Launchpad, que podréis añadir a vuestro gestor de paquetes con el siguiente comando:

echo "deb http://ppa.launchpad.net/abiword-stable/ppa/ubuntu `lsb_release -c -s` main" | sudo tee /etc/apt/sources.list.d/abiword-stable.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 2382D57E
sudo aptitude update
sudo aptitude install abiword

Para más información podéis pasaros por su wiki, la página web del proyecto, o por su página en el Launchpad.

Un saludo.

Python2.6: Generar strings aleatorios

El generar strings aleatorios es algo muy importante en implementaciones de todo tipo de sistemas seguros, y desde siempre me ha sorprendido que Python no disponga de un generador nativo de cadenas aleatorias cuando presumen de que viene con "pilas incluidas", refiriéndose a su extensa librería estándar.

Por otro lado, también es sorprendente lo poco que se ha tratado este tema, o al menos, lo difícil que es, relativamente, encontrar lugares en internet donde se trate este tema.

Dada la necesidad de encontrar el algoritmo óptimo, para implementar claves aleatorias en un servidor, mi primera implementación, basada en el código que encontré en un comentario del blog de Stas Ostapenko.

def simpleSample(chars, length):
return "".join( random.sample( chars*length, length ))

Pero me entró la duda de si existiría una forma más eficiente, de modo que hice la prueba con el siguiente código.

# Random string generation time
from time import time
import string
import random

def classic(chars, length):
# Classic
tr=[]
l = len(chars)-1
for i in xrange(length):
tr.append( chars[ random.randint(0,l) ] )
return "".join(tr)

def simpleSample(chars, length):
# derived from
# http://ostas.blogspot.com/2006/12/python-generate-random-strings.html#c6033016737183722956
return "".join( random.sample( chars*length, length ))

def choice(chars, length):
# http://ostas.blogspot.com/2006/12/python-generate-random-strings.html#c9062709590606913845
return "".join([random.choice(chars) for x in xrange(length)])

if __name__=="__main__":
chars = string.letters + string.digits
times = 100
print ("Random string generators comparison.\n" +
"Dictionary length: %s\n" +
"Times to test every algorithm: %s\n") % (len(chars), times)

for size in (8,16,32,64,128,256,512,1024):
print "String size: %s" % size
winnertime = None
winner = None
for i in (classic, simpleSample, choice):
tf = 0
tmin = None
tmax = None
for j in xrange(times):
t0 = time()
text = i(chars, size)
t1 = time()
t = t1-t0
tf += t/times
if not tmin or t < tmin:
tmin = t
if not tmax or t > tmax:
tmax = t
#print "Algorithm: %s\nTime: %s\nGenerated: %s\n" % ( str(i), t1-t0, text )
print " %s\n (%fs, min %fs, max %fs)" % ( repr(i), tf, tmin, tmax )
if not winnertime or tf < winnertime:
winnertime = tf
winner = i
print " Winner: %s (%f seconds)" % (winner, winnertime)

Los resultados serían, con mi AMD Sempron 1.6Ghz:

Random string generators comparison.
Dictionary length: 62
Times to test every algorithm: 100

String size: 8
<function classic at 0xb7683614> (0.000224s, min 0.000037s, max 0.018489s)
<function simpleSample at 0xb7683ae4> (0.000025s, min 0.000023s, max 0.000069s)
<function choice at 0xb7683b1c> (0.000149s, min 0.000018s, max 0.013031s)
Winner: <function simpleSample at 0xb7683ae4> (0.000025 seconds)
String size: 16
<function classic at 0xb7683614> (0.000187s, min 0.000070s, max 0.011462s)
<function simpleSample at 0xb7683ae4> (0.000035s, min 0.000033s, max 0.000070s)
<function choice at 0xb7683b1c> (0.000169s, min 0.000032s, max 0.013551s)
Winner: <function simpleSample at 0xb7683ae4> (0.000035 seconds)
String size: 32
<function classic at 0xb7683614> (0.000151s, min 0.000135s, max 0.001198s)
<function simpleSample at 0xb7683ae4> (0.000317s, min 0.000054s, max 0.026029s)
<function choice at 0xb7683b1c> (0.000064s, min 0.000062s, max 0.000098s)
Winner: <function choice at 0xb7683b1c> (0.000064 seconds)
String size: 64
<function classic at 0xb7683614> (0.000698s, min 0.000267s, max 0.017400s)
<function simpleSample at 0xb7683ae4> (0.000224s, min 0.000095s, max 0.008380s)
<function choice at 0xb7683b1c> (0.000268s, min 0.000116s, max 0.013866s)
Winner: <function simpleSample at 0xb7683ae4> (0.000224 seconds)
String size: 128
<function classic at 0xb7683614> (0.001032s, min 0.000529s, max 0.021555s)
<function simpleSample at 0xb7683ae4> (0.000198s, min 0.000182s, max 0.001264s)
<function choice at 0xb7683b1c> (0.000534s, min 0.000229s, max 0.013713s)
Winner: <function simpleSample at 0xb7683ae4> (0.000198 seconds)
String size: 256
<function classic at 0xb7683614> (0.001496s, min 0.001052s, max 0.014621s)
<function simpleSample at 0xb7683ae4> (0.000634s, min 0.000344s, max 0.017386s)
<function choice at 0xb7683b1c> (0.000928s, min 0.000451s, max 0.013740s)
Winner: <function simpleSample at 0xb7683ae4> (0.000634 seconds)
String size: 512
<function classic at 0xb7683614> (0.003289s, min 0.002106s, max 0.023516s)
<function simpleSample at 0xb7683ae4> (0.001210s, min 0.000693s, max 0.014664s)
<function choice at 0xb7683b1c> (0.001481s, min 0.000901s, max 0.011516s)
Winner: <function simpleSample at 0xb7683ae4> (0.001210 seconds)
String size: 1024
<function classic at 0xb7683614> (0.005419s, min 0.004200s, max 0.023496s)
<function simpleSample at 0xb7683ae4> (0.002135s, min 0.001389s, max 0.014853s)
<function choice at 0xb7683b1c> (0.002663s, min 0.001804s, max 0.013014s)
Winner: <function simpleSample at 0xb7683ae4> (0.002135 seconds)

Por otro lado, en una entrada de stakoverflow, mikelikespie aporta el código siguiente.

def rand1(leng):
nbits = leng * 6 + 1
bits = random.getrandbits(nbits)
uc = u"%0x" % bits
newlen = (len(uc) / 2) * 2 # we have to make the string an even length
ba = bytearray.fromhex(uc[:newlen])
return base64.urlsafe_b64encode(str(ba))[:leng]

Este último algoritmo desbanca sin dificultad a simpleSample, salvo con cadenas de 8 bytes (los cálculos iniciales no compensa en ese caso), en las mismas condiciones que los anteriores, pero que no permite controlar, exactamente, que diccionario de caracteres queremos para la cadena aleatoria generada, ya que el base64 de Python utiliza caracteres de la A a la Z (mayúsculas y minúsculas), dígitos del 0 al 9, / y + (o - y _ si especificamos que sea seguro para urls), y el carácter = como relleno.

No se con lo que me quedaré al final, pero supongo que la enorme diferencia de rendimiento del último podría compensar la falta de flexibilidad en determinados casos.

Control de versiones

Ejemplo de árbol de versiones

La gente que nos dedicamos al desarrollo de software requerimos constantemente el manipular varias versiones del mismo código, o colaborar activamente con otros desarrolladores en el desarrollo de las aplicaciones. En este último caso, puede darse comúnmente que varias personas escriban código incompatible entre sí, a causa de sobrescribir el trabajo de otros o trabajar sobre versiones antiguas de dicho código.

Para evitar esto, existen los llamados sistemas de control de versiones o de gestión de código fuente (SCM, de sus siglas en inglés), como SVN, GIT, CVS, Mercurial, etc.

Pero ¿Cómo funcionan? Resumiendo mucho, se podría decir que alguien crea un repositorio de código, y todos los programadores actualizan sus versiones desde allí, trabajan con el código, y lo vuelven a subir al repositorio.

Estos sistemas ya se encargarán de notificar posibles problemas de ediciones del mismo fichero (si no se han modificado las mismas líneas, el sistema detecta los cambios y los fusiona), intentos de subir versiones antiguas, o fusionar varias ramas de desarrollo paralelas.

A mí, personalmente, me encanta SVN, que es el sistema que ofrecen los servidores gratuitos (para código abierto) code.google.com, sourceforge.net, aunque también existen Git, con github.com, Bazaar con launchpad.com, sólo por citar unos ejemplos.

Para empezar a usarlos, deberemos crearnos una cuenta para dichos servicios, y descargar una aplicación cliente que se encargará de comunicar los ficheros que modifiquemos localmente con los del servidor. Mi preferida es, sin duda, RapidSVN. Configuraremos una copia local, en la que haremos Checkout para bajarnos los archivos, programar normalmente con nuestro editor de toda la vida, y cuando terminemos añadiremos los nuevos que hayamos creados y los subiremos, junto con los modificados, al servidor, usando la misma aplicación.

Aunque bueno, como muchas otras cosas, en la variedad está el gusto. De hecho, tanto NetBeans como Eclipse disponen de clientes SVN integrados, pero personalmente prefiero las aplicaciones sencillas que simplemente cumplen su cometido a los grandes del todo incluido.

Créditos: La imagen de las ramas ha sido robada vilmente de la Wikipedia.

Mechanical Tower

Hoy es viernes: viernes de vicio. Y por eso hoy os enseño el juego más adictivo, con diferencia, de todos los que he jugado nunca.

Mechanical Tower es un juego del tipo "defender la torre". Para ello dispondremos de créditos limitados, que se incrementarán mientras destruimos enemigos, que servirán para construir las defensas que se encargarán de eliminarlos. Ellos serán muchos, cada vez más numerosos, resistentes y, sobre todo, persistentes.

Conforme vamos avanzando y acabando con las oleadas de enemigos, podremos aplicar diversas mejoras a las trampas, dispondremos de más "pisos" que los enemigos deberán recorrer, y nosotros llenar de obstáculos, para acabar con ellos antes de que ellos lo hagan con nosotros.


Podéis descargarlo en su página web, listo para descomprimir y jugar (si os va lento, lanzadlo con --lowgfx, y usará efectos de partículas en vez de volumétricos para las explosiones).

Utilidades imprescindibles para desarrollo web

A la hora de meteros en el mundo del desarrollo web, muchos optan por grandes entornos de programación, otros nos contentamos con simples editores con resaltado de sintaxis, pero todos necesitamos de herramientas para comprobar errores en el código final, tanto en el CSS, XHTML e incluso Javascript que generen nuestros proyectos, y ahí entran en juego las siguientes utilidades.

Web developer toolbar (Firefox)
Web developer toolbar es una extensión que agrega una barra a Mozilla Firefox, con la que se puede validar el código, desactivar ciertas funcionalidades del navegador, facilitar el ver los elementos de la página... Muy útil para comprobar la calidad del resultado, la accesibilidad, y que el Javascript no es obstrusivo.
FireBug (Firefox)
La más famosa utilidad de este tipo. Permite analizar el código tanto HTML como CSS y editarlos en tiempo real, y hacer debug de Javascript usando puntos de interrupción, observando variables, etc. Además de poder ver y analizar, en tiempo real, las peticiones al servidor. Es un devorador recursos y hace algunas cosas de forma poco ortodoxa, pero es un imprescindible.
Developer tools (incluido en navegadores webkit)
Dependiendo del navegador puede funcionar bien o no, pero pasa a ser, junto con FireBug, una de las herramientas más potentes, con análisis y edición del marcado y sintaxis de HTML y CSS, además de un completo debugger para Javascript (aunque yo sigo prefiriendo el de FireBug). También dispone de análisis de peticiones a servidor, cookies, etc.

Además, no puedo dejar de recomendar las siguientes utilidades web para ayudaros a trabajar con javascript.

Javascript Shell y Javascript Development Environment
Un intérprete interactivo para javascript, cuyo bookmarklet lo abre utilizando la página actual como ámbito, y un editor de Javascript, algo limitado, pero que permite ejecutar directamente el código editado en el intérprete del navegador.
JSLint
Utilidad que valida la calidad del código javascript, comprobación de errores, e incluye un modo de calidad de la sintaxis.

Eventos Javascript multinavegador

Editado 15.03.2010: He eliminado el soporte específico a navegadores no estándares. No obstante, en dichos navegadores seguirá funcionando gracias al código general para navegadores antiguos. Además, se ha añadido una función para facilitar el añadir eventos que sólo se ejecuten una vez (ver más abajo). Las descargas están actualizadas.

Siempre que me dedico a diseñar interfaces asistidas con javascript, me encuentro con el mismo problema: ¿cómo manejar los eventos eficientemente y mantener la compatibilidad multinavegador?

La solución sencilla, y la más compatible1 (DOM 0), consiste en los atributos de evento, como onclick u onmouseover, pero tiene serias limitaciones: sólo admite una función (aunque podemos concatenar varias), y si optamos por concatenar funciones ni podríamos eliminar alguna, aparte de que la cancelación de eventos con return se vuelve una pesadilla.

Sin embargo, los navegadores estándares (motores mozilla, webkit, khtml...) disponen de elemento.addEventListener( nombre_evento, función, capturar ), que simplifica mucho el proceso de asignar varias funciones y elegir si capturan el evento por completo con un booleano, junto con elemento.removeEventListener( nombre_evento, función, capturaba ) para borrar funciones asignadas al elemento.

Por otro lado, navegadores no estándares2 (motor trident) recurren a elemento.attachEvent( onevento, función ), que funciona como addEventListener pero con nombres de eventos de HTML, y no permite establecer si se captura el evento o no, y para eliminar la función del evento tenemos elemento.detachEvent( onevento, función).

Crear una función para simplificar el proceso de asignación y desasignación de funciones a eventos, que sirva para los tres casos anteriores, nos lleva a renunciar a la posibilidad de definir si el evento se captura al momento de asignarlo, pero aún queda la posibilidad de capturar el evento dentro de la función asignada.

Pero de nuevo, existe una forma estándar y varias no estándar de capturar el evento, además de que los navegadores más antiguos sólo lo detendrán si la función asignada retorna false. En este último caso tendremos la fortuna3 de que dichos navegadores tampoco aceptan las formas modernas de asignación.

Teniendo todo esto en cuenta, he malgastado unas preciosas horas de mi vida en diseñar tres útiles funciones auxiliares (que empezaré a usar en todos lados). Podéis descargar este mismo código (3.2 KiB) a o la versión minificada (1 KiB).

/* Mi función auxiliar preferida, recibe un nombre de propiedad
* y un objeto, y retorna true o false dependiendo de si existe en
* el objeto o no. Si no se recibe un objeto se asume window */
function def(p,o){return (typeof((o||window)[p])!="undefined");}
function attEvent(o,e,f){
/* Función de asignación de evento, recibe objeto, nombre de evento
* (p.ej. click, no onclick) y función de callback */
var w3="addEventListener",a="attachedEvents",cp="cancel",nf,of,nr;
if(def(w3,o)){o[w3](e,f,false);} // Estándar
else{ // Funcionalidad para navegadores antiguos
if(!def(a,o)){o[a]={};} // Objeto auxiliar
if(!def(e,o[a])){o[a][e]={};} // Objeto evento
if(!def(f,o[a][e])){ // No agregar dos veces la misma función
nf=function(){
/* Detección de funciones bloqueadas por detEvent:
* si ha sido bloqueada, no se ejecuta la función */
if(o[a][e][f]){f.apply(this,arguments);}
};
nr=function(v){
// Retorna false si se ha ejecutado stopEvent
return !(def(cp,(v||window.event))&&(v[cp]==true));
};
if(o["on"+e]){
/* Si hay funciones previamente asignadas, asignamos
* una función que las ejecutará junto con la nueva */
of=o["on"+e]; // Funciones previas
o["on"+e]=function(){
// Ejecutamos:
of.apply(this,arguments); // 1. funciones previas
nf.apply(this,arguments); // 2. nueva función
// Retornamos false si se ha ejecutado stopEvent
return nr.apply(this,arguments);
};
}
else{o["on"+e]=nf;} // Agregar la primera función
}
/* Asignamos 1 en la función para el evento, si el valor
* pasa a ser 0 (por detEvent), la función no se ejecutará */
o[a][e][f]=1;
}
}
function attEventOnce(o,e,f){
attEvent(o,e,function(){
f.apply(this,arguments);
detEvent(this,e,arguments.callee);
});
}
function detEvent(o,e,f){
/* Función de desasignación de evento, recibe objeto, nombre de
* evento, y función a eliminar (o bloquear en el último caso) */
var w3="removeEventListener",a="attachedEvents";
if(def(w3,o)){o[w3](e,f,false);} // Estándar
else if(def(a,o)&&def(e,o[a])&&def(f,o[a][e])){
// Modo para navegadores antiguos (no borra, sólo desactiva)
o[a][e][f]=0;
}
}
function stopEvent(e){
/* Función de detención de eventos, recibe la instancia del evento
* por parámetro. */
var w3="stopPropagation",mz="preventDefault",e=(e||window.event);
/* Para navegadores que cumplen los estándares */
if(def(w3,e)){e[w3]();}
/* Evitamos la acción por defecto (mozilla) */
if(def(mz,e)){e[mz]();}
/* Para navegadores antiguos y no estándares: establecemos cancel y
* cancelBubble a true, returnValue a false, y retornamos false
* por si queremos usarlo en eventos asignados directamente. */
return e.returnValue=!(e.cancel=e.cancelBubble=true);
}

Usando estos métodos, agregar varias funciones al mismo evento sería bastante simple.

attEvent(window,"load",function(){alert("Primera vez.");});
attEvent(window,"load",function(){alert("Segunda vez.");});

O incluso podríamos asignar una función que se desasignara a sí misma y detuviera el evento cuando se ejecutase.

attEvent(document,"click",function(e){
alert("¡Cliclí por primera vez, capturado!");
// Desasignamos la propia función (callee) del elemento (this)
detEvent(this,"click", arguments.callee);
// Detenemos el evento
stopEvent(e);
});
Editado 15.03.2010: Ahora existe la posibilidad, a petición popular, de añadir eventos de un sólo uso de forma más sencilla: con attEventOnce.

attEventOnce(document,"click",function(){
alert("¡Cliclí una única vez (sin capturar)!");
});

Sí, ya sé que frameworks mastodónticos, como jQuery o Mootools, ya han ideado formas de hacer todo esto, listas para usar, pero ¿qué tiene eso de divertido?


  1. Hay que decirlo: Chrome "se olvida" de los onsubmit de los formularios.
  2. Es tan obvio cuál que, tal cual, obviaré su nombre.
  3. Muy relativo todo, eso sí.

Créditos:

Febrero ha acabado

Ha finalizado el mes febrero, y con él un pequeño experimento: publicar una entrada diaria.

Después del cierre en enero, me pregunté si las estadísticas variarían sensiblemente multiplicando por 7 el número de entradas publicadas al mes, con el aliciente de tener contenido nuevo todos los días.

Puede parecer lógico, sobre el papel, el aumento, al fin y al cabo a mayor contenido mayor posibilidad de ser enlazado y más páginas en los motores de búsqueda. Pero teniendo en cuenta que el 90% de las visitas nuevas vienen buscando los tutoriales, razón por la cual mantengo un índice, me preguntaba si dicho porcentaje variaría. Y no lo ha hecho (salvo por el puntual meneo de Conxura).

Conclusión: Publicar juegos, programas, consejos, y artículos sobre programación (sección recién estrenada) no incrementan la relevancia del blog. Los tutoriales sí, lo que lleva a otra pregunta.

¿De verdad hay tanta gente con problemas?

La frase

Lo importante es no dejar de hacerse preguntas.

(Albert Einstein)

Gmail registra y censura tus adjuntos

Google lo ha hecho: me ha sacado de mis casillas.

Intentando mandar un fichero de Abiword adjuntado por correo electrónico, el destinatario me avisó de que no le estaban llegando. Reenvié, igual, ningún mensaje de error, y no llegaban (luego descubrí que la culpa es de Chromium).

Meto el fichero .abw en un zip, envío, y ¡tachán! mensaje de error.

¡Que no me deja enviar un fichero adjunto! ¡Que han registrado el fichero y creen que un documento de abiword es un ejecutable de windows! ¡Que para proteger a los idiotas que usan ese sistema inseguro no me dejan enviar lo que me sale de mis santos cojones!

Supongo que aunque sea inevitable que los de la NSA, la CIA, y demás registren nuestros correos, pero que algún lumbreras crea que nos protege con un algoritmo que registre mis adjuntos (y confunda un fichero de abiword con un virus) me parece impresentable. Yo alucino.

Conclusiones:

  • Abiword es el enemigo público número uno, hay que bloquearlo a toda costa, y Google viene a protegernos.
  • Es hora de empezar a cifrar los correos con PGP. Y yo que tachaba de paranoico a más de uno...
  • Como decían en Los Justicieros: Quien a buen árbol se arrima, buena horca le cobija.

PD: Y sí, ya les envié un reporte nada agradable quejándome de lo sucedido.