Informe de investigación de UI

Glosario

  • GUI: Graphic User Interface o interfaz gráfica de usuario, es la parte visual y de interacción con el usuario y la mayor parte del Frontend.
  • Frontend : Proceso visible para el usuario posiblemente iniciado por él. Internamente se envían unas señales al Backend y los resultados devueltos son mostrados al usuario.
  • Backend: Proceso oculto al usuario, pero que suele contener el núcleo del programa que se está desarrollando.
  • Python: Lenguaje de programación desarrollado en 1991. Está diseñado siguiendo la filosofía de la legibilidad y prima el esfuerzo del programador sobre el del computador para reducir tiempos de desarrollo. Es un lenguaje multiparadigmático (funcional, orientado a objetos e imperativo) y posee un completo sistema dinámico de tipos. Es similar a Perl, Ruby, Scheme y Tcl.
  • Pygtk1: Referencia a la biblioteca gráfica GTK para el lenguaje de programación Python.
  • Gtk2: Biblioteca gráfica que se utiliza para desarrollar el entorno gráfico GNOME.
  • GNOME3: GNOME es un entorno de escritorio para sistemas operativos de tipo Unix.
  • D-Bus4: Sistema software que proporciona comunicación entre aplicaciones / procesos.
  • Objeto: Objeto básico que utiliza DBus en el intercambio de información entre dos procesos.
  • Proceso: Unidad de ejecución independiente en un sistema operativo.
  • XML5: Siglas en inglés de eXtensible Markup Language («lenguaje de marcas extensible»). Es un metalenguaje extensible de etiquetas. Es un estándar para el intercambio de información estructurada entre diferentes plataformas de una manera segura, fiable y fácil. Además existen parsers en la amplia mayoría de lenguajes de programación.
  • Handler: o 'manejador'. Función que captura una determinada señal o evento de ventana que puede ser implementada después como queramos. Por ejemplo, pulsar un botón.
  • Widget: Pequeña aplicación o programa, cuyo objetivo es el de dar fácil acceso a funciones frecuentemente usadas y proveer de información visual.
  • KDE6: Entorno de escritorio gráfico e infraestructura de desarrollo para sistemas Unix y, en particular, Linux.
  • Libglade7: Biblioteca que efectúa un trabajo similar a las rutinas de salida de código fuente en el diseñador de interfaces GLADE8. Mientras que las rutinas GLADE obtenidas con código fuente C deben ser compiladas, libglade construye la interfaz desde un archivo XML (que es como GLADE guarda el código) en tiempo de ejecución. Esto puede permitir modificar la interfaz de usuario sin recompilar.

Descripción:

Nuestro módulo se encarga de darle al usuario una interfaz que le permita el manejo del programa, tratando de hacer lo más intuitiva posible la configuración del funcionamiento de HD Lorean, y la búsqueda y recuperación de ficheros de las copias de seguridad.

Abstracción al problema:

Nuestro trabajo consiste en crear una capa de abstracción entre el funcionamiento real del programa, que dista de ser intuitivo, y el usuario, ya que éste necesita una o varias interfaces que le resulten lo más cómodas, intuitivas y rápidas de usar.

Encontramos cuatro partes bien diferenciadas en nuestra labor:

  • Uno o varios interfaces donde el usuario pueda ajustar las preferencias de funcionamiento de HD Lorean (dónde se almacenan las copias de seguridad, qué directorios tienen que tener copias de seguridad, etc. )
  • Uno o varios interfaces donde el usuario pueda buscar versiones anteriores de sus ficheros y "traerlos" al presente, ya sea sobrescribiendo la versión actual, o como un fichero distinto.
  • Conviene que el usuario pueda buscar también las versiones de los ficheros por su contenido, y no solo por su ruta o localización en el sistema de archivos.
  • Comunicación instantánea con el backend del sistema.

Por otro lado, nos interesa que el programa sea lo más genérico, extensible y portable posible, de forma que no queremos cerrarnos a un único entorno de escritorio.

Abstracción a la solución

Por un lado, necesitamos crear un interfaz de preferencias, y por otro lado, un interfaz que permita navegar por las versiones de los archivos.

  • Para la interfaz de preferencias, buscamos que el usuario pueda seleccionar diferentes opciones, decidir qué carpetas se vigilan, etc. Estas preferencias las tenemos que pasar al resto de módulos para que empiecen a comportarse según lo que el usuario decida. De forma básica, por ejemplo, podría servir un archivo de configuración que el usuario edite y agregue la ruta de las carpetas, o ponga los parámetros de las diferentes opciones. También podemos hacer una interfaz, donde nos bastaría con los típicos widgets de cualquier interfaz gráfica de usuario: botones, radiobutton, check lists, etc.
  • Para la interfaz de navegación de versiones, podemos pensar diferentes paradigmas: desde un interfaz en 3D parecido al de Time Machine, como una interfaz modo texto donde le pasamos las opciones de la carpeta que queremos ver y la fecha y nos liste los contenidos, o lo más típico, que podría ser una vista de iconos, donde elijamos la fecha que queremos ver y nos aparezcan los iconos de los archivos dentro de la carpeta. Otra opción recogida en el planteamiento inicial del programa es una "barra de tiempo" como metáfora visual de avanzar y retroceder la fecha deseada.
  • En cuanto a la búsqueda de contenidos en las versiones, queremos que el usuario pueda poner un texto y se le muestren los diferentes archivos que lo contienen, y si es sólo en una versión, que el usuario lo sepa. Esto podríamos hacerlo con una interfaz de línea de comandos y también con una interfaz gráfica.
  • Para conseguir una comunicación entre los procesos de interfaz de usuario y el funcionamiento del programa instantánea, necesitamos comunicar dos procesos diferentes usando paso de mensajes.

En GNU/Linux existen varias posibilidades que se nos ofrecen para solucionar estos problemas, donde algunas de las posibilidades más típicas son:

  • Interfaz de usuario en JAVA: librerías Swing/AWT
  • Interfaz de usuario en GNOME: librerías GTK
  • Interfaz de usuario en KDE: librerías Qt
  • Interfaz de usuario en consola: CLI

Al decidirnos por el entorno de escritorio GNOME para nuestra práctica, ya que viene por defecto en gran parte de las distribuciones de linux, reducimos la lista anterior a: Java, GNOME y consola.

Por otro lado, habiendo trabajado con las librerías Swing y AWT en el pasado, decidimos que cualquier otra solución debe ser mejor. Complicaría el arrastrar y soltar ficheros y otras integraciones con el escritorio. Por tanto queda descartada a priori.

Teniendo en cuenta que GNOME usa de forma nativa las librerías GTK, usar estas librerías haría que el interfaz de usuario corra más rápido en este entorno.

También consideramos interesante que la práctica se pueda utilizar desde la consola, pues suele ser un interfaz más rápido de manejar, y los usuarios avanzados prefieren en muchos casos utilizar las aplicaciones de ésta manera. Además facilitará notablemente la depuración, e incluso la creación de tests automatizados para el software, durante su desarrollo.

En cuanto al paso de mensajes, tanto GNOME como KDE tienen sus respectivos sistemas llamados ORBit9 y DCOP, así como D-BUS, que sirve también para comunicarse con el sistema operativo y es el estándar en boga entre ambos sistemas desde hace algunos años.

Tecnologías investigadas:

Interfaz de preferencias:

La idea de tener un archivo de preferencias del que lean los otros módulos nos sirve tanto para el interfaz gráfico (que puede escribir en él), como para un interfaz modo texto (ya que el usuario únicamente tendría que editarlo, con cualquier editor de texto). El archivo de preferencias se ajustaría a los estándares de interoperabilidad presentes en http://freedesktop.org .

Por lo tanto pasamos a investigar cómo hacer el interfaz gráfico.

GTK+

Es un conjunto de bibliotecas o rutinas para desarrollar interfaces gráficas para el entorno de escritorio GNOME. Es software libre y multiplataforma, y este hecho se ajusta a las características de nuestro proyecto, aunque presumiblemente su capacidad multiplataforma no vamos a explotarla ya que HD Lorean es un software que utiliza características del núcleo de Linux y otros programas que no existen en otras plataformas. No obstante es posible que, en futuras fases de desarrollo, investiguemos la posibilidad de portarlo a otros unix como FreeBSD.

La herramienta gráfica que se usa para diseñar interfaces gráficas GTK se llama glade. Esta herramienta genera el código en C o en C++ que conecta los botones de la interfaz con las señales que tienen que generar en GTK. Sin embargo, investigando acerca de ella, encontramos un proyecto que nos abre aún más las puertas: Libglade.

Pygtk

PyGTK es un binding (referencias) de la biblioteca gráfica GTK para Python. PyGTK nos permite crear de un modo rápido y sencillo la GUI de la aplicación ya que se dispone de las librerías GTK para programar directamente para el entorno de escritorio GNOME.
PyGTK dispone de una API que resulta muy útil, ya que describe todas las funciones y los parámetros utilizados para éstas, de modo que resulta bastante fácil programar PyGTK partiendo con unos conocimientos bastante limitados de GTK, simplemente programando sobre Python y llamando a las funciones de las que se dispone. API de PyGTK

Libglade:

Es una librería que carga directamente el XML generado por glade, y conecta los botones del GUI con las señales adecuadas de GTK. La diferencia reside en lo siguiente:

  • El código generado por glade en C o en C++ es un código que hay que compilar e integrar con nuestra práctica. Cuando cambiamos algo de nuestra práctica o la disposición de los botones del interfaz gráfico, hay que volver a compilar e integrar.
  • Sin embargo, usando libglade, podemos cambiar el interfaz gráfico, mientras mantengamos las señales que generan, pues libglade se encarga de conectar estas señales. Por otro lado, el código del UI lo podemos escribir tanto en C/C++ como en Python (pues libglade está disponible para ambos), y lo único que hacemos es conectar las señales con los métodos que le correspondan.

Con esto tenemos lo que necesitamos para hacer la interfaz de preferencias. Podemos hacerla tanto en C como en Python, habiéndonos decantado por empezar las pruebas con Python, que nos permite hacerlo de una forma más rápida y dinámica.

Interfaz de navegación de versiones

Aquí también queremos mantener la misma idea que en la otra interfaz, por ello nos interesa conseguir un "interfaz primario" que sea capaz de transformarnos los queries a la base de datos y los snapshots en algo legible e interpretable tanto por un interfaz gráfico como por un interfaz modo texto. Esto facilitaría portar la "interfaz secundaria" a otra librería, a otro entorno de escritorio, pues la haría más extensible.

A priori encontramos dos posibilidades:

  • Programar nosotros mismos un interfaz primario. Donde, por ejemplo, dando una fecha y una ruta de un archivo, devuelva la versión del archivo en esa fecha. Ésto permitiría luego hacer la versión modo texto, y crear la interfaz gráfica que permita moverse por el sistema de archivos y por la línea de tiempo para encontrar las versiones del archivo.
  • Adaptar el interfaz primario a un paradigma ya existente, y para el que ya existan navegadores, es decir, un sistema de ficheros virtual. Se podría navegar por el directorio 2006, dentro de éste estarían los directorios de todos sus meses, dentro de éstos sus días… y en el último de estos directorios se encontrarían las carpetas indexadas en ese momento, con las copias respectivas de cada uno.

Hacemos comparaciones entre cada solución:

  • Más o menos código: La primera solución obligaría a programar más, pues hay que programar tanto la "UI primaria" como la "UI secundaria" así como los "conectores" o plugins con el navegador de archivos de GNOME (nautilus), o crear nuestro propio navegador de versiones. La segunda opción permitiría programar menos, pues sólo se tendría que programar la UI primaria, ya existen navegadores de archivos tanto en modo texto como en GNOME, como en el resto de entornos de escritorio.
  • Mejor o peor usabilidad: La primera solución permitiría crear un interfaz de usuario más usable y más intuitivo, pues podríamos definirlo y diseñarlo en relación a los objetivos y añadir las opciones que necesitemos.

Así mismo existen diferentes tecnologías que permiten transformar casi cualquier cosa en un sistema de ficheros virtual:

  • GnomeVFS: Provee de una capa de abstracción para la lectura, escritura y ejecución de ficheros. Lo utilizan principalmente el navegador de ficheros Nautilus y otras aplicaciones GNOME. Actualmente se está reescribiendo entre otras cosas para que permita montar particiones de FUSE.
  • FUSE: Módulo del kernel de unix que permite a los usuarios sin privilegios crear sus propios sistemas de ficheros sin necesidad de escribir un módulo de kernel. FUSE es particularmente útil para escribir sistemas de ficheros virtuales, cualquier recurso disponible para la implementación de FUSE puede ser exportado como un sistema de ficheros.

Interfaz de búsqueda por contenidos

Como en el caso anterior, se puede escribir un nuevo buscador de contenidos, lo cual obligaría a realizar también un indexador de contenidos, o utilizar uno que ya exista.

En el caso de utilizar uno que ya exista, habría que ampliarlo indicándole cómo tiene que indexar las copias de seguridad de los archivos, pues estos buscadores indexan ficheros, pero HD Lorean no trata las copias de seguridad como copias íntegras de los ficheros.

Entre los existentes, en GNU/Linux nos encontramos con la posibilidad de usar Tracker10 o Beagle11:

  • Tracker: Escrito en C, muy rápido. A parte de permitirnos buscar los contenidos de los ficheros, permite etiquetar los ficheros. No tiene definida una api, y no encontramos fácil la posibilidad de hacer plugins para describirle cómo indexar nuestros datos, pues no tiene una buena documentación.
  • Beagle: Escrito en C#, algo más lento y pesado que tracker. Tiene un sistema desarrollado de plugins a través del cual podríamos indicarle como indexar nuestros datos, y tiene una buena documentación.

Por otro lado, si se opta por hacer un sistema de archivos virtual, se podría hacer que estos buscadores indexaran este sistema de archivos, y por lo tanto ahorraría el trabajo de hacer plugins para ellos. Tampoco tendríamos que elegir entre ellos, porque los dos servirían.

Comunicación entre procesos (Frontend y Backend)

Para la comunicación entre el Frontend y el Backend interesa un sistema que ya esté desarrollado, pues es una parte bastante difícil, que tiene que estar bien diseñada, y que puede llevar mucho tiempo. Actualmente existen en GNU/Linux varios sistemas dedicados a ésto que funcionan perfectamente. Por otro lado interesa utilizar uno que esté bastante extendido, pues estos sistemas permiten no sólo comunicaciones dentro de nuestra aplicación, sino también con otras aplicaciones.

  • D-BUS12: El sistema de comunicación entre procesos más extendido. Tiene wrappers tanto paraC/C++ y Python, con lo que no nos obliga a cambiar los lenguajes de programación.
  • ORBit (CORBA13): Aplicación de comunicación interprocesos actualmente en desuso. Fue un proyecto muy ambicioso. Atilizado antes que Dbus para la comunicación interprocesos en GNOME. Es un software muy complejo y demasiado difícil de usar, tanto es así que GNOME lo sustituyó en su día por D-BUS.
  • DCOP14: Aplicación de comunicación interprocesos extensamente utilizada en KDE, otro de los principales entornos de escritorio de Linux. Actualmente también está dejando de utilizarse en favor de D-BUS.

Se descarta ORBit (CORBA) y DCOP por tanto. Aunque son tecnologías que podrían suplir perfectamente a DBus, actualmente están dejando de utilizarse, perdiéndose compatibilidad con el resto de aplicaciones de escritorio de Linux al usarlas.

Así mismo, si se decide por utilizar un sistema de archivos virtual, puede servir como forma de pasar archivos "completos" al frontend desde el backend, lo cual simplificaría la comunicación pues basta con pasar una ruta y usar operaciones de copia estándar.

Ejemplos de las tecnologías

Glade + Python

Fragmento de código XML de un archivo .glade:

<glade-interface>
    <widget class="GtkAboutDialog" id="About">
        <property name="visible">True</property>
    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    <property name="border_width">5</property>
    <property name="title" translatable="yes">About HD Lorean</property>
    <property name="resizable">False</property>
    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
    <property name="has_separator">False</property>
    <property name="program_name">HD Lorean</property>
    <property name="version">0.01</property>
    <property name="comments" translatable="yes">

Ejemplo de código Python y libglade:

self.gladefile="dbusgui.glade"
self.wTree = gtk.glade.XML (self.gladefile)
self.wTree = gtk.glade.XML (self.gladefile)
self.texto = self.wTree.get_widget('textview1')

En éste ejemplo podemos observar como se asocia un archivo glade XML a un atributo de un objeto de una clase Python.

Glade es una herramienta gráfica que permite la creación de interfaces gráficos simplemente arrastrando botones, paneles de texto, y demás elementos sobre la ventana en la que estamos trabajando.

Pantallazo-Asist.glade.png

Es una herramienta muy útil ya que además permite asociar eventos de los diferentes elementos gráficos de la ventana a métodos y funciones del código en Python.

Ejemplo de código Python:

dic = { "on_BSalir_clicked" : self.Salir,
        "on_BMostrar_clicked" : self.Mostrar
    }

Este ejemplo de código Python nos muestra la forma de conectar los eventos creados en Glade asociados a un elemento gráfico con funciones del código generado en Python.
FUSE

Extracto de un ejemplo donde se lee un directorio usando fuse:

static int leer_directorio ( const char *path, void *buffer, fuse_fill_dir_t rellenar, off_t offset, struct fuse_file_info *info )
{
    /* En caso de que no estemos haciendo referencia al directorio principal devolvemos -ENOENT,
       esto provocará que la lectura de directorio devuelva -1 y errno se establezca en ENOENT. */
    if ( strcmp ( path, "/" ) )
        return -ENOENT;

    /* Rellenamos el buffer conteniendo el listado de directorio, las entradas obligadas son . y ..
       Como notarán a archivo1 y a archivo2 le sumamos 1, esto es para saltarse la primer / */
    rellenar ( buffer, ".", NULL, 0 );
    rellenar ( buffer, "..", NULL, 0 );
    rellenar ( buffer, archivo1 + 1, NULL, 0 );
    rellenar ( buffer, archivo2 + 1, NULL, 0 );

    return 0; /* Todo salió bien :) */
}

Fuente

Ejemplos de primitivas de la librería FUSE extraídas de su página de documentación:

#define fuse_main  ( argc, argv, op, user_data ) fuse_main_real(argc, argv, op, sizeof(*(op)), user_data)

#define FUSE_REGISTER_MODULE ( name, factory_ )      

#Donde se define name_:

static __attribute__((constructor)) void name_ ## _register(void) \
    { \
        static struct fuse_module mod = { #name_, factory_, NULL, NULL, 0 }; \
        fuse_register_module(&mod); \
    }

Dbus

Dbus es una aplicación que se utiliza para comunicar varios procesos entre sí. Para utilizarlo basta con importar la librería dbus (import dbus en python por ejemplo).
Se podría pensar en dbus como si fuera una aplicación cliente-servidor bidireccional. Una parte se programa con objetos/rutas que enlazan esa ruta con unos métodos del módulo en el que están presente. La otra parte permite conectar esos objetos/rutas para poder utilizarlos en el módulo destino.
Esto permite en nuestro caso comunicar la gui con el backend, que son procesos diferentes pero necesitan información uno del otro. Para ello tenemos que crear objetos dbus y funciones para la conexión con otros objetos en ambos módulos.

Ejemplo:

bus = dbus.SessionBus()
rb = bus.get_object('org.gnome.Rhythmbox','/org/gnome/Rhythmbox/Player')
player = dbus.Interface(rb,'org.gnome.Rhythmbox.Player')
playing = player.getPlaying()

Este ejemplo nos muestra como realizar una conexión desde un proceso cualquiera con Rhythmbox, que contiene varios objetos Dbus que podemos utilizar. Simplemente hay que indicar la interface y el objeto, que viene dado mediante el espacio de nombres /org/gnome/Rhythmbox/Player para poder empezar a utilizar funciones que nos ofrece ese objeto.

Pantallazo-dbusgui.py.png

Aquí podemos ver el resultado final de una aplicación ejemplo creada para obtener información diversa de la música que esta tocando en un momento determinado la aplicación Rhythmbox.

DBus es una tecnología en auge en los últimos tiempos, quiere convertirse en un standard en la comunicación interprocesos.

Conclusión

Haremos la interfaz de preferencias basada en un fichero de texto configurable también a través de un interfaz en glade, usando libglade y python.

Asimismo, en cuanto al navegador de versiones y el buscador de contenidos, se solucionarán ambas usando un sistema de archivos virtual con FUSE. En caso de que estas interfaces quedaran pobres navegando directamente sobre el sistema de archivos virtual con los navegadores de ficheros habituales, se hará una interfaz más intuitiva modo texto y otra para GNOME.

En principio no se tocará para nada beagle ni tracker para que funcionen si hacemos el sistema de archivos virtual, por esto no hay ejemplo de éstas tecnologías, pero si no es posible hacer el sistema de ficheros virtual, tendremos que hacer un plugin para alguno de ellos al menos. En ese caso nos decantaríamos por Beagle.

En cuanto al sistema de comunicación entre procesos, vemos que DBus se está convirtiendo en un standard dentro de la comunicación interprocesos en GNU/Linux. Por lo tanto si queremos que HD Lorean pueda en un futuro comunicarse con otras aplicaciones necesitamos utilizarlo.

Comentarios:

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License