El Papa, Juan Pablo II ha muerto

El Papa Juan Pablo II ha muerto. Que en paz descanse.

Aún recuerdo cuando nos visito en Mérida, Venezuela, hace años atrás y el “niño prodigio” Adrian Guacaran le dedicó una canción al papa peregrino (eso no fué en Mérida)…

La Iglesia en donde se casó mi hermana, en La Hechicera Mérida, fué construida para su visita. Muchos vendedores ambulantes prepararon comida, pero la gente no compró ya que se trataba de un evento de fé y millones se perdieron.

Sólo les cuento anecdotas, ya que cualquier otra cosa no le haría justicia a este personaje.

Echando código: Más de Flickr con Java y XML-RPC

Bueno, después de resolver mi problema usando Perl y al API de Flickr decidí hacerlo ahora con Java; Pero en esta ocasión quise irme a bajo nivel ya que siempre me ha llamado la atención el protocolo XML-RPC. Para procesar las peticiones y respuestas de XML-RPC utilizaré el proyecto de Jakarta XML-RPC.

Decidí empezar despacio, y como es la primera vez que uso el API, sólo quiser obtener mi identificador de usuario numérico usando mi nombre registrado (en esta caso josevnz). Lo primero que hay que hacer es familiarizarse con el uso de los tipos de datos de XML-RPC y su correspondientes tipos en Java. Una vez hecho esto y viendo el tipo de salida que nuestra librería en Java debería generar podemos empezar con el código:

   1:package com.blogspot.elangelnegro.flickr;
2:
3:import java.util.ResourceBundle;
4:import java.util.Properties;
5:import java.util.Vector;
6:import java.util.Hashtable;
7:
8:import java.net.URL;
9:
10:import java.io.IOException;
11:
12:import org.apache.xmlrpc.XmlRpcClient;
13:import org.apache.xmlrpc.XmlRpcException;
14:
15:/**
16: *
17: *
18: *
19: *
20: * For more information, please check the following URLS:
21: * <ul>
22: * <li> <a href="http://www.flickr.com/services/api/misc.urls.html">Flickr URL format</a>
23: * <li> <a href="http://www.flickr.com/services/api/">Description of the API</a>
24: * <li> <a href="http://www.flickr.com/forums/help/5304/">The problem I had to solve :)</a>
25: * <li> <a href="http://ws.apache.org/xmlrpc/client.html">Apache XML-RPC</a>
26: * </ul>
27: *
28: */
29:public final class FlickrBackup {
30:
31: private static final ResourceBundle BUNDLE =
32: ResourceBundle.getBundle(FlickrBackup.class.getName());
33:
34: private static final Properties CONFIG = System.getProperties();
35:
36: /**
37: *
38: * @param args Command line args
39: * <ul>
40: * <li>args[0] Is the Flickr user
41: * <li>args[1] is the password for that account
42: * <li> args[2] is the directory where we will save all the photos
43: * </ul>
44: * @throws Exception if there is any error while downloading the pictures
45: */
46: public static void main(String [] args) throws Exception {
47: Vector params = new Vector();
48: Hashtable struct = new Hashtable();
49: XmlRpcClient xmlrpc = null;
50: try {
51: xmlrpc = new XmlRpcClient(
52: BUNDLE.getString("com.blogspot.elangelnegro.flickr.FlickrBackup.xmlrpc.url")
53: );
54:
55: struct.put(
56: "api_key",
57: BUNDLE.getString("com.blogspot.elangelnegro.flickr.FlickrBackup.xmlrpc.key")
58: );
59: struct.put(
60: "username",
61: args[0]
62: );
63: params.add(struct);
64: String result =
65: (String) xmlrpc.execute ("flickr.people.findByUsername", params);
66: System.out.println(
67: BUNDLE.getString("com.blogspot.elangelnegro.flickr.FlickrBackup.main.messages") +
68: ": " +
69: result
70: );
71: } catch (XmlRpcException xmlexp) {
72: throw xmlexp;
73: } catch (IOException ioexp) {
74: throw ioexp;
75: } finally {
76: struct.clear();
77: params.clear();
78: }
79: }
80:
81:}

Y el correspondiente archivo de propiedades:

   1:com.blogspot.elangelnegro.flickr.FlickrBackup.xmlrpc.url=http://www.flickr.com/services/xmlrpc/
2:# Old key: 24e618762a874d9ff507d255dd95f854
3:com.blogspot.elangelnegro.flickr.FlickrBackup.xmlrpc.key=XXXXXX
4:# Connecting as
5:com.blogspot.elangelnegro.flickr.FlickrBackup.main.messages=Connecting as

La corrida de ejemplo es:

[josevnz@localhost FlickrBackup]$ java -jar dist/FlickrBackup-1.0.jar sdelmont
Exception in thread “main” org.apache.xmlrpc.XmlRpcException: User not found
at org.apache.xmlrpc.XmlRpcClient$Worker.execute(XmlRpcClient.java:457)
at org.apache.xmlrpc.XmlRpcClient.execute(XmlRpcClient.java:163)
at com.blogspot.elangelnegro.flickr.FlickrBackup.main(FlickrBackup.java:64)
[josevnz@localhost FlickrBackup]$ java -jar dist/FlickrBackup-1.0.jar novato
Connecting as:

Novato

[josevnz@localhost FlickrBackup]$

Hmmm, no me devuelve una estructura sino el XML. Me sale usar un DOM para procesar el XML.

Por cierto, buscando en Google, me conseguí esta presentación de Power Point y Flickr. Realmente interesante, no importa si usted es un desarrollador o un administrador de redes ya que tiene para satisfacer la curiosidad de todos. Definitivamente Flickr es la compañia de la cual todo el mundo está hablando (después de Google) y ahora que Yahoo la compró, su futuro financiero está asegurado.

