14 de Febrero, 2011

DBus otra vez

Después de los cambios de última versión de Nautilus Flickr Uploader, me quedé pensando en la funcionalidad™ que le faltaba para cerrar el tema de la usabilidad.

Uno de mis casos de uso es el siguiente:

  1. Descargo las fotos de mi cámara al portátil.
  2. Abro la primera foto con el visor de imágenes de Gnome.
  3. Cuando una imagen me interesa: botón derecho y Abrir con » Subir imágenes a Flickr.
  4. Para el resto de fotos, si alguna más merece la pena ;), las arrastro a la ventana ya abierta del Nautilus Flickr Uploader.

Esta forma de trabajar me sale de forma natural, pero este fin de semana me di cuenta que en realidad estoy condicionado. ¿Por qué no repito la operación del punto 3? Fácil: porque se abriría una nueva ventana de NFU, en lugar de que las fotos fueran a parar a la ventana ya abierta.

Diagrama de flujo de la idea

Así que me he puesto a investigar cómo podría conseguir el comportamiento que realmente un usuario esperaría encontrar.

Las dos cosas que quería hacer (detectar si la aplicación está en marcha y enviar las nuevas fotos a la instancia existente), resulta que están cubiertas a la perfección con DBus (del que ya hablé usando DBus para interactuar con NetworkManager).

Como NFU es una aplicación hecha en Perl, esta vez he empleado Net::DBus (y Net::DBus::GLib para integrarlo con el loop de GTK+).

La idea es arrancar un servicio en DBus como semáforo para no ejecutar la aplicación más de una vez, y al mismo tiempo, si tenemos nuevas fotos para procesar, enviar esta información a la aplicación que ya está ejecutándose (como se ve en el esquema).

La documentación me ha resultado algo complicada de seguir (orientada a módulos, pero sin una imagen general), así que voy a poner un ejemplo por aquí por si le resulta de utilidad alguien.

El servicio DBus

Lo primero que hice fue programar un esqueleto para el servicio, que es más o menos lo que voy a explicar aquí como ejemplo. El funcionamiento va a ser sencillo: dada una ruta en el sistema de archivos, devolveremos un 1 si nuestro proceso tiene permisos de lectura.

Una cosa que me ha gustado de Net::DBus es que soporta introspección, con lo que el servicio proporcionará al cliente la información necesaria sobre los parámetros, lo que se traduce en que podremo usar el objeto como si fuera un objeto local ;).

package Service;

use warnings;
use strict;

# heredamos de Net::DBus::Object
use base qw(Net::DBus::Object);
# el interfaz que vamos a exportar
use Net::DBus::Exporter qw(net.usebox.demo.remote);

# el método que vamos a implementar, indicando los tipos de entrada/retorno
# para que la introspección funcione
dbus_method("test", ["string"], ["bool"]);

# constructor de la clase
sub new
{
	my $cls = shift;
	my $service = shift;
	my $self = $cls->SUPER::new($service, "/net/usebox/demo");

	bless $self, $cls;
	return $self;
}

# nuestro método
sub test
{
		my $self = shift;
		my $path = shift;

		# para ver qué pasa en el servidor
		print "DEBUG: recibido $path\n";

		# devolvemos el resultado de verificar si podemos leer la
		# ruta que nos han indicado
		return -r $path;
}

1;

Guardamos el módulo como Service.pm, y en el mismo directorio creamos el programa que registrará el servicio en el bus de la sesión:

use warnings;
use strict;

# en una aplicación GTK+ usaríamos Net::DBus::GLib
# y NO necesitaríamos el reactor ni ejecutar el main->run del final
use Net::DBus;
use Net::DBus::Reactor;

# Nuestro módulo
use Service;

# nos conectamos el bus de sesión
my $session_dbus = Net::DBus->session();
# indicamos al bus que vamos a proporcionar nuestro servicio
my $service = $session_dbus->export_service('net.usebox.demo');
# creamos nuestro servicio
my $service_obj = Service->new($service);

# entramos en un bucle en el que se atienden las peticiones
Net::DBus::Reactor->main->run();

Lo guardamos en serv.pl (por ejemplo), y lo ejecutamos.

DBus viene con diferentes herramientas (bus-*) que nos permiten interactuar con el bus fácilmente. Por ejemplo, podemos probar el servicio con dbus-send:

$ dbus-send --session --type=method_call --dest=net.usebox.demo \
/net/usebox/demo net.usebox.demo.remote.test string:/home/reidrac

Si ejecutamos en otra consola dbus-monitor, veremos pasar el mensaje, pero con que en la consola del servicio veamos nuestra traza de DEBUG, será suficiente.

El cliente

Para poder usar el servicio tenemos que conectarnos al bus, obtener el servicio, y perdir un objeto que corresponda con nuestro intrefaz.

Este ejemplo manda como parámetro el argumento que indiquemos en la linea de comandos:

use warnings;
use strict;

use Net::DBus;

# nos conectamos el bus de sesión
my $session_dbus = Net::DBus->session();
# pedimos el servicio
my $service = $session_dbus->get_service('net.usebox.demo');
# obtenemos un objecto para realizar la llamda, indicando el interfaz
my $obj = $service->get_object('/net/usebox/demo', 'net.usebox.demo.remote');

# realizamos la llamada como si fuera un objeto local
print $obj->test($ARGV[0]);

Si pasamos como parámetro una ruta a un fichero o directorio para el que el servicio tiene permisos de lectura, obtendremos un uno como resultado.

Utilizando el servicio como semáforo

Si pedimos acceso al servicio y este no existe, obtendremos una excepción, así que podemos capturarla y así saber si nuestra aplicación ya se encuentra en ejecución:

my $service;
eval
{
	$service = $session_dbus->get_service('net.usebox.demo');
};

if(!$@ && $service)
{
	# tenemos el servicio, con lo que nuestra aplicación YA está
	# ejecutándose -- haríamos la llamada al servicio

	exit(0);
}

# sino, pasaríamos a registrar el servicio

De esta manera, la instancia que llega en segundo lugar se comunica mediante DBus con la instancia que controla el servicio, y que realizará el trabajo.

Conclusiones

Es la segunda vez que cacharreo con DBus, y me ha vuelto a sorprender por las posibilidades que tiene.

No por estar desarrollando a diario con Python he dejado de entender mi propio código en Perl, pero escribiendo esta anotación me he dado cuenta que es algo más complicado de leer por todos los modismos que tiene y que pueden resultar oscuros al ojo desentrenado.

Esta funcionalidad que he implementado en la versión 0.10 de Nautilus Flickr Uploader se supone que viene de casa con la reciente publicación de GTK+3, así que habrá que echarle un ojo ;).

Nota: los fuentes de ejemplo de esta anotación se pueden descargar aquí: dbus-perl.tar.gz.

Anotación por Juan J. Martínez, clasificada en: perl, dbus, programming.

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.

Algunas anotaciones relacionadas: