Copia de seguridad de sqlite a la unidad sdcard en Android

Cuando ya tenemos una aplicación en producción y ocurre un problema causado por la base de datos suele ser complicado averiguar donde esta el problema, es buena idea tener una copia de la misma base de datos para ver exactamente que esta pasando.

Hace un tiempo que estaba buscando la manera de sacar un backup de la base de datos de una aplicación en Android, después de probar varias opciones encontré un segmento de código que sí ha funcionado:

public void backupdDatabase(){
    try {
	    File sd = Environment.getExternalStorageDirectory();
	    File data = Environment.getDataDirectory();
	    String packageName  = "com.yourapp.package";
	    String sourceDBName = "mydb.db";
	    String targetDBName = "mydb";
	    if (sd.canWrite()) {
	    	Date now = new Date();
		String currentDBPath = "data/" + packageName + "/databases/" + sourceDBName;
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm"); 
		String backupDBPath = targetDBName + dateFormat.format(now) + ".db";

		File currentDB = new File(data, currentDBPath);
		File backupDB = new File(sd, backupDBPath);

		Log.i("backup","backupDB=" + backupDB.getAbsolutePath());
		Log.i("backup","sourceDB=" + currentDB.getAbsolutePath());

		FileChannel src = new FileInputStream(currentDB).getChannel();
		FileChannel dst = new FileOutputStream(backupDB).getChannel();
		dst.transferFrom(src, 0, src.size());
		src.close();
		dst.close();
	    }
	} catch (Exception e) {
		Log.i("Backup", e.toString());
	}
}

Ustedes ya se encargan de cambiar los valores y mostrar los mensajes de error.

Me olvidaba que necesitarán del permiso para escribir en la memoria SD:

   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

 

Obtenido de: Stackoverflow

 

Probando UDFs para MySQL: Title Case

Conocemos «de sobra» lo que hacen las funciones LCASE y UCASE en MySQL, otros gestores también la implementan con alguna variación en el nombre pero con el mismo resultado.

Pero que pasa si queremos una función que convierta un texto al tipo Titulo, conocido también como title case, es decir convertir «un texto arbitrario» en «Un Texto Arbitrario«, para este caso no existe la función mágica que haga esa conversión y tenemos que escribir una propia.

No es necesario «reinventar la rueda» (aunque podría hacerlo), buscando encontré un repositorio de funciones UDF para MySQL: http://www.mysqludf.org/ , hay varias funciones agrupadas según el objetivo/funcionalidad, solo tienen que descargar el código fuente, compilarlo y agregarlo como funciones nuevas en vuestros servidores MySQL.

He aislado la función str_ucwords (que convierte un texto a «Title Case») en un archivo separado para mostrarles como es que podemos compilar una UDF para MySQL.Pero he modificado el archivo por que solo funcionaba con parámetros constantes, ahora la función acepta nombres de columnas como parámetros.

Pueden descargar el archivo fuente: ucwords.c

Para compilarlo necesitan unas tres instrucciones:

#Compilamos el programa
ivancp@ubuntu$ gcc ucwords.c -o ucwords.so -shared -lmysqlclient -I/usr/include/mysql

#Copiamos el archivo de salida en la carpeta de plugins de mysql
ivancp@ubuntu$ sudo cp ucwords.so /usr/lib/mysql/plugin

#Quitamos el permiso de ejecución del archivo
#para que pueda cargarlo correctamente
ivancp@ubuntu$ sudo chmod -x  /usr/lib/mysql/plugin/ucwords.so

 

  • La opción -shared compila el archivo para que pueda ser usado como librería.
  • La descripción del resto de parámetros van a encontrarla en la entrada: Leer datos de MySQL desde C

Ahora debemos agregar la función a MySQL:

mysql> create function str_ucwords returns string soname 'ucwords.so';
Query OK, 0 rows affected (0.00 sec)

mysql>

 

Ahora veamos algunas pruebas, esta es una consulta común (los datos están almacenados en mayúsculas o Upper Case):

mysql>select nombres from tabla;
+----------------------------------+
| nombres                          |
+----------------------------------+
| AGUILAR PALACIOS RICARDO         |
| ANDIA MARQUEZ LUIS MELITON       |
| CUYUBAMBA RAMOS VICTOR ALEJANDRO |
| FELICIANO VELAZCO VICTORIA       |
| FLORIDA EVANGELISTA LILA REYNA   |
| LEON LAULATE FERNANDO            |
| PALACIOS LOPEZ ROXANA BEATRIZ    |
| REATEGUI RAMIREZ CARLOS          |
| REINOSO MORI JORGE WILLY         |
| SALAZAR VALDIVIA WALTER          |
| SANCHEZ TUTUSIMA CARMEN ROSA     |
| VELAZCO BERROA PETRONA           |
+----------------------------------+
12 rows in set (0.00 sec)

 

