Si a la gente e…

Si a la gente el ver a una persona mayor tocando rock’ n’ roll no le encaja, simplemente pueden no mirar. Hay gente que mejora con la edad si les importa lo que hacen, y ése es mi caso. Realmente nunca he hecho ésto por dinero, ya que nunca hemos vendido demasiados discos. Muchos grupos de mierda son millonarios, pero no es nuestro caso.

Ian «Lemmy» Kilmister

Tutorial de IdexedDB

El almacenamiento en el navegador se está convirtiendo en algo fundamental para el desarrollo de aplicaciones web con HTML5 y Javascript. En un primer momento parecía que WebSQL se iba a convertir en el standar, apoyado por Google y Apple en sus navegadores basados en Webkit. Pero la oposición de Microsoft y, sobre todo, de Mozilla sumada al auge de las bases de datos no relacionales en el ámbito del desarrollo móvil, ha hecho que IndexedDB se postule como el nuevo standar para almacenamiento en estos desarrollos. Desde 2010 la W3C ha dado por obsoleto WebSQL.

Todavía no me he puesto a trabajar con él en profundidad, pero os traigo aquí un tutorial basado en este de HTML5Rocks donde se nos explica cómo migrar de WebSQL a IndexedDB.

El ejemplo es una base de datos para un TODO list. Con WebSQL simplemente operábamos como si de una base de datos SQL se tratara, pero el concepto de IdexedDB es distinto. Se trata de un almacén de objetos Javascript donde guardamos parejas de valores: una clave y el objeto. Las búsquedas no se hacen con una query sino que se realiza una consulta sobre un índice que luego es iterado.

El primer paso con IdexedDB es abrir una base de datos:

window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;

// Manejando el prefijo de Chrome para IDBTransaction/IDBKeyRange.
if ('webkitIndexedDB' in window) {
  window.IDBTransaction = window.webkitIDBTransaction;
  window.IDBKeyRange = window.webkitIDBKeyRange;
}

indexedDB.db = null;

// Para depurar enviamos los errores a la consola
indexedDB.onerror = function(e) {
  console.log(e);
};

Arriba ya tenemos abierta una base de datos. Lo segundo, y también fundamental, es crear un almacén de objetos, igual que creamos tablas cuando trabajamos con una base de datos SQL.

indexedDB.open = function() {
  var request = indexedDB.open("todos");

  request.onsuccess = function(e) {
    var v = "2.0 beta"; // puedes poner cadenas en el nombre de la versión
    todoDB.indexedDB.db = e.target.result;
    var db = todoDB.indexedDB.db;
    // En la transacción setVersión es donde podemos crear los almacenes
    if (v!= db.version) {
      var setVrequest = db.setVersion(v);

      // para poder crear almacenes tenemos que hacerlo en onsuccess
      setVrequest.onfailure = todoDB.indexedDB.onerror;
      setVrequest.onsuccess = function(e) {
        if (db.objectStoreNames.contains("todo")) {
          db.deleteObjectStore("todo");
        }

        var store = db.createObjectStore("todo", {keyPath: "timeStamp"});

        var transaction = e.target.result;
        transaction.oncomplete = function() {
          todoDB.indexedDB.getAllTodoItems();
        }
      };
    } else {
      todoDB.indexedDB.getAllTodoItems();
    }
  };

  request.onfailure = todoDB.indexedDB.onerror;
};

Aquí hemos abierto el almacén todos, tras ello comprobamos que la versión de la base de datos sea diferente a la que estamos definiendo. En caso de que no lo sea, con setVersion creamos el nuevo almacén. El único sitio donde podemos modificar la estructura de la base de datos es en setVersion, donde se nos permite crear y eliminar almacenes de objetos.

El siguiente paso es ver cómo podemos añadir objetos a la colección. Para ello creamos la función addTodo.

indexedDB.addTodo = function() {
  var db = todoDB.indexedDB.db;
  var trans = db.transaction(['todo'], 'readwrite');
  var store = trans.objectStore('todo');

  var data = {
    "text": todoText, // el texto debe ser visible aquí
    "timeStamp": new Date().getTime()
  };

  var request = store.put(data);

  request.onsuccess = function(e) {
    todoDB.indexedDB.getAllTodoItems();
  };

  request.onerror = function(e) {
    console.log("Error Adding: ", e);
  };
};

Este método addTodo comienza cogiendo una referencia a la base de datos, seguimos definiendo una transacción readwrite y obtenemos una referencia a la coleccion de objetos. Luego creamos los datos como un objeto JSON y lo metemos en la base con el método put. Guardaremos el texto y un timestamp que servirá como clave única. Definimos que se re-renderizará en caso de éxito y que en caso de error se enviará a la consola.

Y si podemos insertar datos, desde luego podemos también borrarlos. Para ello crearemos el método deleteTodos:

indexedDB.deleteTodo = function(id, text) {
  if (confirm("Are you sure you want to Delete " + text + "?")) {
    var db = todoDB.indexedDB.db;
    var trans = db.transaction(["todo"], 'readwrite');
    var store = trans.objectStore("todo");

    var request = store.delete(id);

    request.onsuccess = function(e) {
      todoDB.indexedDB.getAllTodoItems();
    };

    request.onerror = function(e) {
      console.log("Error Adding: ", e);
    };
  }
};

El funcionamiento es similar al anterior método, pero con un delete en lugar del put, que recibe la clave del objeto para ser borrado.

Finalmente vamos con la lectura e impresioń de datos en pantalla, que a fin de cuentas es lo que nos importa en estos casos. Si queremos almacenar algo es para poder recuperarlo posteriormente.