No pienso colocar el código esta vez. Voy a completarlo y pienso mostrar la aplicación completa en cuanto la tenga lista.

Trucos Unix: ¿Como revizar si los módulos de un grupo de scripts en Perl están instalados?

Bueno, un buen amigo en la compañia me llegó con el siguiente problema (bueno, más bien yo de metido le pregunté a la hora de el almuerzo):

Yo: Epale chamo, ¿nos vamos a almorzar?. Me estoy pudriendo de el hambre…
Pana: Dame un chance, resulta que ‘fulanito’ se fué de vacaciones y dejó un montón de scripts en este servidor y no sé si los terminó de instalar.
Yo: No entiendo, ¿qué te falta por revizar?
Pana: Bueno, para empezar no sé si todas las dependiencias están allí….
Yo: Nada pana, dejame enseñarte un truco bien pendejo y así nos vamos a almorzar rápido

Si usted recuerda, en Perl usted puede validar si un script tiene la sintaxis correcta así:

perl -c script.pl

Pero además de eso, Perl va a revizar si los módulos requeridos está instalados

Así que lo que le dije al pana fué que buscara con ‘find’ todos los scripts de Perl y que los revice uno por uno:

find ./directorio -name ‘*.pl’ -type f -exec perl -c {} \;

Al rato nos fuimos a almorzar, una vez comprobado que todos los scripts estaban bien, en menos de 5 minutos 🙂

Echando código: Haciendo data mining con PostgreSQL y Java, Parte II

En un articulo anterior yo les comentaba como hacer parsing the una bitacora de un servidor web Apache utilizando Java y PostgreSQL; También les comentaba que con SQL tenemos una flexibilidad increible para obtener y manipular los datos. Sin embargo, para que la presentación de los datos luzcan un poco más profesionales deberíamos usar un generador de reportes; De esa manera trabajamos menos y podemos repetir los resultados con otro tipo de reportes.

¿Porqué utilizar un generador de reportes? La razón más sencilla es que usted tiene cosas más importantes que hacer que generar un reporte que se vea bonito, usted debe concentrarse en solucionar problemas como administrador; Por otro lado, usando un generador de reportes usted puede fácilmente controlar el aspecto de sus datos (convirtiendolos en información) mientras utiliza lo obtenido ya sea para solucionar problemas o para comunicarle algo importante a sus clientes en un lenguaje que ellos puedan entender.

Empecé evaluando varias aplicaciones. La primera que traté de usar se llama ‘DataVision‘.

Yo tuve unos cuantos problemas usandolo con PostgreSQL y JDBC:

  • Manejo de el tipo de datos CIDR. No es texto, así que si quieren ver la dirección IP de la máquina en cuestion, deberán convertirlo a texto utilizando la función host (por ejemplo: select host(ip_addr) from http_log). Para más información, vean aquí las rutinas de manipulación de datos de tipo CIDR.
  • Bajese la última versión del manejador de JDBC para su base de datos. Yo tuve problemas con una versión vieja, asi que me busque el manejador para PostgreSQL 7.4.

El manual de DataVision es bastante preciso en cuanto a como proceder para obtener los reportes y para controlar la apariencia de los resultados. La mejor forma de probarlo es convertir uno de los SQL que escribimos anteriormente al reporte. Voy a utilizar uno sencillo:

  -- Count all IP addresses (including repetitive hits from the same IP address) that have visited the site for each day
SELECT log_date as Date ,count(ip_addr) AS "Visits"
FROM http_log
GROUP BY log_date
ORDER BY log_date DESC;

En mi caso, salida de esta consulta me retorna lo siguiente:

Date Visits
2005-03-25 246
2005-03-24 868
2005-03-23 743
2005-03-22 780
2005-03-21 824
2005-03-20 673
2005-03-19 644
2005-03-18 311

El primer golpe al usar esta herramienta, es que no soportaGroub By‘ en el SQL. Si usted quiere agrupar los datos, deberá dejar que sea Datavision quien lo haga. Eso puede que sea un inconveniente serio, dependiendo de la complejidad de el SQL. Como no puedo utilizar ‘group by’, entonces debo cambiar mi estrategia para contabilizar los totales por día; Eso no me agrada mucho, ya que yo quisiera concentrarme lo menos posible aprendiendo la herramienta, y más tiempo resolviendo el problema que tengo a la mano, asi que lo mejor es seguir buscando.

Revisando SourceForge otra vez, consigo que existe otro generador de reportes llamado ‘JasperReports‘. Parece ser que esta herramienta cuenta con un soporte inmenso de la comunidad Open Source, y decido bajarme una interfaz gráfica para la herramienta llamada Ireport.

Usando un ‘Wizard’ obtuve mi primer reporte en 30 minutos. Increible, justo lo que yo quería, sin limitaciones en el SQL:

iReport logreport screenshot

Lo que sale en la pantalla es el reporte, el cual puede ser guardado en varios formatos (HTML, PDF, etc). Increiblemente conveniente, flexible. Justo lo que un SA con tiempo limitado necesita.

La creación de el reporte se limito a escoger la fuente de datos (no limitada a bases de datos), el diseño de la plantilla, compilarla y finalmente ejecutarla. Luego de correr el reporte, este se puede guardar en el disco duro en el formato de salida que se desee, además de que la plantilla de el reporte se puede guardar para una futura ocasión.

Puede bajarse mi reporte desde aquí.

Echando código: Como automatizar los "Ping Manuales" de VeneBlogs, usando Java y Ethereal

Ya como había comentando antes en este Blog, les decia que Ethereal puede ser muy útil no sólo con fines de seguridad sino también como una herramienta de desarrollo de aplicaciones. Por ejemplo, suponga que usted decide hacer una clase en Java la cual le hace el “Ping manual” a VeneBlogs. Dado que aún no se ha prestado para abusos, el colocar los datos allí es muy simple ya que sólo se exige el usuario y la clave para poder hacer el ping usando una forma en HTML que usa el método POST.

