Sitemaps vía crawling

Hoy me pidieron agregar un Sitemap para uno de los trabajos que hicimos para el gobierno y me encontré con que los plugins que uso para esta tarea no me cerraban de forma cómoda. El problema es que este sitio tiene, además del contenido dinámico, muchas páginas estáticas que no puedo referenciar desde un modelo, por lo que debía forzarlas y era bastante molesto.

Buscando encontré una solución práctica para este caso (donde hay pocas páginas, menos de 1k) que usa un crawler para recorrer todo el sitio y obtener las URLs a agregar al sitemap. El script que presentan me sirvió, aunque tuve que hacerle algunos cambios menores.

El primer problema que tenía era que me agregaba páginas que no deben ir en un sitemap (ni ser indexadas) como las de login, recuperar clave, form de registración, etc. Por lo que tuve que modificar ligeramente el código para no seguir los enlaces que estuvieran marcados con rel="nofollow" y para eso modifiqué en el método extract_and_call_urls la última línea como sigue :

links.each{ |link|
   extract_and_call_urls(link.href) unless
      !can_follow?(link) || ignore_url?(link.href) || 
      @visited_pages.include?(link.href) 
}

Y definiendo el nuevo método :

 def can_follow?(link)
   return false if link.nil? ||
   (link.attributes["rel"] && link.attributes["rel"].include?("nofollow"))
   
   true
 end

Entonces, cuando el crawler encuentra un enlace que el developer marcó que no debe seguirse en una indexación (esto es principalmente para los crawlers de los search engines) se ignora y no se agrega al sitemap.

El otro cambio menor fue que tenía algunas URLs con el path completo y por default siempre me agregaba al inicio el domain name, por lo que me quedaban URLs inválidas, por lo que hice la siguiente modificación :

# Antes
xml.loc(@starting_url + url)

# Después
xml.loc(url.include?(@starting_url) ? url : (@starting_url + url))

Una vez probado el script hice una tarea rake para poder correrla fácil desde un cronjob :

# lib/tasks/sitemap.rake
require 'lib/crawler'

desc "Generate the sitemap file"
task :sitemap => :environment do
  start_url = ENV["URL"] || "http://localhost:3000"
  Crawler.new(start_url, (ENV["CREDS"] if ENV["CREDS"]), ENV["QUIET"] || false, ENV["SITEMAP"] || false, ENV["DEBUG"] || false)
end

Y listo, lo último fue hacer un deploy y configurar un cron.dayli para que cree el sitemap actualizado :

rake sitemap URL=http://www.haciendoelcolon.buenosaires.gob.ar SITEMAP=true

Así una vez por día se actualiza el sitemap y se hace un ping a google para que sepa que debe pasar a reindexar el contenido.

Esto tiene varias desventajas (pero aún así para este sitio sirve a su propópito) :

  • No se puede priorizar cada tipo de contenido fácilmente
  • La fecha de última modificación es inexacta
  • Carga el webserver para generar el sitemap

Código completo : crawler.rb

Cómo aprovechar una crisis para hacer publicidad

Simpático mail titulado «Gripe A: Microsoft facilita tecnologías» me llegó hoy de lo que supongo es algún vendor local de productos de MS (cliente(at)mailmicrosoft.com). No hay mucho que decir, me hizo gracia y acá parte del mail :

ms_crisis

El resto del mail es una enumeración de las cosas online que podés usar gratuitamente, por ahora, para poder trabajar con otra gente sin contagiarte de Gripe A N1H1 :).

¡Qué haríamos sin empresas como Microsoft! (¡sarcasm detected!)

Take A Photo – Demo

Hace unos meses publiqué un plugin para tomar fotos instantáneas desde una página web, usando Flash y un plugin para Ruby on Rails.

Hoy me puse un rato a jugar y armé un demo donde se pueden tomar fotos (ojo que todo es público :D) : TAP.

Las fotos se borran una vez por día y acepta un máximo de 50 fotos por día (para evitar que me llenen el disco). Tengo algunos TODOs que iré viendo de resolver en los tiempos libres (como cambiar el tamaño de la foto, recortar, aplicar efectos, etc).

Necesita Flash 9 o superior y una web cam para que tenga sentido :). Todavía sigo peleando con el Flash player de Linux y la camarita, sorry :S.

Les recomiendo verlo en Firefox, no me gasté en ver si en IE el CSS no se rompe y no creo que lo haga nunca.

JoinPoint Language Definition

Luego de unos 15 días intensos sobre mi tesis ya tengo andando una pequeña parte andando de lo que será la nueva API, funcionando e integrada con el JIT.