function showAll() {
  document.getElementById("ourList").innerHTML = "";

  var request = window.indexedDB.open("todos");
  request.onsuccess = function(event) {
    // Enumerar el almacén de objetos completo.
    var db = todoDB.indexedDB.db;
    var trans = db.transaction(["todo"], 'readonly');
    var request = trans.objectStore("todo").openCursor();
    var ul = document.createElement("ul");

    request.onsuccess = function(event) {
      // Esto es un hack para versiones antiguas de Firefox (older versions than 6)
      var cursor = request.result || event.result;

      // Cuando el cursor esté a nulo es que hemos terminado la enumeración, por lo que
      // actualizamos el DOM
      if (!cursor) {
        document.getElementById("ourList").appendChild(ul);
        return;
      }

      var li = document.createElement("div");
      li.textContent = "key: " + cursor.key + " => Todo text: " + cursor.value.text;
      ul.appendChild(li);
      cursor.continue();
    }
  }
}

La idea en el caso de mostrar los resultados es abrir la transacción como readonly, creamos una lista (para este caso) y en cada iteración del cursor creamos un elemento li con un div dentro con los datos que vamos agregando a la lista. Cuando se termine el cursor, en la última iteración, añadimos la lista al DOM.

Como apreciación he de decir que IndexedDB, aunque desde luego es muy útil, tiene cosas que me parecen innecesariamente complicadas. Tal vez sea porque vengo del mundo de las bases de datos SQL pero WebSQL me parecía más claro y usable. Y con todo yo estoy acostumbrado a trabajar con Javascript (reconozco que nunca cosas muy complejas), no quiero imaginarme a muchos programadores que no tocan jamás el lado del cliente.

Hacer un UPSERT en MySQL

Si el otro día os contaba cómo hacer un INSERT IF NOT EXISTS en MySQL, hoy vamos a ver cómo hacer un UPSERT. La idea es intentar un INSERT, pero en caso de que se de una duplicidad de un campo único (una clave primaria, un campo UNIQUE…) en lugar de insertar actualizará el campo en cuestión. Hasta esta mañana desconocía esta forma de insertar, pero me lo comentaron en StackOverflow por un problemilla que tenía con un procedimiento almacenado.

La sintaxis sería más o menos:

INSERT INTO tabla (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1, b=2;

Os pongo un ejemplo práctico, metido dentro de un procedimiento almacenado. Recibe el id de un usurio y una variable bit que representa si se ha conectado o ha cerrado sesión, guarda estos datos junto con el momento en que se ha realizado dicha acción en una tabla usada para llevar el control de lo mismo (bueno, es más o menos lo mismo que me pusieron en SO):

DELIMITER $$
CREATE PROCEDURE `sp_UltimoLogin`(id_in int, accion_in bit)
BEGIN
  INSERT INTO `login` (`idusuario`, `fecha`, `accion`) VALUES (id_in, now(), accion_in)
    ON DUPLICATE KEY UPDATE `fecha` = now(), `accion` = accion_in;
END $$

Un truquito que os ahorrará realizar comprobaciones en muchos supuestos, aligerando la carga de trabajo y reduciendo líneas de código.

Mailvelope: encriptar correos de Gmail en Chrome con GPG

Mailvelope es un complemento de Chrome que nos permite utilizar GPG para encriptar nuestros correos. De momento para Firefox no está disponible pero podéis descargar desde GitHub el código y compilarlo.

Si sois novatos en esto de la criptografía os diré que GPG funciona como un sistema criptográfico asimétrico. Ya he hablado de esto antes, pero resumiendo rápidamente hay dos claves, una pública que puede saber cualquiera y una privada que sólo tú sabes. Si quieres enviar un correo encriptado usas la clave pública del receptor para encriptarlo, y ese correo sólo podrá ser leído si se usa la clave privada para desencriptarlo.

Lo primero, tras la instalación, es generar una clave. Podéis hacerlo pulsando el icono que tenéis arriba a la derecha. En el submenú desplegable elegís Options.

Icono del navegador Mailvelope

En el menú de la izquierda seleccionáis Generate Key y ahí veréis un menú como el de la imagen de debajo donde podéis elegir el algoritmo y la longitud de clave, la contraseña que usaréis para desencriptar con vuestra clave privada y varios datos como el correo o el nombre.

Generar clave Mailvelope

El par de claves que creamos debe aparecerá en Display keys. La seleccionamos y hacemos click en Export » Display public key, como puedes ver en la imagen. Puedes elegir entre exportarla a un fichero o copiar el texto y subirlo a un servidor de llaves públicas.

Ok ¿cómo enviar un correo? Es simple. Lo primero es que tenéis que añadir a vuestro llavero/keyring la clave del usuario al que le queréis enviar el correo, que podéis importar desde el menú de opciones (como ya hemos dicho en el párrafo anterior, los usuarios pueden publicar su clave pública en un servidor de llaves o enviarte un archivo con ella).

Importando clave
Menú de importación de claves

Una vez tienes tu clave entras en tu webmail (es compatible con varios) y desde allí ya verás a la derecha un icono como el de la imagen cuando empieces a redactar.

icono de redactar Mailvelope

Si pulsas en él se te abrirá una ventana modal como la de la siguiente foto. En ella puedes redacatar el correo normalmente. Si pulsas el icono con forma de candado te abrirá un menú donde eliges la clave pública del receptor y, aceptando al clickar en Transfer, generará el mensaje encriptado. Luego simplemente envías de forma habitual.

redactar correo encriptado

Para leerlos es más simple, tan pronto recibas un correo encriptado con tu clave pública sólo tendrás que hacer click, meter tu clave privada y leer.

Así que ya puedes gozar de seguridad en tus envíos confidenciales sin necesidad de romperte mucho la cabeza.