Podemos usar Ethereal para capturar todo el tráfico desde el principio, así que con ello primero obtenemos el HTML de la forma a disecar:

VeneBlogs HTML Form

Sin embargo esta parte de la conversación es trivial, ya que podemos usar un navegador como Mozilla para ver el código HTML de la forma; ¿Pero y que hay de lo que realmente se envia por el cable hacia el servidor? Bueno, siguiendo las instrucciones de el tutorial anterior, podemos capturar el resto de la conversación y ver lo siguiente:

  • Contenido de la forma POST (cabeceras HTTP, contenido de la forma, Cookies)
  • Respuesta de retorno de el servidor

VeneBlogs captura de datos

Por cierto, ¿ya vió el password en el URL (si, esa no es mi verdadera clave :))?. Es una lastima que VeneBlogs no utilice SSL + HTTP para proteger a sus usuarios de este tipo de problemas.

Mi primer intento de hacer un ping a VeneBlogs desde Java fué el siguiente programa:

   1:package com.blogspot.elangelnegro;
2:import java.util.ResourceBundle;
3:import java.io.IOException;
4:import java.io.InputStreamReader;
5:import java.io.BufferedReader;
6:import java.io.LineNumberReader;
7:import java.io.DataOutputStream;
8:import java.net.URL;
9:import java.net.URLConnection;
10:import java.net.HttpURLConnection;
11:import java.net.URLEncoder;
12:import java.net.MalformedURLException;
13:
14:/**
15: * This program connects to the VeneBlogs.com manual ping page and sends a ping to it, using the POST method.
16: * <b>License:</b> LGPL.
17: * Write a Resource Bundle file with contents similar to this one (change only the appropriate parts):
18: * <pre>
19: * # URL used to connect to Veneblogs
20: * com.blogspot.elangelnegro.ManualPing.url=http://www.veneblogs.com/ping/prlogin.php
21: * com.blogspot.elangelnegro.ManualPing.referer=http://www.veneblogs.com/ping/pingmanual.php
22: * com.blogspot.elangelnegro.ManualPing.username=PPPP@domain.com
23: * com.blogspot.elangelnegro.ManualPing.password=XXXX
24: * </pre>
25: * @author Jose V Nunez Zuleta
26: * @version 0.1 - 01/10/2005
27: * @see http://www.javaworld.com/javaworld/javatips/jw-javatip34.html
28: * @see http://elangelnegro.blogspot.com
29: */
30:public final class VeneBlogsManualPing {
31: private static final ResourceBundle BUNDLE =
32: ResourceBundle.getBundle(VeneBlogsManualPing.class.getName());
33: public static final String VERSION = "1.0";
34: /**
35: * Command line processing
36: * @param args Command line arguments, currently ignored
37: * @throws IOException if there is an error communicating with the website
38: * @since 0.1
39: */
40: public static final void main(String [] args) throws IOException {
41: if (BUNDLE.getString("com.blogspot.elangelnegro.VeneBlogsManualPing.url") == null) {
42: throw new IllegalArgumentException();
43: }
44: URL url = null;
45: LineNumberReader reader = null;
46: DataOutputStream out = null;
47: try {
48: url = new
49: URL(BUNDLE.getString("com.blogspot.elangelnegro.VeneBlogsManualPing.url"));
50: URLConnection con = url.openConnection();
51: con.setDoInput(true);
52: con.setDoOutput(true);
53: con.setUseCaches (false);
54: con.setRequestProperty("User-Agent",
55: VeneBlogsManualPing.class.getName() + "/" +
56: VERSION + " El Angel Negro");
57: con.setRequestProperty("Accept",
58: "text/xml,text/plain,text/html");
59: con.setRequestProperty("Content-Type",
60: "application/x-www-form-urlencoded");
61: con.setRequestProperty("Referer",
62: BUNDLE.getString("com.blogspot.elangelnegro.VeneBlogsManualPing.referer"));
63: ((HttpURLConnection) con).setRequestMethod("POST");
64: ((HttpURLConnection) con).setInstanceFollowRedirects(true);
65: // Write the form to the Website using POST
66: out = new DataOutputStream(con.getOutputStream());
67: // Append the operands in the same order, we don't know if they process them by name...
68: StringBuffer postData = new StringBuffer();
69: postData.append("username=" +
70: URLEncoder.encode(
71: BUNDLE.getString("com.blogspot.elangelnegro.VeneBlogsManualPing.username"))
72: + "&");
73: postData.append("operation=in&");
74: postData.append("password=" +
75: URLEncoder.encode(
76: BUNDLE.getString("com.blogspot.elangelnegro.VeneBlogsManualPing.password"))
77: + "&");
78: // They have it all this empty spaces, don't ask me why! :)
79: postData.append("submit=" +
80: URLEncoder.encode(" Enviar "));
81: out.writeBytes(postData.toString());
82: postData.setLength(0);
83: out.flush();
84: out.close();
85: // Read the response
86: reader = new LineNumberReader(
87: new BufferedReader(new InputStreamReader(con.getInputStream())));
88: for (String line = reader.readLine(); line != null; line = reader.readLine()) {
89: System.out.println(line);
90: }
91: } catch (MalformedURLException malExp) {
92: throw new IllegalArgumentException(malExp.toString());
93: } catch (IOException ioExp) {
94: throw ioExp;
95: } finally {
96: try {
97: if (reader != null) {
98: reader.close();
99: }
100: } catch (IOException ignore) {};
101: try {
102: if (out != null) {
103: out.close();
104: }
105: } catch (IOException ignore) {};
106: }
107: }
108:}

