Conectar programas C/C++ con aplicaciones Android

En esta entrada voy a describir la manera de comunicar aplicaciones escritas en Android con programas escritos en C/C++ vía JNI utilizando el NDK de Android.

El NDK es un conjunto de herramientas que permite incorporar los componentes que hacen uso de código nativo en las aplicaciones de Android. Permite implementar parte de tus aplicaciones usando código nativo con lenguajes como C y C ++. Esto puede proporcionar beneficios a ciertas clases de aplicaciones, en la medida que se puede reutilizar el código existente y en algunos casos obtener un aumento de la velocidad.

Java Native Interface (JNI) es un framework de programación que permite que un programa escrito en Java ejecutado en la máquina virtual java (JVM) pueda interactuar con programas escritos en otros lenguajes como C, C++ y ensamblador.

La estructura de un proyecto Android que utilice JNI y NDK para permitir la intercomunicación entre aplicaciones C/C++ y Android es la siguiente:
carpeta raíz del proyecto/

  • jni/
  • libs/
  • res/
  • src/
  • AndroidManifest.xml
  • default.properties
  • … otros ficheros …

Descripción de los ficheros:

  • La carpeta src contiene el código Java de la aplicación Android
  • La carpeta res contiene los recursos de la aplicación (imágenes, archivos XML que describen las capas de interfaz, etc)
  • La carpeta libs *contendrá* las librerías nativas *después* de una construcción exitosa
  • La carpeta jni *contiene* el código C/C++ de la aplicación con la que queremos comunicarnos, además de dos scripts importantes: Android.mk y Application.mk. Estos scripts son dos Makefiles típicos que controlan el proceso de construcción de la aplicación C++.

Veamos los pasos para ejecutar una sencilla aplicación Android que se comunique con un programa escrito en C++.

  • Descargar y descomprimir NDK
    Sé que hay versiones mucho más actuales, pero aún así, utilizo esta de Crystax que está bastante optimizada.
  1. wget http://www.crystax.net/en/download/android-ndk-r4-linux-x86-crystax-4.tar.bz2 -O /tmp/android-ndk-r4-linux-x86-crystax-4.tar.bz2
  2. cd ~ && tar xvjf /tmp/android-ndk-r4-linux-x86-crystax-4.tar.bz2

  • Crear la carpeta libs en el proyecto
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> Folder. Escribimos libs como nombre de carpeta.
  • Crear la carpeta jni en el proyecto
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> Folder. Escribimos jni como nombre de carpeta.
  • Crear el programa C++ con el que queremos comunicarnos
    Pulsamos el botón derecho sobre la carpeta JNI, y escogemos la opción New -> File. Escribimos code.cpp como nombre de fichero. Hacemos doble clic sobre el fichero y escribimos el siguiente código:
  1. #include <jni.h>
  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <getopt.h>
  5. #include <string>
  6. #include <cassert>
  7. #include <stdexcept>
  8.  
  9. using namespace std;
  10.  
  11. extern "C"
  12. {
  13. JNIEXPORT jstring JNICALL Java_blog_neonigma_AndroidActivity_SayHello(JNIEnv *env, jobject thiz, jstring name)
  14. {
  15.   try
  16.   {
  17.     const char *myName = env->GetStringUTFChars(name, 0);
  18.     std::string nameCPP(myName);
  19.     nameCPP = "Hello: " + nameCPP;
  20.     return env->NewStringUTF(nameCPP.c_str());
  21.   }
  22.   catch (exception &ex)
  23.   {
  24.     const char *error = "Failed";
  25.     return env->NewStringUTF(error);
  26.   }
  27. }
  28. }

  • Consideraciones sobre el código:
    • El bloque extern “C” indica que el programa está escrito en C++. Sí, en C++. Si queremos escribirlo en C, eliminamos el bloque extern.
    • La declaración de la función nativa tiene que coincidir con el namespace + nombre de actividad Android + nombre de función en el código Java/Android. En este caso, como nuestra actividad es AndroidActivity y se encuentra en el namespace blog.neonigma, la función debe nombrarse como Java_blog_neonigma_AndroidActivity_SayHello, siendo SayHello el nombre que damos a la función y “Java_” un texto estático.
    • La función siempre recibe un puntero al entorno (JNIEnv *env) y un objeto manejador (jobject thiz)
    • Para convertir const char * en jstring, usamos la función NewStringUTF del entorno
    • Más información en la API oficial de JNI

  • Crear los Makefiles para NDK
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> File. Escribimos Android.mk como nombre de fichero. El código para este fichero sería el siguiente:
  1. LOCAL_PATH := $(call my-dir)
  2.  
  3. include $(CLEAR_VARS)
  4.  
  5. LOCAL_MODULE    := mixed_sample
  6. LOCAL_SRC_FILES := code.cpp
  7. LOCAL_LDLIBS +=  -llog -ldl
  8.  
  9. include $(BUILD_SHARED_LIBRARY)

Los valores más importantes son code.cpp, que indica el fichero de código con el que queremos comunicar y mixed_sample, que es el nombre que damos a la librería que se va generar vía JNI.

Repetimos el proceso y creamos un fichero Application.mk.

  1. APP_STL := gnustl_static
  2. APP_CPPFLAGS := -frtti -fexceptions
  3. APP_ABI := armeabi
  4. APP_PROJECT_PATH := ~/workspace/blog-jni-c

En este caso, los valores más importantes son la arquitectura armeabi (puede cambiarse por la más moderna armeabi-v7a) y el path del proyecto, donde hay que indicar el path al workspace actual.

  • Crear un builder para la compilación
    Debe generarse un builder en Eclipse para la compilación del código C/C++ con NDK.

    1. Pulsamos en projects -> properties y escogemos la opción Builders. Pulsamos el botón New.
    2. En la ventana que aparece, escogemos la opción Program (sin escribir nada), y pulsamos OK.
    3. En la siguiente ventana configuramos el Builder en sí. En la pestaña Main, escribimos en el campo Location la ruta absoluta al programa ndk-build, en mi caso /home/neonigma/android-ndk-r4-crystax/ndk-build En el campo Working directory, pulsamos en el botón Browse workspace y escogemos el proyecto en el que estamos trabajando.
    4. Ahora vamos a la pestaña Refresh y marcamos Refresh upon completion, Specific resources y Recursively include sub-folders.

      Pulsamos en el botón Specify resources y en la ventana que aparece, escogemos el recurso libs.
    5. Por último, vamos a la pestaña Build Options, dejamos las casillas ya marcadas y marcamos During auto builds y Specify working set of relevant resources.

      Pulsamos en el botón Specify resources y escogemos el recurso jni de nuestro proyecto.
  • Escribir el código de la actividad Android
    Para enviar datos al programa en C++ y recogerlos, necesitamos escribir el código de la actividad principal de la siguiente manera:
  1. package blog.neonigma;
  2.  
  3. import src.blog.neonigma.R;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.widget.Toast;
  7.  
  8. public class AndroidActivity extends Activity {
  9.     @Override
  10.     public void onCreate(Bundle savedInstanceState) {
  11.         super.onCreate(savedInstanceState);
  12.         setContentView(R.layout.main);
  13.        
  14.         String greeting = SayHello("neonigma");
  15.         Toast.makeText(this, greeting, Toast.LENGTH_LONG).show();
  16.     }
  17.    
  18.     public native String SayHello(String name);
  19.    
  20.     static {
  21.         System.loadLibrary("mixed_sample");
  22.     }

  • Consideraciones sobre el código
    • La llamada a función nativa se define como public native.
    • String se equipara a jstring, e int a jint, a la hora de enviar y recibir parámetros.
    • Debe definirse como estática la carga del módulo de la librería generada y que especificamos en el código del Makefile Android.mk.

Referencias:
http://opencv.itseez.com/doc/tutorials/introduction/android_binary_package/android_binary_package_using_with_NDK.html#android-binary-package-with-ndk
http://zsoluciones.com/datos/?p=246

El código del ejemplo puede descargarse desde el repositorio creado en BitBucket.

A 1 persona le gusta esta entrada

Tip: cómo modificar el último commit en bazaar / Launchpad

Hoy he cometido un pequeño error al comitear los cambios a un proyecto en el que estoy involucrado junto con @edortix y @pipotux. Dichos cambios me venían en forma de megaparche por parte de @pipotux y al subirlos, no me di cuenta que no tenia configurada mi cuenta de correo en el equipo que me encontraba trabajando.

Para solucionarlo, basta con revertir el commit, configurar correctamente la cuenta de usuario y reescribir el commit de la siguiente manera:

  1. bzr config email="neonigma &lt;neonigma@gmail.com&gt;"
  2. bzr launchpad-login
  3. bzr uncommit 27 # el número de revisión a "descomitear"
  4. bzr ci -m "A lot of changes. The most important one: changed the turn method"
  5. bzr push lp:project --overwrite

A 5 personas les gusta esta entrada

Clientes SOAP en Python y PHP

En el post anterior vimos como crear servicios Web personalizados con el protocolo SOAP en Drupal. Por supuesto, un servicio Web en SOAP puede hacerse en multitud de lenguajes. Al igual que los clientes que lo consumen. En este caso vamos a ver ejemplos para consumir el servicio Web creado en el post anterior con clientes SOAP escritos en los lenguajes Python y PHP.

Cliente en Python
En primer lugar, instalamos easy_install:

  1. neonigma@neonigma-desktop:~$ sudo apt-get install python-setuptools python-dev build-essential

A continuación, instalamos suds:

  1. neonigma@neonigma-desktop:/opt/lampp/htdocs$ sudo easy_install suds
  2. install_dir /usr/local/lib/python2.6/dist-packages/
  3. Searching for suds
  4. Reading http://pypi.python.org/simple/suds/
  5. Reading https://fedorahosted.org/suds
  6. Best match: suds 0.4
  7. Downloading http://pypi.python.org/packages/2.6/s/suds/suds-0.4-py2.6.egg#md5=94a9414e90e01243262548ad9eaf2784
  8. Processing suds-0.4-py2.6.egg
  9. creating /usr/local/lib/python2.6/dist-packages/suds-0.4-py2.6.egg
  10. Extracting suds-0.4-py2.6.egg to /usr/local/lib/python2.6/dist-packages
  11. Adding suds 0.4 to easy-install.pth file
  12.  
  13. Installed /usr/local/lib/python2.6/dist-packages/suds-0.4-py2.6.egg
  14. Processing dependencies for suds
  15. Finished processing dependencies for suds

A continuación, con sólo este pequeño programa en Python usando la librería suds podemos utilizar la operación que definimos en el servicio Web del post anterior:

  1. from suds import WebFault
  2. from suds.client import Client
  3. import traceback as tb
  4. import logging
  5.  
  6. logging.basicConfig(level=logging.INFO)
  7. logging.getLogger('suds.client').setLevel(logging.DEBUG)
  8.  
  9. try:
  10.     client = Client('http://debian-virtual/drupal/?q=services/soap/?wsdl')
  11.     print client.service.add_numbers(10,3)
  12. except WebFault, f:
  13.     print f
  14.     print f.fault
  15. except Exception, e:
  16.     print e
  17.     tb.print_exc()

El resultado de la ejecución es el siguiente:

  1. root@debian:/var/www# python cliente.py
  2. DEBUG:suds.client:sending to (http://debian-virtual/drupal/services/soap/?wsdl/)
  3. message:
  4. &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  5. &lt;SOAP-ENV:Envelope xmlns:ns3="http://debian-virtual/drupal/services/soap/?wsdl/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"&gt;
  6.    &lt;SOAP-ENV:Header/&gt;
  7.    &lt;ns1:Body&gt;
  8.       &lt;ns3:add_numbers&gt;
  9.          &lt;number_one xsi:type="ns2:int"&gt;10&lt;/number_one&gt;
  10.          &lt;number_two xsi:type="ns2:int"&gt;3&lt;/number_two&gt;
  11.       &lt;/ns3:add_numbers&gt;
  12.    &lt;/ns1:Body&gt;
  13. &lt;/SOAP-ENV:Envelope&gt;
  14. DEBUG:suds.client:headers = {'SOAPAction': u'"http://debian-virtual/drupal/services/soap/?wsdl/add_numbers"', 'Content-Type': 'text/xml; charset=utf-8'}
  15. DEBUG:suds.client:http succeeded:
  16. &lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"&gt;&lt;SOAP-ENV:Body&gt;&lt;ns1:soap_call_wrapperResponse xmlns:ns1="http://debian-virtual/drupal/services/soap/?wsdl/"&gt;&lt;return xsi:type="xsd:int"&gt;13&lt;/return&gt;&lt;/ns1:soap_call_wrapperResponse&gt;&lt;/SOAP-ENV:Body&gt;&lt;/SOAP-ENV:Envelope&gt;
  17. 13

Cliente en PHP
Para el cliente en PHP, necesitamos la librería nuSOAP. En este caso, la tenemos ya instalada del post anterior, por lo que apuntaremos a esa ruta. Escribimos este pequeño cliente en PHP:

  1. &lt;?php
  2. ini_set('soap.wsdl_cache_enabled', '0');
  3. require_once('drupal/modules/soap_server/nusoap/lib/nusoap.php');
  4.  
  5. $wsdl = "http://localhost/drupal-testing/?q=services/soap/?wsdl";
  6. $soap = new soapclient($wsdl);
  7.  
  8. $result = $soap-&gt;add_numbers(10,3);
  9. print "El resultado es: " . $result;
  10. ?&gt;

Apuntando a la URL de este ejemplo (http://localhost/cliente.php en este caso), veremos que nos imprime el resultado de la suma correctamente.

Sé el primero en valorar positivamente

Crea tu propio servicio Web con NuSOAP en Drupal

Una de las cosas que más me gustan del software libre es la liberación del conocimiento. Incluso (o sobre todo), cuando lo que has investigado, usado o desarrollado al final no te sirve. Me parece realmente mal que se deseche todo ese conocimiento, ya que se ha invertido el tiempo en ello, por qué no invertir 20 minutos más en estructurarlo y escribirlo. Otro podría necesitar ese conocimiento tan cercano. Aunque no esté 100% depurado y sea directamente utilizable, podría ser un gran punto de partida para otras personas, o incluso para uno mismo en momentos posteriores.

El presente artículo es uno de los que sí me han servido, pero ya empezaba a olvidar. Así que lo escribo. Y tengo en mente el siguiente, totalmente relacionado, que cumple una de las premisas del párrafo anterior: al principio no me sirvió para mucho y meses más tarde ha sido la llave para desbloquear el cierre de un proyecto.

Sin más preámbulos, veamos como crear tu propio servicio Web utilizando SOAP en Drupal. Esta forma da una idea de lo extendible que es un servicio Web dentro de este CMS y me sirve para recordar en cualquier momento cómo llevar a cabo este proceso de extensión.

En primer lugar, como siempre, instalamos drush:

  1. neonigma@neonigma-laptop:~$ sudo apt-get install drush

Descargamos el módulo services:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules$ drush dl services -r modules
  2. Project services (6.x-2.3) downloaded to                             [success]
  3. /var/www/drupal-testing/modules/.

Lo habilitamos:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules$ drush en services
  2. The following modules will be enabled: services
  3. Do you really want to continue? (y/n): y
  4. Services was enabled successfully.                                   [ok]

Descargamos el módulo soap_server:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules$ drush dl soap_server -r modules/services
  2. Project soap_server (6.x-1.2-beta1) downloaded to                    [success]
  3. /var/www/drupal-testing/modules/.

Lo habilitamos:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules$ drush en soap_server
  2. The following modules will be enabled: soap_server
  3. Do you really want to continue? (y/n): y
  4. SOAP Server was enabled successfully.                                [ok]

Para completar la instalación de SOAP Server, no basta con haber instalado el módulo. Debemos instalar la librería NuSOAP que es la que proporcionará realmente el servicio. La descargamos de este enlace por ejemplo a /tmp y la descomprimimos de la siguiente forma:

  1. neonigma@neonigma-laptop:/tmp$ mkdir nusoap
  2. neonigma@neonigma-laptop:/tmp$ mv nusoap-0.9.5.zip nusoap
  3. neonigma@neonigma-laptop:/tmp$ cd nusoap/
  4. neonigma@neonigma-laptop:/tmp/nusoap$ unzip nusoap-0.9.5.zip
  5. neonigma@neonigma-laptop:/tmp/nusoap$ rm nusoap-0.9.5.zip
  6. neonigma@neonigma-laptop:/tmp/nusoap$ cd ..
  7. neonigma@neonigma-laptop:/tmp$ mv nusoap/ /var/www/drupal-testing/modules/soap_server/

En este punto, tenemos un servidor SOAP escuchando en http://localhost/drupal-testing/?q=services/soap. Pero existe un bug en esta versión de SOAP server, por el cual al acceder a esa URL por primera vez no nos muestra las operaciones disponibles, sino el mensaje de error You must specify a name when you register an operation.

Afortunadamente tiene fácil solución. Nos bajamos este parche visto en este enlace en la ruta del módulo soap_server y lo parcheamos de esta forma:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/soap_server$ wget http://drupal.org/files/issues/751326.patch
  2. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/soap_server$ patch -p0 &lt; 751326.patch

Si volvemos a acceder de nuevo a la URL de escucha, ahora deberíamos de ver las operaciones disponibles y un enlace al WSDL. Esta URL al WSDL para mi no encaja con la realidad, por lo que yo siempre uso http://localhost/drupal-testing/?q=services/soap/?wsdl a la hora de proporcionar este parámetro a los clientes que deseen conectarse al servicio Web personalizado.

Otro de los hándicaps de utilizar SOAP en Drupal es que se requiere URLs limpias, tema bastante espinoso en Drupal 6, al menos para mi gusto. Para habilitarlas debemos seguir los siguientes pasos:

  • Comprobamos si tenemos instalado el módulo rewrite de Apache:
    1. root@neonigma-laptop:/var/www/drupal-testing# apache2ctl -M
  • Si no está instalado, lo instalamos:
    1. root@neonigma-laptop:/var/www/drupal-testing# a2enmod rewrite
    2. Enabling module rewrite.
    3. Run '/etc/init.d/apache2 restart' to activate new configuration!
    4. root@neonigma-laptop:/var/www/drupal-testing# /etc/init.d/apache2 restart
  • Visitar la URL http://localhost/drupal/?q=admin/settings/clean-urls y, si está disponible el botón de radio con la opción Activado, lo marcamos y guardamos la configuración. Si el botón no está disponible para marcado, debemos revisar cómo habilitar las URLs limpias en este post.

Con todo instalado, toca el turno de lo interesante, crearnos nuestro propio servicio Web. Los servicios Web preinstalados y listos para usar se encuentran en modules/services/services, por lo que un buen punto de partida es copiarnos uno de ellos y modificarlo a nuestro gusto.

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/services/services$ cp -R comment_service/ nuestro_service
  2. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/services/services/nuestro_service$ mv comment_service.info nuestro_service.info
  3. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/services/services/nuestro_service$ mv comment_service.inc nuestro_service.inc
  4. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/services/services/nuestro_service$ mv comment_service.module nuestro_service.module

La utilidad de cada fichero es la siguiente:

  • nuestro_service.info: este fichero contiene la información de visualización del módulo. Es decir, la identificación del módulo dentro de http://localhost/drupal-testing/?q=admin/build/modules, en la zona Services.
  • nuestro_service.module: este fichero contiene la descripción estática de las funciones del servicio Web que se van a compartir.
  • nuestro_service.inc: este fichero contiene el código de las funciones del servicio Web definidas en el fichero anterior.

Lo primero que vamos a hacer es cambiar la información de identificación de nuestro servicio Web en el fichero nuestro_service.info. Para ello, modificamos el primer bloque del código original:

  1. ; $Id: comment_service.info,v 1.1.2.1 2009/06/06 22:57:40 marcingy Exp $
  2. name = Comment Service
  3. description = Provides a comment service.
  4. package = Services - services
  5. dependencies[] = services
  6. dependencies[] = comment
  7. core = 6.x

y escribimos en su lugar este otro:

  1. ; $Id: nuestro_service.info,v 1.1.2.1 2010/12/04 11:28:00 marcingy Exp $
  2. name = Nuestro propio servicio Web
  3. description = Proporciona nuestra propia funcionalidad
  4. package = Services - services
  5. dependencies[] = services
  6. core = 6.x

Nótese que hemos eliminado la dependencia al módulo comment.

A continuación, editamos el fichero nuestro_service.module:, eliminando todo el código existente y colocando este en su lugar:

[PHP]
<?php
// $Id: nuestro_service.module,v 1.1.2.1.2.9 2010/07/16 02:59:56 skyredwang Exp $

/**
* @file
* Link functionality to services module.
*/

/**
* Implementation of hook_perm().
*/
function nuestro_service_perm() {
return array(‘access add two numbers’);
}
/**
* Implementation of hook_service().
*/
function nuestro_service_service() {
return array(

// nuestro.add
array(
‘#method’ => ‘add_numbers’,
‘#callback’ => ‘nuestro_service_add’,
‘#access arguments’ => array(‘access add two numbers’),
‘#file’ => array(‘file’ => ‘inc’, ‘module’ => ‘nuestro_service’),
‘#args’ => array(
array(
‘#name’ => ‘number_one’,
‘#type’ => ‘int’,
‘#description’ => t(‘The first number.’),
),
array(
‘#name’ => ‘number_two’,
‘#type’ => ‘int’,
‘#description’ => t(‘The second number.’),
),
),
‘#return’ => ‘int’,
‘#help’ => t(‘This method adds two numbers and return the result’),
),
);
}[/PHP]

Por último, editamos el fichero nuestro_service.inc:, eliminamos todo el código existente y definimos lo que hará la función que acabamos de exponer:

  1. &lt;?php
  2. // $Id: nuestro_service.inc,v 1.1.2.1.2.5 2010/05/08 19:26:00 heyrocker Exp $
  3.  
  4. /**
  5.  * @file
  6.  *  Link functionality to services module.
  7.  */
  8.  
  9. /**
  10.  * Returns the addition of two parameters received.
  11.  *
  12.  * @param $number_one
  13.  *   First number to add.
  14.  * @param $number_two
  15.  *   Second number to add.
  16.  * @param $since
  17.  *   Timestamp to indicate what nodes are new. Defaults to time of last user acces to node.
  18.  * @return
  19.  *   Number of nuestros that node has.
  20.  */
  21. function nuestro_service_add($number_one, $number_two) {
  22.   return (int)($number_one + $number_two);
  23. }

Es el momento de verificar que todo está en su sitio. Ahora veamos la configuración de módulos en http://localhost/drupal-testing/?q=admin/build/modules. Debemos activar nuestro nuevo módulo que representa el servicio Web que acabamos de crear.

Nos vamos a http://localhost/drupal-testing/?q=admin/user/permissions y verificamos que los permisos que hemos creado en el fichero services.modules, concretamente access add two numbers, están disponibles para asignar a los roles que tenemos creados. Inicialmente se encuentran desmarcados, deberemos marcarlos para acceso a usuario anónimo si es nuestro deseo.

Daremos permisos a todo el mundo para utilizar nuestro servicio Web. Para hacer que sólo puedan utilizar el servicio Web los usuarios registrados, me remito a la entrada anterior en este blog, donde se explicaba este hecho.

En http://localhost/drupal-testing/?q=admin/build/services podemos ver el servidor SOAP ejecutándose y las operaciones expuestas de nuestro servicio Web.

Para acceder a un testeador de nuestro servicio Web pulsamos en el enlace a nuestro.add. En la pantalla que aparece podemos escribir los dos argumentos y pulsar en Call method para realizar la llamada al servicio Web y obtener el resultado.

A 1 persona le gusta esta entrada

Gestión ligera de máquinas virtuales con KVM y Virtual Manager bajo Ubuntu

De la mano de mi compañero jhernandez descubro un gestor de máquinas virtuales mucho más ligero que el pesado VMWare al que llevaba acostumbrado muchos años.

Antes que nada nos creamos un directorio en nuestro home para almacenar nuestras máquinas virtuales.

  1. neonigma@neonigma-desktop:~$ mkdir vm

La idea es instalar KVM junto con la GUI Virtual Manager:

  1. neonigma@neonigma-desktop:~$ sudo apt-get install kvm virt-manager

Luego podemos acceder de la forma que se muestra en la imagen:

Si al acceder nos muestra un error de este tipo:

Significa que nuestra carpeta personal de virt-manager se ha creado erróneamente con permisos de root. La solución es cambiar el propietario de esta carpeta:

  1. neonigma@neonigma-desktop:~$ sudo chown neonigma:neonigma .virt-manager -R

Una vez abierto, añadiremos una conexión nueva en el menú Archivo – Añadir conexión y especificaremos como Hipervisor QEMU/KVM y como conexión Local. Si a continuación vemos el error que aparece en esta imagen:

instalaremos el siguiente paquete para resolverlo:

  1. neonigma@neonigma-desktop:~$ sudo apt-get install libvirt-bin

y daremos acceso al fichero /var/run/libvirt/libvirt-sock:

  1. neonigma@neonigma-desktop:~$ sudo chmod 777 /var/run/libvirt/libvirt-sock

Ya podemos ejecutar tranquilamente Virtual Manager como usuario. Ahora podemos ver en la pantalla principal una conexión a Localhost. Pulsamos dos veces sobre ella y nos aparece un menú con cuatro pestañas. Seleccionamos la pestaña Almacenamiento, y pulsamos en el botón que muestra un icono con el símbolo + en verde. En la ventana que se nos abre, podemos poner como nombre myHome y en Tipo elegimos Directorio del sistema de archivos. Pulsamos Adelante y donde pone Ruta de destino escribimos la ruta que creamos en el primer paso del artículo, es decir, /home/neonigma/vm en mi caso. Ya tenemos un espacio creado y dentro de nuestro /home.

Cerramos esta ventana donde hemos creado nuestro espacio de almacenamiento y volvemos a la ventana principal. Ahora pulsamos en el único icono activo que muestra un monitor y que nos va a permitir crear nuestra máquina virtual. En la primera pantalla escribimos el nombre de nuestra máquina virtual y dejamos el parámetro intacto:

En la siguiente pantalla seleccionamos Utilizar imagen ISO, pulsamos en el botón Explorar y a continuación pulsamos el botón Explorar localmente, seleccionamos la ISO del sistema operativo a instalar y pulsamos Aceptar. La siguiente imagen muestra cómo debería quedar la configuración:

En la siguiente imagen, indicamos el tamaño de memoria RAM que cedemos al sistema operativo virtual, pulsamos Adelante y vemos la última pantalla que se refiere al almacenamiento en el disco virtual. En esta pantalla seleccionamos la opción Seleccione un almacenamiento gestionado o de otro tipo existente, pulsamos en explorar, en la zona izquierda en nuestro espacio myHome, y finalmente en la zona derecha en nuestro debian-virtual.img. La configuración queda así:

La pantalla final nos muestra un resumen de la configuración escogida y pulsamos en Finalizar. En este momento, comienza automáticamente la instalación de nuestro sistema operativo virtual.

A 1 persona le gusta esta entrada

Login y registro de usuarios en Drupal con Python vía XMLRPC

Para este propósito necesitamos tener un rol administrador con todos los permisos asignados. El usuario que tenga asignado este rol será con el que iniciemos sesión en Python y el que tendrá poder para registrar usuarios. A partir de aquí, voy a suponer una instalación limpia de Drupal. Drupal tiene módulos que proporcionan un servidor XMLRPC y una serie de servicios Web impecables que expone al exterior. Veamos los pasos para preparar Drupal para este cometido.

  1. Instalar la herramienta de descarga y habilitación sencilla de módulos Drupal
    1. neonigma@neonigma-desktop:~/Descargas$ sudo apt-get install drush
  2. Descargar y habilitar el módulo Services
    1. neonigma@neonigma-desktop:/opt/lampp/htdocs/drupal/modules$ drush dl services -r modules
    2. Project services (6.x-2.2) downloaded to /opt/lampp/htdocs/drupal/modules/services.                                                                                                                  [success]
    3.  
    4. neonigma@neonigma-desktop:/opt/lampp/htdocs/drupal/modules$ drush en services
    5. The following projects will be enabled: services
    6. Do you really want to continue? (y/n): y
    7. services was enabled successfully.                                                                                                                                                                   [ok]

    En Drupal vamos a Administrar -> Construcción del sitio -> Módulos. Activamos los siguientes módulos y pulsamos en Guardar configuración:

    MUY IMPORTANTE. En Drupal vamos ahora a Administrar -> Administración de usuarios -> Opciones de usuario y DESACTIVAMOS la casilla Es necesaria la verificación por correo cuando un visitante crea una cuenta. Pulsamos el botón Guardar la configuración del final de la página. La siguiente imagen muestra cómo debe quedar la configuración:

    A continuación, vamos a Administrar -> Construcción del sitio -> Services, pulsamos en la pestaña Opciones y en el desplegable Authentication module escogemos Key authentication. A continuación desmarcamos Use keys y dejamos marcado Use sessid. Este párrafo es lo más importante, el que permite la magia. La siguiente imagen muestra cómo debe quedar la configuración:

    Por último, vamos a asignar todos los permisos correctamente. Vamos a Administrar -> Administración de usuarios -> Permisos. En la columna de administrador debemos haber marcado todos los checkbox, dando permiso al administrador para hacer lo que desee en el portal.

    Con esto, ya tendríamos Drupal preparado para el acceso a los distintos servicios Web. Para comprobarlo, vamos Administrar -> Construcción del sitio -> Services y hacemos clic en el enlace XMLRPC – /services/xmlrpc. La URL que se nos abra en el navegador será nuestra URL de conexión a los servicios Web. En mi caso de ejemplo, la URL es http://localhost/drupal/services/xmlrpc

    Ahora, sólo tenemos que escribir un pequeño programa de testing como este para aprovechar estos servicios:

    1. #! /usr/bin/python
    2. import os.path, sys, xmlrpclib, socket
    3.  
    4. config = {
    5.   'url': 'http://localhost/drupal/services/xmlrpc',
    6.   'username': 'admin',
    7.   'password': 'admin',
    8. }
    9.  
    10. # Make initial connection to service
    11. server = xmlrpclib.ServerProxy(config['url'], allow_none=True);
    12.  
    13. try:
    14.     server.system.listMethods() # si podemos listar las operaciones del servicio web, hemos conectado al servidor
    15. except xmlrpclib.ProtocolError, socket.error:
    16.     print 'No se puede conectar al servidor'
    17. except EOFError:
    18.     exit(1)
    19. else:
    20.     try:
    21.         connection = server.system.connect(); # hacer uso de la funcion connect del servicio web System
    22.     except:
    23.         print "Error en la conexion. Codigo de error: %d" % err.faultCode
    24.         print "%s" % err.faultString.encode('utf-8','ignore')
    25.     else:
    26.         try:
    27.             session = server.user.login(connection['sessid'], config['username'], config['password']); # usar funcion login del servicio web User
    28.  
    29.         except xmlrpclib.Fault, err:
    30.             print "Error en el inicio de sesion. Codigo de error: %d" % err.faultCode
    31.             print "%s" % err.faultString.encode('utf-8','ignore')
    32.         else:
    33.             sessid = session['sessid']; # necesario obtener el sessid, es nuestra key para identificarnos despues
    34.             user = session['user'];
    35.  
    36.             try:
    37.                 # construimos los datos del nuevo usuario y le entregamos la key
    38.                 user_data = {
    39.                     'sessid': session['sessid'],
    40.                     'name': 'enigma',
    41.                     'mail': 'enigma@enigma.es',
    42.                     'pass': 'enigma',
    43.                 }
    44.                 print session # datos del administrador
    45.                 result = server.user.save(sessid, user_data) # intentar registrar nuestro usuario, no olvidar la key
    46.                 print result # resultado del registro de usuario
    47.             except xmlrpclib.Fault, err:
    48.                 print "Error en el registro de usuario. Codigo de error: %d" % err.faultCode
    49.                 print "%s" % err.faultString.encode('utf-8','ignore')        
    50.             else:
    51.                 print "Se ha registrado correctamente al usuario"

    El resultado de la ejecución de este código es el siguiente:

    1. neonigma@neonigma-desktop:/opt/lampp/htdocs/drupal$ python register.py
    2. u003igf16uvf9fnhaspe3v36e0
    3. {'sort': '0', 'status': '1', 'picture': '', 'uid': '3', 'language': 'es', 'created': '1290805507', 'roles': {'3': 'usuario administrador', '2': 'authenticated user'}, 'signature': '', 'init': 'admin@admin.fake', 'access': '1290858423', 'signature_format': '0', 'theme': '', 'form_build_id': 'form-ac8a1d5923bb603b231df3f65fc8d38f', 'mode': '0', 'pass': '21232f297a57a5a743894a0e4a801fc3', 'threshold': '0', 'mail': 'admin@admin.fake', 'login': 1290858449, 'data': 'a:1:{s:13:"form_build_id";s:37:"form-ac8a1d5923bb603b231df3f65fc8d38f";}', 'timezone': '3600', 'name': 'admin'}
    4. 9
    5. Se ha registrado correctamente al usuario

    Si vamos a Administrar -> Administración de usuarios -> Usuarios veremos nuestro usuario perfectamente creado. Además, podemos iniciar sesión con él.

    A 3 personas les gusta esta entrada

Habilitar Wi-Fi en HP 2133 con Fedora 12

Me dejo aquí un pequeño tip que tengo que buscar cada vez que tengo movimiento en mi netbook con Fedora.

a) wget http://mirror2.openwrt.org/sources/broadcom-wl-4.150.10.5.tar.bz2
b) tar xjf broadcom-wl-4.150.10.5.tar.bz2
c) cd broadcom-wl-4.150.10.5/driver
d) b43-fwcutter -w /lib/firmware wl_apsta_mimo.o