Antes de comentar como funciona vamos por el primer ejemplo que consta de dos partes. Por un lado una clase común y corriente que simularía ser la aplicación :

using System;
public class Normal
{
    public Normal()
    {
        Metodo1(26);
    }

    public void Metodo1(int i)
    {
        Console.WriteLine("Aca");
    }

    static public void Main(string [] args)
    {
        new Normal();
    }
}

Y por otro una clase que solo tiene definiciones de JoinPoints (pueden estar mezcladas, no hace falta separarlo)

using System;
using Weaving;
public class Unused
{
     [RunBefore, JoinPoint("void", "Normal", "*", ParameterFlag.MATCH_ANY)]
     public void Logger(int i, Context c)
    {
        Console.WriteLine ("Called Before with param "+i.ToString());
    }
}

Lo importante de esta segunda clase son los Attributes que se le agregan a los métodos (en adelante los llamaremos callbacks). RunBefore lo que le indica es cuándo se debe correr el callback : en el ejemplo significa que debe ser antes de la ejecución del JoinPoint (sería el equivalente del BeforeAdvice de AspectJ).

El Attribute JoinPoint lo que hace es materializar el JoinPoint en el JIT, para que el RunBefore lo pueda encontrar. Los parámetros que recibe son (cualquiera de las regex soporta los wildcards ‘*’ y ‘?’) :

  • Return Type regex
  • Class Name regex
  • Method Name regex
  • Flags

Para completar el regex contra el que se matchea, el Weaver toma los argumentos del Callback, en este caso lo que se intenta es anticipar la llamada de cualquier método de la clase Normal que reciba un int como primer parámetro y cuyo return type sea void. El Flag MATCH_ANY indica que pueden haber otros parámetros, pero son ignorados por el callback. Si especificáramos MATCH_EXACT estaríamos buscando un método que tenga exactamente un argumento de tipo Integer.

El útimo parámetro (Context) no se usa para el matching y es donde el callback recibe información del contexto sobre donde se está ejecutando (instancia interceptada, método interceptado, etc).

Si compilamos y ejecutamos el ejemplo veríamos algo como lo que sigue :

[mono] ~/src/ejemplos @ gmcs -out:Normal.exe /r:Mono.Weaving.dll Normal.cs Unused.cs 
[mono] ~/src/ejemplos @ mono Normal.exe
(1) [JIT] class Weaver loaded
(2) [CS] ASSEMBLY LOADED: Normal, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
(3) [JIT] New pattern Defined : void Normal:* (int)
(4) [JIT] Matched void Normal:Metodo1 (int) with void Normal:* (int)
(5) [JIT] No instance object found. We need to create one
(6) [JIT] I'll run callback Unused:Logger (int,Weaving.Context) over instance pointed at 0x62f90
Called After with param 26
Aca

Como se puede apreciar, antes de ejecutar el método Metodo1 se ejecutó el callback y recibió correctamente el parámetro original. Cabe aclarar que el weaving se está haciendo a nivel del JIT, intercalando el assembler generado para la plataforma donde se ejecuta, como expliqué en el pasado.

Paso entonces a describir un poco como funciona. Para empezar hay 2 partes, una en código manejado y otra en el JIT (CS y JIT en el output). La parte manejada se usa solamente para detectar cuando un nuevo Assembly es cargado, via AppDomain.CurrentDomain.AssemblyLoad, y ahí por Reflection se leen todas las clases que hay en el Assembly, se escanean los metodos buscando los joinpoints y se llama un método interno del JIT que es quién finalmente define el nuevo joinpoint en la VM.

Esto esta así porque averiguando como hacerlo directo en C encontré varias referencias en la lista de Mono de que el loader en C es muy complejo (haciendo un trace pasé como por 20 funciones).

Como el weaver debe existir antes de ejecutar la aplicacion, el JIT carga el Assembly Weaving.dll desde el GAC antes de cargar el .exe que se va a ejecutar y justo después de cargar el corlib (que debe ser cargada primero que nadie). Luego una instancia de Weaving.Weaver y la mantiene viva por el resto del ciclo de vida de la aplicación.

Cuando el Loader detecta el Attribute de JoinPoint se llama a Weaver.DefineMethod que se mapea a una
función en C. Ésta es la que emite el mensaje (2) creando la regex y guardando toda la metadata necesaria para ejecutar el matcher después.

Otra tarea del loader es detectar si la sintaxis del callback es correcta, por ahora se valida lo siguiente :

  • Context no definido : Advice need at least one parameter of type Weaving.Context
  • Context de tipo inválido : Advice last parameter type XXXXX. Expected Weaving.Context
  • Callback no puede ser abstracto : Abstract Method can’t be advices
  • Callback no puede ser un constructor : Constructors can’t be advices