Por alguna razón no funcionó nunca bien y por ello me decidí a comenzar un pequeño pequeño proyecto llamado ‘Pingo‘. Con Pingo espero no tener que llamar 3 sitios web (VeneBlogs, Bitacoras, Technorrati) para notificar que mi bitacora está actualizada (Blogger no lo hace), sino que todo se hará desde un solo programa:

Project Information
-------------------

1. Submitter: josevnz

2. Project UNIX Name: pingo

3. Project Descriptive Name: Pingo, the Blog manual ping tool

4. License: GNU General Public License (GPL)

License other:

5. Project Description: Pingo is a program that can be used to "manual
ping" Blogs directories that doesn't support such automatic feature.
Inittial support is planned for VeneBlogs.com, Bitacoras.com and
Technorati.com

6. Registration Description: Pingo is a program that can be used to
"manual ping" Blogs directories that doesn't support such automatic
feature. Inittial support is planned for VeneBlogs.com, Bitacoras.com and
Technorati.com.

If the project gains interest, then other blog directories will be
added (for that to happen an extensible arquitecture is planned from the
beginning, at least for blogs that require communication in plain HTTP or
XML-RPC).

Pingo will use Open Source libraries like Jakarta XML-RPC and will be
written in Java (Swing for the user interface) in order to guarantee
that it can be executed on any machine with support for a Java Virtual
machine.

inittial supported languajes are English and Spanish.

[end]

Vamos a ver si me lo aprueban. Por cierto, si está interesado en colaborar, tiene tiempo libre y sabe echar código entonces sea usted bienvenido :).

Echando código: ¿Como puedo respaldar mis fotos de las cuentas grátis de Flickr, usando Perl?

Hice la misma pregunta en el foro de ayuda de Flickr; Después de 3 horas uno de sus empleados me indicó lo siguiente:

Eric Flickr Staff says:

Sorry for the lack of reply to your email josevnz!

I’m afraid there is no way to do this automatically. You’ll have to download the photos from the free accounts one by one and re-upload them to your new account.

¡Que ladilla!. Así que empecé a echarle cabeza a la vaina, para ver si podía evitarme esa ladilla o por lo menos minimizar el daño.

Después de mucho pelear con uno de las personas de soporte (coño, estoy pagando, ¿no?), este me dió una idea:

Eric Flickr Staff says:

Well, it is a pain I know but you can actually pull your original sizes by hacking the url. They will end with “_o.jpg” (assuming they are jpgs), so just change the end of the url of your “resized versions” to “_o.jpg” and you’ll have your original.

So photos6.flickr.com/6697434_8332121f37_s.jpg would become photos6.flickr.com/6697434_8332121f37_o.jpg

Resulta que la documentación de el API de Flickr dice como generar el URL. Así que me baje la clave del API y me decidí a bajar un wrapper en Perl ya que me daba una ladilla increible utilizar un protocolo de bajo nivel para obtener todos los nombres y para generar los URL:

Al final me decidí a atacar el problema así:

  1. Se perezoso. Busca un wrapper para el API de Flickr para evitar lidiar con detalles de bajo nivel.
  2. Obtener el identificador de un usuario de Flickr.
  3. Obtener el listado de todas fotos publicas para un identificador de usuario dado.
  4. Bajarse las fotos a un directorio local mientras se recorre la lista de fotos. Eso requiere que armemos el URL a mano.

Primero instalamos el modulo de Perl para Flickr, Flickr::API:

[root@localhost root]# perl -MCPAN -e’install Flickr::API

Tiene un coñazo de dependencias. Pero por lo menos en Fedora Core 2 se dejó sin chistar 🙂

Luego a echar código. Después de 3 cervezas Corona y dos discos completos de Metallica (Reload y Saint Anger), el programa estaba listo:

   1:#!/usr/bin/perl
2:use strict;
3:use Flickr::API;
4:use Data::Dumper;
5:use LWP::UserAgent;
6:
7:
8:use constant API_KEY => 'a8a57025bbe0f7d1889b6cb6d754ad7b';
9:use constant PER_PAGE => 500;
10:use constant PAGE => 1;
11:use constant DEBUG => 0;
12:
13:if (! defined $ARGV[0]) {
14: die "[ERROR]: Please provide the user ID!";
15:} else {
16: printf "Getting list of public pictures for '%s'\n", $ARGV[0];
17:}
18:my $api = new Flickr::API({
19: 'key' => API_KEY}
20: );
21:my $ua = LWP::UserAgent->new;
22:# Get the user NSID first
23:my $response = $api->execute_method(
24: 'flickr.people.findByUsername', {
25: 'username' => $ARGV[0]
26: });
27:if (! $response->{success}) {
28: die "[ERROR]: " . $response->{error_message} . " ($ARGV[0])";
29:}
30:# Parse the tree and get the NSID
31:my %tree = %{$response->{tree}};
32:# Cheat with 'print Dumper($tree);' to get the value I want right away
33:my $id = $tree{children}->[1]->{attributes}->{nsid};
34:if (DEBUG) {
35: printf "%s\n", $id;
36:}
37:# Get now the list of public photos
38:$response = $api->execute_method(
39: 'flickr.photos.search', {
40: 'user_id' => $id
41: });
42:%tree = %{$response->{tree}};
43:# Get the photo information. For that we get a more manageable structure
44:my @subtree = @{$tree{children}->[1]->{children}};
45:if (DEBUG) {
46: print Dumper(\@subtree);
47:}
48:foreach my $ref (@subtree) {
49: my $attributes = $$ref{attributes};
50: next if $attributes == undef;
51: # Construct the URL like this:
52: # http://photos{server-id}.flickr.com/{id}_{secret}_o.(jpg|gif|png)
53: my $url = "http://photos" .
54: $$attributes{'server'} .
55: ".flickr.com/" .
56: $$attributes{'id'} .
57: "_" .
58: $$attributes{'secret'} .
59: "_o.jpg";
60: printf "Downloading: %s -> %s...", $$attributes{'title'}, $url;
61: $ua->mirror(
62: $url,
63: $$attributes{'id'} . "_" . $$attributes{'secret'} . "_o.jpg"
64: );
65: if ($response->is_success) {
66: printf "OK\n";
67: } else {
68: printf "ERROR\n";
69: }
70:
71:}
72:__END__

El programa se corre así:

[josevnz@localhost fotos]$ ../DownloadPublicPictures.plx josevnz2
Getting list of public pictures for ‘josevnz2’
Downloading: I95 snow -> http://photos5.flickr.com/4746321_036bd281f6_o.jpg…OK
Downloading: Teatro Avon Enero 2005 -> http://photos5.flickr.com/4746319_215f0d4490_o.jpg…OK
Downloading: mudanza de servidores -> http://photos4.flickr.com/4640875_5e09991db5_o.jpg…OK
Downloading: rack de servidores -> http://photos3.flickr.com/4640874_8c8648dd32_o.jpg…OK
Downloading: Lanzamiento de CVEBrowser -> http://photos3.flickr.com/4592140_5037d50d90_o.jpg…OK
Downloading: Montes East Village Manhattan New York -> http://photos4.flickr.com/4294649_99a73efb94_o.jpg…OK
Downloading: The Olive Tree East Village Manhattan New York -> http://photos1.flickr.com/4294648_f867326f0a_o.jpg…OK
Downloading: mazinger-flash -> http://photos3.flickr.com/4177404_b1ba64e2f9_o.jpg…OK
Downloading: Regalos de cumpleaños: DVD -> http://photos4.flickr.com/4145436_781e968181_o.jpg…OK
Downloading: Ipod Edición Especial de U2: El objeto de mi deseo -> http://photos3.flickr.com/3573557_0d1a452590_o.jpg…OK
Downloading: Stamford Enero 2005: Frio maldito -> http://photos2.flickr.com/3573556_1bdc308657_o.jpg…OK
Downloading: The Aviator -> http://photos3.flickr.com/3571985_583e39b9f3_o.jpg…OK
Downloading: Blood Rayne 1 y Blood Rayne 2: Las sagas -> http://photos3.flickr.com/3526934_7d3a658771_o.jpg…OK
Downloading: Blood Rayne 2: Yo tomando un descanzo -> http://photos3.flickr.com/3526930_520f38ac88_o.jpg…OK
Downloading: Blood Rayne 2: Final -> http://photos3.flickr.com/3526927_e64cb0a886_o.jpg…OK
Downloading: Battlestar Galatica site -> http://photos3.flickr.com/3431907_6819a3c7bc_o.jpg…OK

¡Chevere!. Ya con esto me bajé todas mis fotos :D. Por cierto, para dejar varias fotos a la vez en Flickr, yo utilizo FlickrUploadr, bien sencilla de usar (escrita en Python) y sobre todo, trabaja muy bien bajo Linux…

Como siempre les dejo el código fuente para que se diviertan.

Echando código: ¿Como mandar mensajes a mi cuenta de Yahoo Messenger desde Java?

Si bien yo soy un fanático de el protocolo Jabber, por ser abierto, tengo que admitir que lo que uso en el trabajo y con mi familia en Yahoo Messenger; Si bien el cliente de Linux es una porquería, eso lo compenso utilizando un cliente Open Source llamado Gaim, ¡el cual es arrechisimo!

Una de mis tareas como SA es monitorear aplicaciones en tiempo real y algo que he querido hacer desde hace tiempo es enviarme los mensajes de ciertas bitacoras (como /var/log/messages) a mi sesión de messenger. Es una aplicación perfecta, ya que el cliente ya está escrito, sólo tengo que redireccionar la salida a mi cliente y listo.

Como buen fanatico de Java, me conseguí en Source Forge una librería llamada JYMGS la cual habla el protocolo de Yahoo Messenger utilizando Java. Ahora la pregunta era: ¿Que tán fácil es de usar?

Bueno, me tomó sólo dos clases hacer un “HelloWorld” en Java. Primero, por razones de abstración de el API, necesito un listener el cual va a reaccionar a los eventos de Yahoo (como desconexión, conexiones, etc). La implementación más sencilla es simplemente esta (heredo de un ‘Adaptor’):

   1:import ymsg.network.event.SessionAdapter;
2:public class YahooSession extends SessionAdapter {
3: // Empty
4:}

Y luego la clase que hace las llamadas:

   1:import ymsg.network.Session;
2:import ymsg.network.StatusConstants;
3:import ymsg.network.event.SessionAdapter;
4:public final class YahooPing {
5: public static void main (String [] args) throws Exception {
6: Session session = null;
7: try {
8: session = new Session();
9: session.addSessionListener(new YahooSession());
10: session.setStatus(StatusConstants.STATUS_INVISIBLE);
11: session.login(args[0], args[1]);
12: if (session.getSessionStatus() == StatusConstants.MESSAGING) {
13: session.sendMessage(args[2], args[3]);
14: try {
15: Thread.sleep(5*1000);
16: } catch(InterruptedException e) {
17: // Empty
18: }
19: session.logout();
20: } else {
21: session.reset();
22: }
23: } catch(Exception e) {
24: if (session != null) {
25: session.reset();
26: }
27: }
28: }
29:}

Aquí les muestro como mandar un mensaje como el usuario ‘robot’ a el usuario ‘angelnegro’:

[josevnz@XXX YahooPing]$ java YahooPing robota password angelnegro “¡Hola bicho!”

Es muy fácil de usar, dentro de poco sacaré un programita que me permita hacer lo que estoy buscando 🙂

Rompecabezas malditos: ¿Cual es la probabilidad de que aún me gane un IPOD?