Aquí esta lo que queremos ver, la consulta primero convierte a minúsculas todo el campo y luego convierte éste resultado a Title Case, vean:

mysql>select str_ucwords(lcase(nombres)) from tabla;
+----------------------------------+
| str_ucwords(lcase(nombres))      |
+----------------------------------+
| Aguilar Palacios Ricardo         |
| Andia Marquez Luis Meliton       |
| Cuyubamba Ramos Victor Alejandro |
| Feliciano Velazco Victoria       |
| Florida Evangelista Lila Reyna   |
| Leon Laulate Fernando            |
| Palacios Lopez Roxana Beatriz    |
| Reategui Ramirez Carlos          |
| Reinoso Mori Jorge Willy         |
| Salazar Valdivia Walter          |
| Sanchez Tutusima Carmen Rosa     |
| Velazco Berroa Petrona           |
+----------------------------------+
12 rows in set (0.00 sec)

 

Funciona a la perfección!

Por favor revisen la documentación, en otro post explicaré que deben tener en cuenta para elaborar sus propios UDF:

http://dev.mysql.com/doc/refman/5.0/en/adding-udf.html

WordPress ahora acepta sentencias LaTeX

Gracias a un plugin para WordPress incluido en Jetpack podemos incluir expresiones matemáticas en los posts fácilmente. Solo necesitan instalar Jeckpack en su WordPress para habilitar esta opción.

Esto es especialmente útil al momento de explicar un algoritmo, que incluya expresiones matemáticas, o para quienes tengan un blog y sean profesores/físicos/matemáticos/etc

Solamente tienen que escribir la expresión matemática en sintaxis  entre los signos de dolar:

[expresion] 

Aquí algunos ejemplos:

x = a_0 + \frac{1}{\displaystyle a_1  + \frac{1}{\displaystyle a_2  + \frac{1}{\displaystyle a_3 + a_4}}}

x = a_0 + \frac{1}{a_1 + \frac{1}{a_2 + \frac{1}{a_3 + a_4}}}

z \left( 1 \ +\ \sqrt{\omega_{i+1} + \zeta -\frac{x+1}{\Theta +1} y + 1}  \ \right)  \ \ \ =\ \ \ 1

El código fuente de los ejemplos anteriores es:

x = a_0 + \frac{1}{\displaystyle a_1  + \frac{1}{\displaystyle a_2  + \frac{1}{\displaystyle a_3 + a_4}}}

x = a_0 + \frac{1}{a_1 + \frac{1}{a_2 + \frac{1}{a_3 + a_4}}} 

z \left( 1 \ +\ \sqrt{\omega_{i+1} + \zeta -\frac{x+1}{\Theta +1} y + 1}  \ \right)  \ \ \ =\ \ \ 1 

Simulando secuencias en MySQL

Quienes hemos usado PostgresSQL solemos fastidiarnos con el código que hay que escribir para tener un campo auto-numérico, pero también hemos llegado a extrañar esa característica cuando usamos MySQL.

Como sabemos MySQL, usa la propiedad AUTO_INCREMENT para campos numéricos que usamos en claves primarias.

Pero en que situaciones podemos necesitar una secuencia? pues les doy algunas ideas:

  • Cuando queremos tener mas de un campo auto-numérico en una tabla.
  • Cuando necesitamos un contador general que podemos utilizar en mas de una tabla.
  • No se me ocurren mas, pero estoy seguro que en algún momento podemos darle un uso.

Manos a la obra:

Necesitaremos una tabla para guardar el valor actual de la secuencia:

create table _sequence
(
	seq_name varchar(50) not null primary key,
	seq_val int unsigned not null
);

Vamos a necesitar también una función para obtener el valor siguiente de la secuencia, incluyendo casos como:

  • Si no existe la secuencia, crearla (para qué complicarnos).
  • Actualizar el nuevo valor para la siguiente llamada
  •  

    He aquí la función getNextSeq que recibe como parámetro, el nombre de la secuencia:

    /*Funcion que devuelve el siguiente valor de una secuencia*/
    delimiter //
    drop function if exists getNextSeq//
     
    create function getNextSeq(sSeqName varchar(50)) returns int unsigned
    begin
        declare nLast_val int;
    
        set nLast_val =  (select seq_val 
                              from _sequence 
                              where seq_name = sSeqName);
        if nLast_val is null then
            set nLast_val = 1;
            insert into _sequence (seq_name,seq_val) 
            values (sSeqName,nLast_Val);
        else
            set nLast_val = nLast_val + 1;
            update _sequence set seq_val = nLast_val 
            where seq_name = sSeqName;
        end if;
    
        return nLast_val;
    end//
     
    delimiter ;

     

    Ahora necesitamos (a manera de mantenimiento) un procedimiento para modificar el valor de una secuencia:

    /*Procedimiento que crea/establece el valor de una secuencia*/
    delimiter //
    drop procedure if exists sp_setSeqVal//
    
    create procedure sp_setSeqVal(sSeqName varchar(50), nVal int unsigned)
    begin
        if (select count(*) from _sequence where seq_name = sSeqName) = 0 then
            insert into _sequence (seq_name,seq_val) 
            values (sSeqName,nVal);
        else
            update _sequence set seq_val = nVal 
            where seq_name = sSeqName;
        end if;
    end//
    
    delimiter ;
    

     

    Es todo!, ahora sólo necesitamos hacer algunas pruebas, para ello vamos a crear una tabla en la cual vamos a tener dos campos auto-generados con nuestra secuencia.

    create table multiple_autonums
    (
       id int not null primary key auto_increment,
       seq_1 int, -- Secuencia 1
       seq_2 int, -- Secuencia 2
       unique(seq_1),
       unique(seq_2)
    );

    Para que la magia ocurra debemos crear también un trigger antes de insertar:

    /*trigger ejecutado antes de insertar que obtiene
      los siguientes valores de las secuencias*/
    
    delimiter //
    drop trigger if exists multiple_autonums_ai//
    
    create trigger multiple_autonums_bi before insert on multiple_autonums
    for each row
    begin
       set new.seq_1 = getNextSeq("seq_1");
       set new.seq_2 = getNextSeq("seq_2");
    end//
    
    delimiter ;
    

     

    Hagamos algunas pruebas

    -- insertando valores para ver que pasa.
    insert into multiple_autonums (id) values (null),(null),(null);
    
    -- Resultado de la primera inserción
    select * from multiple_autonums;
    +----+-------+-------+
    | id | seq_1 | seq_2 |
    +----+-------+-------+
    |  1 |     1 |     1 |
    |  2 |     2 |     2 |
    |  3 |     3 |     3 |
    +----+-------+-------+
    3 rows in set (0.00 sec)
    
    -- Modificando las secuencias
    call sp_setSeqVal("seq_1",47);
    call sp_setSeqVal("seq_2",9786);
    
    -- Insertando nuevamente los valores
    insert into multiple_autonums (id) values (null),(null),(null);
    
    -- Viendo que ha pasado
    select * from multiple_autonums;
    +----+-------+-------+
    | id | seq_1 | seq_2 |
    +----+-------+-------+
    |  1 |     1 |     1 |
    |  2 |     2 |     2 |
    |  3 |     3 |     3 |
    |  4 |    48 |  9787 |
    |  5 |    49 |  9788 |
    |  6 |    50 |  9789 |
    +----+-------+-------+
    

     

    Que les pareció?

    Me acabo de dar cuenta que hay algunas condiciones que no he tomado en cuenta, pero de todas formas sirve. Incluso pueden modificar las funciones para hacer mas complejo el calculo del valor siguiente, se me ocurre por ejemplo que podemos generar numeros de facturas al estilo: [serie]-[numero] ejem: 001-00485

    Espero les sirva.

Como hacer ping desde PHP

Cuando queremos «hacer algo» con algún equipo de nuestra red y éste no responde es por que probablemente este apagado/desconectado/colgado/etc, ping es lo primero que ejecutamos para confirmar nuestras sospechas.

Pero, y si hemos preparado un script para «hacer algo» (como sacar un backup) periódicamente debemos asegurarnos que el equipo esta conectado y encendido antes de meter la pata.

Muchos de los scripts que he desarrollado usan php-cli, me parece mas amigable que bash por que además puedo leer y escribir en bases de datos y también usar ncurses, es una maravilla!

Intentando con varios métodos, finalmente tengo algo que es de utilidad para determinar si un host esta «vivo» mediante un ping desde PHP:

getMessage();
}
else
{
    $ping->setArgs(array('count' => 1)); //Envios = 1
    $result = $ping->ping($host);

    if($result->getReceived() == 1 )  //Si recibimos lo 
    {
          echo "El equipo esta encendido!";
    }
    else
    {
          echo "El equipo esta apagado!";
    }
}
?>

Antes necesitamos tener instalado php-pear y Net_Ping:

ivancp@ubuntu~$ sudo apt-get install php-pear
ivancp@ubuntu~$ sudo pear install Net_Ping

Espero les sirva!

Mi archivo .vimrc

Ha sido duro aprender a usar vim, sinceramente no es facil, pero cuando uno se acostumbra a la forma de trabajo de éste poderoso editor no puedes dejar de usarlo. No uso otro editor de texto a menos que sea completamente necesario, los archivos que son mas frecuentes para mi son archivos fuente de C/C++, PHP, Python, Texto plano, HTML, Bash, y archivos de configuración de Ubuntu.

Existen muchos editores para programar como Netbeans, Codeblocks, etc, etc… pero por alguna razón prefiero usar vim.

Quiero compartir con ustedes parte de mi archivo .vimrc que es cargado cuando el editor es invocado. Puede contener todo lo que necesiten, podría considerarse como un archivo de configuración, aquí les dejo mi archivo comentado.

"Habilitar el resaltado de la sintaxis
syntax enable

"Para que los tabs no ocupen mucho espacio
set shiftwidth=4
set tabstop=4

"Si estamos usando gVIM entonces cambiamos el
"esquema de colores (no me agrada el fondo blanco)
if has("gui_running")
	colorscheme torte
endif

"Muestra los comandos que estamos por ejecutar (para evitar desastres)
set showcmd

"Habilita la sangría automática para archivos fuente
autocmd BufRead,BufNewFile *.c,*.cpp,*.h,*.php,*.htm,*.html set cindent

"Plantillas de archivos: podemos tener archivos fuente como plantillas
"de tal modo que nos ahorramos en escribir unas cuentas lineas de codigo
"por ejemplo si creamos un nuevo archivo .cpp cargamos un Hola mundo

autocmd BufNewFile *.cpp 0read ~/apps/confs/template.cpp
autocmd BufNewFile *.php 0read ~/apps/confs/template.php

"Para ver los números de linea
set number

El archivo no esta completo, por que tengo varias funciones implementadas que amerita otro post.

Mostrar posts de phpBB en una pagina externa

phpBBAlgunas veces queremos mostrar el contenido del post de phpBB en una pagina externa (fuera del foro) pero suele ser frustrante estudiar y utilizar el código de phpBB para extraer (inclusive) un texto simple, las medidas de seguridad contra el SPAM hacen que el código tenga niveles de complejidad difíciles de comprender.

Después de algunas horas estudiando el codigo y algunos tutoriales publicados sobre como extraer entradas del foro, estoy logrando migrar todos los artículos a phpBB de tal forma que va a ser mucho mas facil para los miembros de Latindevelopers publicar sus propios artículos.

Desistí de usar phpBB CMS Mod por que aun no esta listo y complicaría la migración, me parece mas como usar phpBB, lo único que me preocupa es que aparezcan algunos agujeros en la seguridad de phpBB, pero ya me ha pasado y no creo que sea gran cosa.

Aqui comparto el código para poder mostrar un post de phpBB fuera del foro, pero no esta todo el codigo, deben leer el articulo antes de usar el siguiente codigo, hice algunas modificaciones:

$posts_ary = array(
        'SELECT'    => 'p.*, t.*, u.username,
                         u.user_colour, p.bbcode_bitfield ',
        'FROM'      => array(
            POSTS_TABLE     => 'p',
        ),
        'LEFT_JOIN' => array(
            array(
                'FROM'  => array(USERS_TABLE => 'u'),
                'ON'    => 'u.user_id = p.poster_id'
            ),
            array(
                'FROM'  => array(TOPICS_TABLE => 't'),
                'ON'    => 'p.topic_id = t.topic_id'
            ),
        ),
        'WHERE'     =>  'p.post_id = ' . $post_id_article ,
        'ORDER_BY'  => 'p.post_id DESC',
    );

$sql = 'SELECT *
    FROM ' . ATTACHMENTS_TABLE . '
    WHERE post_msg_id = '.$post_id_article.'
            AND in_message = 0
    ORDER BY filetime DESC, post_msg_id ASC';
$result = $db->sql_query($sql);
$attachments = array();
while ($row = $db->sql_fetchrow($result))
{
    $attachments[$post_id_article][] = $row;
}

$db->sql_freeresult($result);

$posts = $db->sql_build_query('SELECT', $posts_ary);
$search_limit = 1;
$posts_result = $db->sql_query_limit($posts, $search_limit);