Fuente: http://hp2133.umsw.de/

A 1 persona le gusta esta entrada

Bugs en Python Soaplib 0.8.1. WSDL no reconocido por Axis o Soapui / Use specific ‘len(elem)’ or ‘elem is not None’ test instead

Existen varios bugs reconocidos en la generación y uso del WSDL en soaplib v8.1. Los clientes Soapui y Axis no van a aceptar el WSDL generado por soaplib.

El primer bug se reconoce aquí: http://github.com/jkp/soaplib/issues/#issue/12

Está solucionado en el trunk de soaplib, editamos el fichero /usr/local/lib/python2.6/dist-packages/soaplib-0.8.1-py2.6.egg/soaplib/serializers/primitive.py y cambiamos una línea que nos indican aquí:

http://github.com/jkp/soaplib/commit/1dd0aa6e01ebb04a7c802f261c93534061de8b7d

El segundo bug visto en este enlace da una salida como ésta al acceder al servicio Web:

FutureWarning: The behavior of this method will change in future versions. Use specific ‘len(elem)’ or ‘elem is not None’ test instead.

Este bug provoca errores al utilizar el cliente Java desde Axis. En este caso buscaríamos dos líneas del fichero wsgi_soap.py con la siguiente sentencia:

  1. if payload:

las dos líneas vamos a cambiarlas por:

  1. if payload is not None:

Una solución a mano es modificar los ficheros primitive.py y wsgi_soap.py y compilarlos con

  1. python -mcompileall .

una vez dentro de cada ruta de los ficheros mencionados (es decir, dos veces en total).

Soaplib genera entonces un WSDL correcto, que Axis acepta perfectamente. Axis podrá también acceder sin problemas al servicio Web.

La mejor solución es generar un egg nuevo para la instalación con easy_install.

Pasos:

1. Nos bajamos el código de soaplib de aquí, por ejemplo en ~/Descargas.
2. Modificamos el fichero primitive.py y el fichero wsgi_soap.py
3. Generamos un nuevo egg:

  1. python setup.py bdist_egg

Esto crea un nuevo fichero en ~/Descargas/soaplib-0.8.1/dist/soaplib-0.8.1-py2.6.egg

4. Eliminamos todas las referencias al egg del soaplib antiguo, así como a sus ficheros:

  1. easy_install -mxN PackageName

5. También a sus ficheros:

  1. rm -rf /path.to/python2.6/site-packages/soaplib-0.8.1-py2.6.egg

6. Podemos instalar el nuevo egg:

  1. easy_install ~/Descargas/soaplib-0.8.1/dist/soaplib-0.8.1-py2.6.egg

Podemos probar wsdl2java descargando e instalando Axis:

1. Descargar Axis:

  1. wget http://apache.rediris.es/ws/axis2/1_5_1/axis2-1.5.1-bin.zip

2. Descomprimir Axis:

  1. unzip axis2-1.5.1-bin.zip

3. Accedemos a las utilidades de Axis:

  1. cd axis2-1.5.1/bin

4. Establecer variable JAVA_HOME:

  1. export JAVA_HOME=/path/to/jvm/jre

5. Lanzar wsdl2java contra nuestro servidor ejecutando:

  1. ./wsdl2java.sh -uri http://127.0.0.1:8080/ws?wsdl

A 1 persona le gusta esta entrada

Crea un manual de código en LaTeX de forma automática

Para la formalización de ciertos proyectos, se pide “redactar” un manual de código en el que aparezcan todos los ficheros que componen la aplicación.

Si utilizáis LaTeX, he creado un script que te permite crear el manual de código de la aplicación con sólo llamarlo con los parámetros adecuados.

Deciros que el esqueleto del script, la parte que recorre directorios y ficheros, está basado en el script Arbol de directorios de Paco Debian.

El código del script es el siguiente:

  1. #!/bin/bash
  2. #set -e
  3. #set -u
  4.  
  5. #set -x
  6.  
  7. func_ficheros()
  8. {
  9.     listaficheros=$(find -maxdepth 1 -type f -iname "*."$extpar | sort) # extraer lista ordenada de ficheros del directorio actual
  10.  
  11.     for item in $listaficheros # recorrer lista de ficheros
  12.     do
  13.         extfich=${item##*.} # obtener extension del fichero
  14.         if [ $extfich = $extpar ] # si la extension es la misma que especificamos en $2
  15.         then
  16.             fichero=${item##*"./"} # eliminar todos los "./" que coloca find
  17.             fichero=${fichero//"_"/"_"} # reemplazar todos los "_" por "_", LaTeX trata "_" como error
  18.             echo "section{Fichero "$fichero"}" # creamos la seccion con el nombre del fichero
  19.             echo -e "lstinputlisting{"$PWD"/"${item##*"./"}"}n" # incluimos el fichero con ruta completa y con "_" porque es ruta fisica
  20.         fi
  21.     done
  22. }
  23.  
  24.  
  25. func_recursiva()
  26. {
  27.     for OBJ in * # recorrer el directorio $1
  28.     do
  29.         (
  30.         if [ -d "${OBJ}" ] # es un directorio
  31.         then
  32.             cd "${OBJ}" # cambiar a este directorio
  33.             var=$(find -maxdepth 1 -type f -iname "*."$extpar | wc -l) # ver si hay en el directorio actual ficheros de tipo $2
  34.            
  35.             if [ $var != "0" ] # si hay ficheros de tipo $2
  36.             then
  37.                 nombrecarpeta=${PWD##*$raiz_pwd} #extraer solo el nombre de la carpeta
  38.                 carpeta=${nombrecarpeta//"_"/"_"} # reemplazar todos los "_" por "_", LaTeX trata "_" como error
  39.                 echo -e "n" # retorno de carro
  40.                 echo "chapter{Carpeta "$dirpar$carpeta"}" # crear nueva subseccion con esta subcarpeta
  41.  
  42.                 func_ficheros # procesar los ficheros de este directorio
  43.             fi
  44.             func_recursiva # seguir mirando subcarpetas
  45.         fi
  46.         )
  47.     done
  48. }
  49.  
  50. #################
  51. #          INICIO            #
  52. #################
  53.  
  54. if [ $# != 3 ] # control de errores - utilizar los tres parametros: nombrecarpeta, extensionfichero y ficherolatex
  55. then
  56.     echo -e "ERROR: Incorrecto numero de argumentosn"
  57.     echo "Uso del programa: ./myscript nombrecarpeta extensionfichero ficherolatex"
  58.     echo "Ejemplo: ./myscript micarpeta php codigo.tex"
  59.     exit 0
  60. fi
  61.  
  62. dirpar=$1 # obtener el parametro de carpeta raiz, en func_recursiva se pierde la visibilidad del parametro
  63. extpar=$2 # obtener el parametro de extension a buscar, en func_recursiva se pierde la visibilidad del parametro
  64.  
  65. exec 1&gt;$3 # enlazar salida de echo a fichero pasado como parametro $3
  66.  
  67. cd $1 # entrar al directorio especificado en $1
  68. raiz_pwd=$PWD # guardar la raiz de este directorio, la perderemos al recorrer el arbol en func_recursiva
  69. echo "chapter{Carpeta "$1"}" # crear la carpeta raiz como capitulo
  70.  
  71. func_ficheros # procesar directorio raiz
  72. func_recursiva # llamar a func_recursiva para exploracion completa del directorio

Como veis, cada línea está bien comentada para que no haya ningún problema de comprensión.

La ejecución del script se realiza de la siguiente manera:

./myscript nombrecarpeta extensionfichero ficherolatex

Si por ejemplo queremos realizar un manual de código del conocido CMS Drupal, deberemos llamar al script de la siguiente forma:

neonigma@neonigma-laptop:/opt/lampp/htdocs$ ./myscript drupal php codigo.tex

En líneas generales, el script recorre la carpeta drupal de nuestro servidor Web buscando ficheros con extensión PHP y genera un fichero de LaTeX con la siguiente estructura:

chapter{Carpeta o subcarpeta}
section{Sección para fichero .php encontrado}
lstinputlisting{ruta al fichero php, que provocará el listado de código del mismo}

  • El pdf resultante del proyecto LaTeX puede descargarse haciendo clic aquí.
  • Podéis descargar el script pulsando aquí.
  • El ejemplo de proyecto LaTeX, que incluye el fichero codigo.tex generado automáticamente por el script, puede descargarse pulsando aquí.
A 5 personas les gusta esta entrada

Documentar automáticamente las tablas de una base de datos MySQL

Seguro que a muchos os ha tocado la tediosa tarea de documentar una aplicación. Para la parte técnica, incluso debemos preparar imágenes de las tablas de la base de datos. Pues bien, no para que la máquina realice el 100% del trabajo, pero sí para ahorrarnos mucho pero que mucho curro, mi buen amigo Joaquín Gracia aka Kubiat se curró este script en PHP que me he encargado de modificar a mi gusto y de complementar con otro script en Bash.

Tenemos, por tanto, el siguiente material:

* Script en PHP que muestra en una página Web todas las tablas extraídas de la base de datos y que, además, ha sido modificado para obtener cada tabla en un fichero HTML independiente. Más o menos lo que muestra la siguiente imagen:

La visita a la página Web crea cada tabla de la base de datos en un fichero HTML, como puede verse en el listado por consola:

* Script en Bash que recoge cada fichero HTML del directorio de trabajo y lo convierte a imagen JPG, como muestra la siguiente imagen:

Para utilizar el script en PHP, basta con acceder a la dirección Web local donde lo hayamos colocado. Este script sólo necesita modificar las variables de acceso a la base de datos de la función conecta. En el caso de la imagen, la dirección Web a la que se accede es http://localhost/jscript/tablas.php

Para utilizar el script en Bash, le damos permisos de ejecución con chmod +x nscript y lo ejecutamos (importante que sea dentro de este directorio de trabajo) escribiendo ./nscript

El código de este script es tan sencillo como esto:

  1. #!/bin/bash
  2. #
  3. # Script para la obtencion de imagenes a partir de HTMLs
  4.  
  5. sudo chmod 777 *.html
  6. archivos=$(ls *.html)
  7.  
  8. for lista in $archivos
  9. do
  10.     convert -verbose -density 600x600 -trim +repage $lista $lista.jpg
  11.     rm $lista
  12. done

Lo que significa que el script tomará todos los archivos html de la carpeta, y uno a uno los convertirá a jpg con una densidad de página de 600×600, recortando los espacios en blanco sobrantes y paginando en varias imágenes si no cupiera en una. Después de esta conversión, se borra el fichero html que ya deja de sernos útil.

En el ejemplo hemos utilizado una instalación de Drupal para extraer todas las tablas de la base de datos y convertirlas a imágenes. La muestra de la lista de imágenes resultantes podemos verla en la siguiente imagen:

Obviamente podemos ver que el texto que acompaña a cada campo es demasiado genérico, de ahí a que no todo el trabajo lo realice la máquina. Nosotros podemos, en el paso de la generación de los archivos html, modificar cada campo de cada tabla en los ficheros html para que la descripción se ajuste a lo que queremos. Una vez terminadas las descripciones, lanzaríamos el script en bash y obtendríamos las imágenes automáticamente.

Recordar que este script borra cada fichero HTML, por lo que deberemos guardar en una carpeta los ficheros html por si luego queremos retocar alguna descripción, o eliminar la línea del script en bash que borra cada fichero html.

Descarga el script PHP aquí.
Descarga el script en Bash aquí.

Si la imagen resultante de alguno de los ficheros html nos resulta demasiado grande o demasiado dividida (por ejemplo, la tabla comments de Drupal se ha dividido en dos imágenes), podemos utilizar otro script para conversión manual de un único fichero html.

Este script contiene el siguiente código:

  1. #!/bin/bash
  2. #
  3. # Script para la obtencion de imagenes a partir de HTMLs
  4. # Uso: ./xscript 0.55 nombre-de-pagina-html-sin-extension
  5.  
  6. html2ps -U -s $1 -o $2.ps $2.html
  7. convert -density 600x600 -trim +repage $2.ps $2.jpg

La llamada a este script se realiza de la siguiente forma: ./xscript factor-escala nombre-de-pagina-html-sin-extension

Por ejemplo: ./xscript 0.55 comments

Descarga el script manual aquí.

A 2 personas les gusta esta entrada