iRec orphans - CGastrell/phonegap GitHub Wiki

Como sabemos, cada vez que iniciamos una entrevista **pre-**generamos el archivo de audio. Esto lo hacemos porque necesitamos tenerlo listo o simplemente porque no somos mas listos como para hacerlo de otra forma. Como sea, la situacion es que quedan archivos, que si bien no ocupan nada, es basura que queda por ahi y deberiamos limpiarla.

Para poder determinar que archivos debemos eliminar vamos a:

  • obtener una lista de archivos en la carpeta de audio
  • obtener una lista de paths que estan almacenados en las entrevistas que tenemos guardadas en entrevistas.lista
  • cruzaremos un listado con el otro y, todos aquellos archivos que no existan en el listado extraido de las entrevistas, deberian ser eliminados

Para esto vamos a agregar metodos en entrevistas y en fileApi.

fileApi

La ultima version que tenemos de fileApi tiene estos metodos:

var fileApi = {
  ready: false, // <--solo por las dudas
  initialize: function(callback){
    var path = cordova.file.externalDataDirectory;
    //si no hay problemas, llamamos a callback con el primer
    //parametro en null (lo que seria el error)
    var onResolve = function(directoryEntry) {
      fileApi.dir = directoryEntry;
      fileApi.ready = true;
      callback && callback(null, fileApi);
    }
    //si hay un error llamamos a callback con el error
    //como primer parametro (ver arriba)
    var onError = function(err){
      callback && callback(err, fileApi);
    }
    window.resolveLocalFileSystemURL(path, onResolve, onError);
  },
  writeTextFile: function(file, content, callback) {
    var onFile = function(fileEntry) {
      fileEntry.createWriter(
        function(fileWriter){
          fileWriter.write(content);
          callback && callback(content);
        }, fileApi.onError);
    }
    fileApi.dir.getFile(file, {create: true}, onFile, fileApi.onError);
  },
  onError: function(err) {
    console.log(err);
  },
  getDir: function(dir, callback) {
    var onDir = function(dir){
      callback && callback(null, dir);
    }
    var onError = function(err) {
      callback && callback(err, null);
    }
    fileApi.dir.getDirectory(dir, {create:true}, onDir, onError);
  }
}

fileApi es nuestra interfaz con el sistema de archivos. La idea es que se encargue de la parte pesada y nos devuelva resultados, asi que parece un buen lugar para implementar un metodo que lea el contenido de un directorio y nos arroje el resultado.

En la documentacion del file plugin de phonegap vemos un listado de objetos y clases que el plugin puede manejar para devolvernos funcionalidad. Entre ellos figuran 3 que vamos a necesitar:

  • DirectoryEntry
    • en particular el metodo createReader que, segun documentacion, createReader: Create a new DirectoryReader that can read entries from a directory
  • DirectoryReader
    • cuyo unico metodo es readEntries cuya descripcion es readEntries: Read the entries in a directory
  • FileEntry
    • en la documentacion figura el metodo remove() ...

Entonces, crearemos en fileApi el metodo readDir. Este metodo tomara como argumentos:

  • dir - {String}: el nombre del directorio cuyo contenido queremos listar
  • callback - {Function}: un callback para devolver el resultado

Agreguemos el metodo justo debajo del metodo getDir ya que va a estar intimamente relacionado:

  getDir: function(dir, callback) {
    var onDir = function(dir){
      callback && callback(null, dir);
    }
    var onError = function(err) {
      callback && callback(err, null);
    }
    fileApi.dir.getDirectory(dir, {create:true}, onDir, onError);
  }, // <-- no se olviden esta coma
  readDir: function(directoryEntry, callback) {
    directoryEntry.createReader().readEntries(callback);
  },

Por que estan relacionados los 2 metodos? Porque el metodo createReader es de un DirectoryEntry, es decir, no podemos pasarle un String con el nombre del archivo, necesitamos pasarle el objeto que nos devuelve getDir. Si se fijan, getDir si puede recibir el nombre del directorio que queremos como un {String}. Entonces, la llamada para obtener el contenido de un directorio sera algo asi (recordando que son todas funciones asincronicas):

fileApi.getDir('audio', function(directoryEntry) {
  directoryEntry.readEntries(function(entries){
    // aca finalmente tenemos entries, que
    // es un array con FileEntries
  });
});

Con este metodo implementado ya podemos cerrar fileApi y empezar a agregar metodos en entrevistas

entrevistas

En el objeto entrevistas agregaremos el metodo obtenerListaDeArchivosDeAudio al final del objeto (antes del ultimo cierre de llave, separado con una coma):

  obtenerListaDeArchivosDeAudio: function(callback) {
    fileApi.getDir('audio', function(err, dirEntry){
      if(err){
        callback && callback(err, []);
      }
      fileApi.readDir(dirEntry, function(entries){
        callback && callback(null, entries);
      });
    });
  },

Este metodo tambien lo haremos asincronico, que llamara el callback que le proveamos con:

  • err - {Error}: un error, si lo hubiese
  • entries - {Array}: un array de FileEntries, resultado del fileApi.readDir()

Tambien necesitamos el listado de entrevistas, pero necesitamos un listado mas simple, uno que solo contenga el path de los archivos de audio de las entrevistas, entonces generaremos el metodo obtenerArchivosDeAudioDeEntrevistas(), que tambien sera asincronico. La magia de este metodo sera levantar todo el listado en entrevistas.lista e iterarlo para extraer un array simple con los paths a los archivos de audio:

  obtenerArchivosDeAudioDeEntrevistas: function(callback) {
    var filePaths = [];
    $.each(entrevistas.lista, function(idx, item){
      filePaths.push(item.audioPath);
    });
    callback(filePaths);
  },

Y por ultimo, un metodo que llame a los dos metodos anteriores, encadenado uno con otro, y luego itere por sobre cada archivo de audio y conforme un nuevo listado con aquellos que no figuren entre los archivos de audio de las entrevistas y lo devuelva, tambien de manera asincronica.

Para cruzar los 2 arrays usaremos el metodo Array.indexOf, que nos devuelve un entero representando el indice del array donde se encontro el valor que estamos buscando. Si no se encuentra devolvera -1

Agreguen el metodo encontrarArchivosHuerfanos():

  encontrarArchivosHuerfanos: function(callback) {
    var huerfanos = [];
    entrevistas.obtenerArchivosDeAudioDeEntrevistas(function(paths){
      entrevistas.obtenerListaDeArchivosDeAudio(function(err, files){
        if(err) {
          //si hay error devuelvo el array vacio
          return callback && callback(huerfanos);
        }
        $.each(files, function(i,e){
          // por cada archivo en el directorio
          // buscamos el nativeURL en el array
          // de paths que obtuvimos de las entrevistas
          if(paths.indexOf(e.nativeURL) == -1) {
            // si NO se encuentra
            // es un archivo huerfano
            huerfanos.push(e);
          }
        });
        callback && callback(huerfanos);
      });
    });
  },

Funcionalidad

Como bien nombramos el metodo encontrarArchivosHuerfanos la idea es que solo los busque y los devuelva, no que los borre. Lo que hagamos con ellos sera parte de la funcionalidad donde lo querramos usar.

Podriamos hacerlo al arrancar la aplicacion, o silenciosamente al salir de una entrevista. Pero por el momento vamos a crear un boton con la funcion especifica de buscarlos y borrarlos.

Crearemos el boton en la pagina #home, debajo del boton para entrevistar, en el archivo index.html y le pondremos el id limpiarHuerfanos:

  <div data-role="content">
    <a href="#entrevista-list" class="ui-btn">Archivo de entrevistas</a>
    <a href="#guia-list" class="ui-btn">Entrevistar</a>
    <a href="#" id="limpiarHuerfanos" class="ui-btn">Borrar archivos temporales</a>
  </div>

Luego, en el archivo js/index.js agregaremos un handler para el evento pagecreate de la pagina #home (hasta el momento no existia dado que no inicializabamos nada en #home):

$('#home').on('pagecreate', function(){
  $('#limpiarHuerfanos').on('click', function(evt){

    // cancelamos el comportamiento del boton
    evt.preventDefault();

    // mostramos el spinner, por si demora mucho
    $.mobile.loading('show');

    // y llamamos a nuestro metodo en entrevistas
    entrevistas.encontrarArchivosHuerfanos(function(archivos){
      // contamos cuantos volvieron
      var cantidad = archivos.length;

      // luego iteramos por el resultado
      $.each(archivos, function(i,e){
        // y como sabemos que son FileEntries
        // ejecutamos .remove() en cada uno
        e.remove();
      });
      // mostramos un mensaje y escondemos el spinner
      alert('Se eliminaron '+cantidad+' archivos huerfanos');
      $.mobile.loading('hide');
    });
  });
});
⚠️ **GitHub.com Fallback** ⚠️