if($posts_row = $db->sql_fetchrow($posts_result))
{
    $topic_title = $posts_row['topic_title'];
    $post_author = get_username_string('full',
                      $posts_row['poster_id'],
                      $posts_row['username'],
                      $posts_row['user_colour']);
    $post_date = $user->format_date($posts_row['post_time']);
    $post_link = append_sid("{$phpbb_root_path}viewtopic.$phpEx",
                  'f=' .$posts_row['forum_id'] .
                  '&amp;t=' . $posts_row['topic_id'] .
                  '&amp;p=' . $posts_row['post_id']) . '#p' .
                   $posts_row['post_id'];
    $bbcode_bitfield = $posts_row['bbcode_bitfield'];

    $post_text = nl2br($posts_row['post_text']);

    $bbcode = new bbcode(base64_encode($bbcode_bitfield));
    $bbcode->bbcode_second_pass($post_text,
                                $posts_row['bbcode_uid'],
                                $posts_row['bbcode_bitfield']);

    $post_text = smiley_text($post_text);

    parse_attachments($forum_id,
                        $post_text,$attachments[$post_id_article],
                        $update_count);

    echo $topic_title."
"; echo $post_text; }

Al ejecutar el código podran ver la entrada, pero tambien tienen que copiar los estilos relacionados con el tema que estan usando, incluso los archivos javascript.

CMS based on phpBB posts

Maldita sea…!

Un sabor amargo me queda despues de hoy!

Sucede que estamos en medio de la implantación de un pequeño software en la Municipalidad de Puno y hace algunas horas nos dimos con la sorpresa que el tipo de dato time_t incrustado en la clase CTime solo soporta fechas desde 01/01/1970 al 31/12/2039… maldita sea!

Tendremos que trabajar algunas horas extra solucionando ese «problemita».

Se había tomado la decisión de guardar las fechas en segundos para poder hacer mejor los cálculos posteriormente, creo que eso no va a cambiar. Pero se trabajo de igual manera con el resto de campos de tipo fecha. (craso error)

El problema saltó cuando se intentó registrar a alguien que había nacido en el año 1949: ¡Horror!

¿Por que no nos dimos cuenta? Aun que el problema pueda ser solucionado con unas cuantas líneas de código y previa modificación del modelo de la base de datos, ¿Por qué no pensaron en esto? (Esto se parece al problema de la memoria base de 256K) Creo que echarle la culpa a otros es de malos amigos, así es que dejémoslo ahí.

De ahora en adelante, utilizaremos la clase COleDatetime que evitarán sorpresas futuras y post’s como éste.

PD. Hay que estar preparados para el año 2039.

Articulos enviados en Marzo

Las semanas que pasaron nuestro servidor ha estado muy inestable, perdimos 2 meses de activad y muchos archivos, pero finalmente nos recuperamos del desastre y seguimos adelante con Latindevelopers.com

Teníamos pendientes 4 articulos por publicar:

Shell para Simulación

LaPetite – La Aplicación M.F.C. mas Pequeña

ImageCtrl

CARGAR JPG, GIF, BMP todo en uno

Espero que no vuelva a suceder el mismo desastre o que no afecte tanto a la comunidad.

Las proximas semanas vamos a terminar de implementar algunas secciones del site.

Problema solucionado ODBC Connector

MySQL Generalmente me conecto a MySQL con ayuda del controlador ODBC que provee el mismo MySQL. Como sabemos todo MySQL esta en pledo desarrollo, ocurre que se presentan situaciones que no fueron contempladas, por consiguiente hay errores que perjudican el desarrollo haciendo que uno tome precausiones antes de utilizar un codigo especifico. Muchos hemos crecido con MySQL y creo que si no hubiera sido asi lo hubieramos desechado por detalles como este que les voy a mostrar a continuacion.

Para no darle mas vueltas al asunto concretamente el problema es como sigue:

Cuando se intentaba leer un campo varchar (o relacionado) que este vacio o NULL, el controlador ODBC lo reconocía como UNKNOWN, evidentemente no podía realizarse el paso de valores al tipo CString o char* del Visual C++.

Para evitar ese problema se tenía que averiguar la longitud del campo que se va a leer, algo asi como esto:

ULONG len = 0;
rs.GetAccessor()->GetLength(2,&len); // segundo campo
if(len > 0)
{
	rs.GetFieldValue(2,sValue);
	//...
}

Esto sucedia con las versiones del ODBC Connetor inferiores a la 3.51.12. Ahora el codigo anterior se reduce a la linea que lee el campo y hace el paso del valor a la variable.