IPOD U2 puzzle

Bueno, el concurso de Cloudscape ya terminó y aún no se si me van a dar un IPOD, una franela o nada :); Se que mi respuesta es correcta, pero esto es lo que se sabe hasta ahora:

Posted By: moorman
Date: 2005-03-07 09:52
Summary: Cloudscape Database Challenge

Last week, we randomly selected the email addresses of those eligible to claim prizes in the Cloudscape Database Challenge. Prize winners will be announced as they are confirmed. The following winners have been confirmed to date:

Name – Prize
——————-
David M. King – T-shirt
David L. Webster – T-shirt
Brian R. Szmyd – T-shirt
Nicholas Hodapp – iPod
Donald L. Fairall, Jr. – iPod
Waimun Yeow – iPod
James Eugene McCullough – iPod
Mark Paciga – iPod
Scott A. Dial – T-shirt
Benjamin Stoutenburgh – T-shirt
Lawrence Berlinski – T-shirt
Chris Kapp – T-shirt
Wayne Howell Fay – T-shirt
Avi Mosher – iPod
Iwona Dednarczyk – iPod

Total contest entries: 8375
Total correct answers: 2072

Partiendo de esa información, ¿podré responder estas dos preguntas, dada la información anterior?

  1. ¿Cual es la probabilidad de que me gane un IPOD?
  2. ¿Cual es la probabilidad de que me gane una franela?

Recordando, esta es la cantidad disponible de premios:

you have a chance to win one of ten 40gig Apple iPod first-place
prizes, or one of 50 SourceForge.net T-shirts second-place prizes.

Creo que ya tenemos todo lo que necesitamos para resolver el problema. Como siempre hay que reformular el problema en terminos que podamos usar para obtener la solución.

Existe dos posibles eventos:

  1. Ganarse un IPOD = A
  2. Ganerse una Franela = B

Y las probabilidades asociadas, dado que sabemos que ya han habido varios ganadores (lechudos ellos):

P(A) = (10 – 7) / (2072 – 7 -8) = 3 / 2057 = %0.15
P(B) = (50-8) (/2072 – 7 -8) = 42 / 2057 = %2.041

Ganarse el IPOD está un poco dificil, pero hey, tengo ¡%2 de ganarme la franela!, una probabilidad bastante decente. Asi que aún no pierdo las esperanzas. Lo otro es que si todos estos concursos tienen una probabilidad similar, entonces vale la pena participar.

Echando código: ¿Como encontrar el mayor número en una tabla (usando SQL), sin usar funciones de agregación (max, min)?

Esta es otra de las preguntas con las cuales pueden tratar de matarlo en una entrevista:

¿Como encontrar el mayor número en una tabla (usando SQL), sin usar funciones de agregación (max, min)?

Hmmm. ¿Interesante, no es así?. La pregunta busca ver sus conocimientos básicos de SQL y como ataca problemas.

Primero, antes de empezar a resolver este problema, vamos a hacer un par de preparativos, ya que la idea es que usted lo haga frente a un computador; Así que si no tiene instalado PostgreSQL 7.4 y Java le recomiendo que lo haga, así como tambien debería crear una base de dados para pruebas la cual llamaremos ‘test’:
Ahora vamos a crear las tablas necesarias para resolver el problema y vamos a llenarlas con datos.

[root@localhost pgsql]# createdb –user=postgres test –host=localhost.localdomain
CREATE DATABASE
psql –user=postgres –host=localhost.localdomain test –file src/sql/create_tables.sql
CREATE TABLE

[josevnz@localhost SQLProblem]$ cat src/sql/create_tables.sql
/*
* Test table that will hold our numbers and names.
* Author: Jose V Nunez Z
* Blog: El Angel Negro – http://elangelnegro.blogspot.com
* License: GPL
*/
— Table with users
create table users (
ages int not null,
names varchar(15)
);

Y ahora vamos a llenarlas de datos, usando un programita en Java que genera números aleatorios, los cuales pueden estar repetidos:

   1:import java.sql.SQLException;
2:import java.sql.PreparedStatement;
3:import java.sql.Statement;
4:import java.sql.DriverManager;
5:import java.sql.ResultSet;
6:import java.sql.Connection;
7:
8:import java.util.Random;
9:
10:import java.util.ResourceBundle;
11:
12:/**
13: * This program generates a series of random numbers for our problem.
14: * @author Jose V Nunez Z
15: * @version 0.1 - 03/09/2005
16: * @see http://elangelnegro.blogspot.com
17: */
18:public final class GenerateNumbers {
19:
20: private static final ResourceBundle BUNDLE =
21: ResourceBundle.getBundle(GenerateNumbers.class.getName());
22:
23: /**
24: * Program entry routine
25: * @param args - Ignored for now
26: * @throws Exception
27: */
28: public static void main(String [] args) throws Exception {
29: Connection con = null;
30: Statement stat = null;
31: PreparedStatement prep = null;
32: Random rand = new Random();
33: int max = 0;
34: try {
35: max = Integer.parseInt(BUNDLE.getString("maxNumbers"));
36: Class.forName(BUNDLE.getString("driver"));
37: con = DriverManager.getConnection(
38: BUNDLE.getString("url"),
39: BUNDLE.getString("user"),
40: BUNDLE.getString("password")
41: );
42: stat = con.createStatement();
43: stat.executeUpdate("truncate table users");
44: stat.close();
45: prep = con.prepareStatement("INSERT INTO users(ages, names) VALUES(?, 'dumb')");
46: for (int i=0; i < max; i++) {
47: prep.setInt(1, rand.nextInt(i+1));
48: prep.executeUpdate();
49: prep.clearParameters();
50: }
51: prep.close();
52: } catch (SQLException sqlex) {
53: throw sqlex;
54: } catch (NumberFormatException nfexp) {
55: throw nfexp;
56: } finally {
57: if (stat != null) {
58: try {
59: stat.close();
60: } catch (SQLException ignore) {
61: // Empty
62: };
63: }
64: if (prep != null) {
65: try {
66: prep.close();
67: } catch (SQLException ignore) {
68: // Empty
69: };
70: }
71: if (con != null) {
72: try {
73: con.close();
74: } catch (SQLException ignore) {
75: // Empty
76: };
77: }
78:
79: }
80: }
81:}