Cabe destacar que estas exceptions no pueden ser capturadas en un try/catch y hacen que el programa aborte con un backtrace en la consola.

Una vez inicializado y detectados los JoinPoint, a medida que la aplicación es ejecutada, para cada método ejecutado se hace un scan de los patterns definidos y se testea contra la regex de cada uno, si alguna matchea arma la lista de parametros para el callback (tomando los valores originales con los que fue llamada la función interceptada).

El callback tiene 2 comportamientos, dependiendo si el metodo es static o no. Si es STATIC, se ejecuta como STATIC de manera que es el mismo callback para cualquier match. Esto da un comportamiento similar al Singleton de AspectJ (ideal para un logger por ejemplo).

Ahora, si el callback no es STATIC (como en el ejempo) por cada Signature que matchea se crea una instancia diferentes sobre la cual llamar el callback (ideal para un profiler, para saber cuantas llamadas tiene cada método, cada uno con su counter propio).

Obviamente en los callbacks aplican las mismas restricciones que lo métodos comunes : los statics solo pueden acceder a miembros statics de la clase y los no statics los miembros de esa instancia.

Y más o menos de eso se trata mi tesis :). Aún quedan muchas cosas por pulir, mejorar y documentar. La extracción de parámetros desde el stack no es trivial aunque por suerte el profiler de Mono hace algo parecido y calculo que basándome en ese código pueda sacarlo fácil.

Migración de Zimbra

Este fin de semana me puse como objetivo migrar el mail server de la empresa que ya estaba haciendo agua. Corría sobre un server con un disco que daba errores de lectura, no había RAID y la última semana hubo un 90% de uso de CPU en I/O :).

Arranqué el sábado al medio día y fue un día perdido. Tuve la loca idea de respetar la arquitectura del nodo para crear la VM y montar Zimbra en 64bits. Resulta que migrar el mail server a un nuevo servidor requiere de mucho matching entre el origen y el destino, por lo que fue prácticamente imposible hacerlo. Recapacité por la noche y el domingo fui por un aproach más conservador :).

Lo primero a tener en cuenta para migrar este productor (que es all-in-one-fucking-package) es aceptar que hay que usar una de las distros soportadas, y no tratar de hacer magia (al menos para no sufrir y que salga andando).

El proceso básico está en este post y no parece complicado, pero hay varios obstáculos que pasar. Lo primero, es que hay que hacer una instalación dummy de zimbra con la misma versión que teníamos antes. Mi problema era que tenía una versión muy vieja de Zimbra y solo había paquetes para Debian y Ubuntu 6.06, pero yo quería al menos Ubuntu 8.04.

El truco que usé fue editar /etc/debian_version y ponerle lo que espera y pasó sin problemas (calculo que por mero azar :D).

Una vez eso el resto fue copiar con rsync de un server a otro (25Gb tardan MUCHO, pero MUCHO!) y ejecutar el installer de la nueva versión, que tardó también como 6 hs en actualizar las tablas de MySQL. Esto último creo que fue causa de que el logger en el server viejo no estaba limpiando después de procesar, por lo que había muchos logs y eso hizo que demore tanto.

La migración fue perfecta, sin errores, pero teniendo problemas con el LDAP. Confieso que  odio ldap e iba a decir «hasta acá llegué, lo dejo» pero apareció rápidamente en el wiki la solución : regenerar los certificados. Anduvo en el primer intento.

Cosas a tener muy en cuenta por si les toca :

  • El nuevo server debe tener el mismo Domain Name y MX record a puntados (la ip puede cambiar, pero el DNS debe estar actualizado)
  • Bloquear el puerto 25 mientras se migra, asi en el nuevo server no empiezan a entrar mails hasta que no estemos seguros de que anda
  • Hacer backup antes de borrar algo 🙂
  • Usar solo las distros/versiones soportadas, ahorra miles de dolores de cabeza
  • Planificar con tiempo la copia de archivos, puede tardar más de lo que uno cree 🙂

Un tiempo después

No, no voy a comerntar sobre el aburrido programa de «Solita» :D, solo que hoy estuve arreglando unos bugs de Oregano y me di cuenta que habían pasado ya unos 18 meses desde el último commit que hice!

En fin, mucho tiempo, pero ya tengo un lindo TODO de cosas a ir haciendo, con muy baja prioridad por la Tesis, pero que quizás para mi cumple esté para hacer otro release, esperemos no colgarme de nuevo.

Mientras tanto se arreglaron dos bugs molestos :

  • Se arregló el export a PNG con fondo transparente
  • Se implementó finalmente la opción de exportar en escala de grises

oregano_grayscale

Nueva Casa (x2)

Si están leyendo esto significa que los DNS ya se actualizaron world-wide (o al menos el que usen :D) ya que después de casi 3 años finalmente le liberé a Sebas la máquina que me tenía rackeada en mi viejo trabajo :).

En estos día seguramente me ponga a migrar a WordPress 2.7 con la esperanza que no se rompa nada, pero quedan avisados (sobre todo los planets, espero que no floodee por algun bug en las feeds como pasó otra veces).

La otra noticia es que ya conseguí casa en el sur y a fin de mes me voy a firmar el contrato de alquiler. Un pasito más hacia mi emigración de Capital.

El diario Escondido

Luego de muchos meses de mucho trabajo, remasterización, ajustes, pruebas y demás, finalmente pudimos terminar y poner online una de las campañas más ambiciosas que hemos hecho hasta ahora : El diario Escondido.

httpv://www.youtube.com/watch?v=MUI7FbMoNDw

Hasta el día del lanzamiento solo podrán ver el trailer, si lo juegan me gustaría que después se pasen y dejen opiniones sobre qué les pareció :).

PD: No, no estoy autorizado a decirles como pasar, sumarles puntos extra o hacerlos subir de ranking :D.

Expo2009 @ LugMen

El fin de semana que viene voy a estar en Mendoza para participar de la Expo2009. Cada vez que he ido a esa ciudad la pasé muy bien y estoy seguro que será así, más aún cuando está de por medio el cumpleaños de Vita y Boris trae Pisco desde chile :).

Por mi parte voy a estar dando una charla bien careta «1, 2, 3 Probando. Acercando BDD a las masas.» sobre testing y metodologías. Hay que seguir robando y Mono ya no daba para mas :).

En el programa no hay demasiadas cosas que me interesen pero como siempre voy a copartir con la gente mas que para aprender algo, ver amigos, tomar mucha cerveza y compartir un finde distinto.

Ad Effectum Vivendi

Estos días al pedo y mientras mi novia trabajaba me puse a revolver la biblioteca y encontré este simpático libro. El tema principal es exponer las burradas que aparecen en las causas judiciales que su autor, Osvaldo Monroy, ha visto a lo largo de los años trabajando en los juzgados de General Roca.

El libro es cortito, unas 90 páginas, y está separado en varias partas : historias, errores graciosos, lo que puede pasar cuando aparece una falta de ortografía en un escrito y un glosario de significado de nombres.

Veamos algunos ejemplos de burradas que escriben los amados abogados en su vorágine de copy & paste (las notas son del libro, no mias:):

«Siendo falso que realizara cualquier otro tipo de actividad que en su demanda identifica como ‘etc’.». Nota: etcétera significa literalmente «lo que sigue» :).

«No habiendo aceptado el cargo el perito médico, pidió que se le renueve en el cargo». Nota: otra oportunidad!

«Sr Cristian Mansilla, Juan Jose Gomez s/n, PB, General Roca». Nota: El 98% de las viviendas del barrio Juan Jose Gomez son solo de planta baja.

Algo de glosario judicial (o mas bien burradas mal escritas):

  • Ad effectus vivendi : Filosofía de vida que encuentra en la risa diaria la fortaleza que vence el acartonamiento gris que nos liquida.
  • Contradictoro : Dicese del bovino que padece de indecisión
  • Certificación de servios : Constancia vital para algunos habitantes de la ex-Yugoslavia.
  • Inscripoto : Muy mal inscripto
  • Up supra : Término compuesto anglo-latino que significa: arriba-arriba (Nota mia : el correcto es «ut supra»)

Y por último algunos nombres (son reales!, aparecen en las actas de audiencia!) y sus significados :

  • Aristo : El de varias caras y perfil bajo
  • Bennavel : Tomado del grito convicante japonés utilizado cuando aparecía algo digno de verse.
  • Isaldo : Semidios de la mitología contable
  • Lentijo : Apelativo con que se inscribe frecuentemente a los varoncitos de Santiago del Estero
  • Offman : Apagado. El que no se prende en ninguna
  • Celinda : Expresión de deseo paterno ante la llegada de la esperada chancleta
  • Vedita : Disminutivo. La que se abstiene por poco tiempo.
  • Zacarías Paredes : El demoledor
  • Ana Aleja Coronel : La Señora Democracia
  • Amada Luz Marina : Faro
  • Moisés Tiznado : Pelé (este me costó entenderlo :D)
  • Máxima Negrete : Full black

En fin, me pareció muy gracioso el libro, hay varias historias muy largas como para transcribir, pero si se cruzan con este libro alguna vez, peguenle una mirada :).