En SQL normal, usted sólo tendría que hacer esto para hallar al número más grande:

select max(ages) from users

Eso nos dá el número 9872 (para mi corrida de ejemplo). Vamos a pensar un poco como hacerlo sin esta función.

Lo primero que se me ocurrió es limitar la cantidad de resultados que vienen desde la base de datos. Con Java es trivial pero si lo quiero hacer con SQL entonces puedo usar la sintaxis propia de PostgreSQL:

test=# select ages from users order by ages desc limit 1;
ages
——
9872
(1 row)

test=# explain select ages from users order by ages desc limit 1;
QUERY PLAN
———————————————————————
Limit (cost=69.83..69.83 rows=1 width=4)
-> Sort (cost=69.83..72.33 rows=1000 width=4)
Sort Key: ages
-> Seq Scan on users (cost=0.00..20.00 rows=1000 width=4)
(4 rows)

test=#

No está tan mal. Otra forma es iterar por todos los resultados, sin ordernarlos y a medida que vamos obteniendo los resultados guardamos el valor de el número sólo si es mayor que el anterior. Asi no ordenamos, pero quizas la sobrecarga de esta operación es muy grande; Lo único es que debemos cargar el lenguaje ‘pgsql’ en la nueva base de datos, de lo contrario no podremos crear stored procedures:

[josevnz@localhost lib]$ createlang plpgsql test –user=postgres –host localhost.localdomain

El código en plpgsql:

13:-- Another solution: Use a cursor and some programming
14:CREATE OR REPLACE FUNCTION getmax() RETURNS integer AS '
15:DECLARE
16: biggest integer := 0;
17: curr integer := 0;
18: curs CURSOR FOR SELECT ages FROM users;
19:BEGIN
20: OPEN curs;
21: FETCH curs INTO curr;
22: WHILE FOUND LOOP
23: IF curr > biggest THEN
24: biggest := curr;
25: END IF;
26: FETCH curs INTO curr;
27: END LOOP;
28: CLOSE curs;
29: return biggest;
30:END;
31:' LANGUAGE plpgsql;

Y la salida de ejemplo:
test=# select getmax();
getmax
——–
9872
(1 row)

En teoria el stored procedure debería ser más eficiente que la salida anterior, ya que no hay que ordenar toda la tabla para luego sacar el primero de ese resultado.

Finalmente, esta solución la conseguí en un libro:

— This answer doesn’t work at all…
SELECT
distinct ages
FROM
users
WHERE
ages NOT IN (
SELECT a.ages FROM users a, users b WHERE a.ages).

Según PostgreSQL esto es lo que el query va a hacer:

QUERY PLAN
——————————————————————————
Unique (cost=11712.41..11714.91 rows=100 width=4)
-> Sort (cost=11712.41..11713.66 rows=500 width=4)
Sort Key: ages
-> Seq Scan on users a (cost=0.00..11690.00 rows=500 width=4)
Filter: (NOT (subplan))
SubPlan
-> Seq Scan on users b (cost=0.00..22.50 rows=334 width=4)
Filter: ($0 < test="">.

El costo es altisimo, además de que el query nunca finaliza ya que la sección en rojo genera un número exagerado de combinaciones…

Por cierto, el costo de usar la función de agregación ‘max’ es el menor de todas las alternativas (la rutina está bien optimizada):

test=# explain select max(ages) from users;
QUERY PLAN
—————————————————————
Aggregate (cost=22.50..22.50 rows=1 width=4)
-> Seq Scan on users (cost=0.00..20.00 rows=1000 width=4)
(2 rows)

test=#

Si usted conoce otra solución a este problema, mucho se lo agradeceré. Estuve pensandolo por un tiempo pero sólo pude llegar a esto :D. También le dejo un enlace a un buen libro de PostgreSQL.

Se puede bajar todo el ćodigo desde aquí.

Echando código: ¿Como reiniciar un proceso, con Perl y SNMP?

Este post se parece mucho a uno anterior, el el cual utilizabamos Perl y SNMP para ver si una serie de procesos estaba muerto; Sin embargo en este caso lo que tenemos es un ‘watchdog’, un simple programa que es ejecutado cada cierto tiempoy si detecta que un programa en particular está caido, entonces trata de reiniciarlo.

La lógica es muy sencilla, revise si el servicio está siendo monitoreado por Net-SNMP (más fácil que hacer un ‘ps’ usted mismo, además de que es más flexible ya que usted puede controlar cuantas procesos es la cantidad correcta que deberían estarse ejecutando). Si la bandera es diferente de 0 es que hay un error, usted reinicia al demonio y manda una notificación por correo electrónico a su ‘helpdesk’. Para correr el programa periodicamente, deberá utilizar ‘cron’:

   1:#!/usr/bin/perl
2:
3:use Net::SNMP;
4:use Net::SMTP;
5:use strict;
6:
7:# START USER CONFIGURABLE SECTION
8:use constant MAIL_SERVER => 'localhost';
9:use constant MAIL_SUBJECT => "[INFO $0]: A daemon was restarted automatically!";
10:use constant MAIL_FROM => 'angelnegro@domain.com';
11:use constant SNMP_COMMUNITY => 'public';
12:# END USER CONFIGURABLE SECTION
13:
14:use constant PORT => 161;
15:use constant BASE_OID => '.1.3.6.1.4.1.2021.2';
16:use constant INDEX_OID => BASE_OID . '.1.2';
17:use constant STATUS_OID => BASE_OID . '.1.100';
18:use constant VERSION => 2;
19:use constant UNKNOWN_INDEX => -1;
20:use constant DEFAULT_HOST => 'localhost';
21:use constant ERROR_CODE => 192;
22:use constant OK_CODE => 0;
23:use constant DEBUG => 0;
24:
25:if ((scalar(@ARGV)) != 3) {
26: die "Please provide the daemon name, restart command, and a destination email address!";
27:}
28:my $flag = OK_CODE;
29:my ($session, $error) = Net::SNMP->session(
30: -hostname => DEFAULT_HOST,
31: -community => SNMP_COMMUNITY,
32: -version => VERSION,
33: -port => PORT
34:);
35:if ($error) {
36: die "[ERROR $0]: $error";
37:}
38:my $result = $session->get_table(
39: -baseoid => INDEX_OID
40:);
41:if (! defined $result) {
42: printf "\t[ERROR $0]: No response from, %s\n", DEFAULT_HOST;
43: $flag = ERROR_CODE;
44:} else {
45: my $index = UNKNOWN_INDEX;
46: foreach my $key (keys %${result}) {
47: # Get the Index for the desired service. The key index
48: # will depend of the order given by the SA, so it is better
49: # to look for it!
50: if ($$result{$key} eq "$ARGV[0]") {
51: my @tokens = split('\.', $key);
52: $index = $tokens[scalar(@tokens)-1];
53: }
54: }
55: # It is being monitored?
56: if ($index == UNKNOWN_INDEX) {
57: print "\t[INFO $0]: Daemon not being monitored!\n";
58: $flag = ERROR_CODE;
59: } else {
60: # Now get the status of the daemon, according to SNMP!
61: my $key = STATUS_OID . "\.$index";
62: my $result2 = $session->get_request(
63: -varbindlist => [$key]
64: );
65: if (! defined $result2) {
66: my $error_message = $session->error;
67: printf "\t[ERROR $0]: %s, %s\n", $ARGV[0], $error;
68: $flag = ERROR_CODE;
69: } elsif ($$result2{$key} != 0) {
70: printf "[WARNING $0]: %s, status=%s\n", $ARGV[0], $$result2{$key};
71: printf "[INFO $0]: Executing: %s\n", $ARGV[1];
72: my $output=`$ARGV[1] 2>&1`;
73: my $exit_code = $?;
74: my $smtp = Net::SMTP->new(
75: MAIL_SERVER,
76: Timeout => 20,
77: Debug => DEBUG
78: );
79: $smtp->mail(MAIL_FROM);
80: $smtp->to($ARGV[2]);
81: $smtp->data();
82: $smtp->datasend("To: " . $ARGV[2] . "\n");
83: $smtp->datasend("Subject: " . MAIL_SUBJECT . ": $ARGV[0]\n");
84: $smtp->datasend("\n");
85: $smtp->datasend("Command executed to restart the daemon:");
86: $smtp->datasend("\n");
87: $smtp->datasend($ARGV[1]);
88: $smtp->datasend("\n");
89: $smtp->datasend("Command output:");
90: $smtp->datasend("\n");
91: $smtp->datasend($output);
92: $smtp->datasend("\n");
93: $smtp->datasend("Exit code: $exit_code");
94: $smtp->dataend();
95: $smtp->quit;
96: }
97: }
98:}
99:my $error_message = $session->error;
100:if ($error) {
101: printf "\t[ERROR $0]: %s, %s\n", $ARGV[0], $error;
102: $flag = ERROR_CODE;
103:}
104:if (defined $session) {
105: $session->close;
106:}
107:exit $flag;
108:
109:__END__
110:
111:=head1 NAME
112:
113:SimpleWatchDog.plx - A program used to restart daemons if the fail.
114:
115:=head1 DESCRIPTION
116:
117:This program is meant to be executed from cron in order to check the status of a givem program.
118:It is expected than the daemon to check runs in the same machine where this program runs.
119:
120:=head2 CONFIGURATION
121:
122:=over4
123:
124:=item Identify which process name to put on the /etc/snmp/snmpd.conf file
125:
126:ps -e|perl -p -i -e' $_ =~ s# {1,}# #g; $_ =~ s# #|#g;'|cut -f5 -d'|'|sort|grep -v CMD|grep myprogram
127:
128:=item * Modify the 'snmpd.conf' file
129:
130:proc myprogram 1 1
131:
132:=item * Restart SNMPD
133:
134:service restart snmpd
135:
136:=item * Modify this script
137:
138:Modify the variables: MAIL_SERVER, MAIL_SUBJECT, SNMP_COMMUNITY
139:
140:=back
141:
142:=head2 HOW TO RUN
143:
144:Put an entry like this on cron:
145:
146: # Keep an eye on the daemon 'myprogram', every 10 minutes
147: */10 * * * * /opt/SimpleWatchDog.plx myprogram /etc/init.d/mypgrogram restart josevnz@domain.com > /var/log/myprogram-watchdog.log 2>&1
148:
149:=head1 AUTHOR
150:
151:Jose V Nunez Zuleta
152:
153:=head1 BLOG
154:
155:El Angel Negro - http://elangelnegro.blogspot.com
156:
157:=head1 LICENSE
158:
159:GPL
160:
161:=head1 VERSION
162:
163:0.1 - 02/27/2005
164:
165:=cut

El crontab para nuestro programa:

# Watchdog for the program smartd
*/10 * * * * /usr/local/sysadmin/SimpleWatchDog.plx NBScpCopier.pl “/etc/init.d/smartd restart” angelnegro@domain.com > /var/log/smartd-watchdog.log 2>&1

Aqui está el código por si quiere bajarse el programita.