Con Firefox 3 es más fácil que nunca vigilar el estado de las descargas. Aunque ya era posible hacer esto en versiones previas de Firefox, solo era posible hacer posible tener un observador en cada momento. Firefox 3 introduce un nuevo API que permite cualquier numero de observadores para las descargas.
Este articulo demuestra como vigilar las descargas en Firefox 3, usando el administrador de descargas. Como un bonito añadido adicional, demuestra demás cómo usar el API Storage para crear instrucciones sqlite en una base de datos. El resultado es una ventana que puedes abrir seleccionando "Registro de descargas" en el menú herramientas, que muestra todas las descargas que se han iniciado desde que se instaló la extensión. En la lista está el nombre del archivo, las horas de comienzo y final de la descarga, la velocidad de descarga y el estado de la descarga. Se incluye un título emergente que muestra la URL completa de la fuente del archivo.
Descarga un ejemplo completo en samples/extension-samples/DownloadLogger.zip
Configuración
Al cargar la extensión, se realizarán algunas tareas de limpieza rutinaria. En particular, necesita que exista una versión de la interfaz del administrador de descargas nsIDownloadManager
y crea una base de datos en donde almacenará sus datos.
onLoad: function() { // código de inicialización this.initialized = true; this.strings = document.getElementById("downloadlogger-strings"); this.dlMgr = Components.classes["@mozilla.org/download-manager;1"] .getService(Components.interfaces.nsIDownloadManager); this.dlMgr.addListener(downloadlogger); // Abre la base de datos, poniendo su archivo en el directorio de perfil (profile) this.dbFile = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties) .get("ProfD", Components.interfaces.nsIFile); this.dbFile.append("downloadlogger.sqlite"); // Accede al servicio de almacenamiento y abre la base de datos this.storageService = Components.classes["@mozilla.org/storage/service;1"] .getService(Components.interfaces.mozIStorageService); var dbConn = this.storageService.openDatabase(this.dbFile); // Ahora crea la tabla; si ya existe, esto produce un error, pero ¡no importa! dbConn.executeSimpleSQL("CREATE TABLE items (source TEXT, size INTEGER," + " startTime INTEGER, endTime INTEGER," + " speed REAL, status INTEGER)"); dbConn.close(); },
Esto es bastante simple. El administrador de descargas es almacenado en una variable en el objeto downloadlogger
para ser usado más tarde y llama a su método addListener()
para empezar la escucha de los cambios de estado de descargas. El archivo de la base de datos se abre y se ejecuta la instrucción sqlite: CREATE TABLE
para crear la tabla.
Finalmente, se cierra la base de datos.
close()
de mozIStorageConnection
se añade en la versión alpha 8 de Firefox 3. En versiones anteriores de Firefox, no hay forma explícita de cerrar la base de datos; Se cierra cuando el colector de desperdicios desecha el objeto conexión.Seguir los cambios de estados de las descargas
Una vez que el código mostrado más arriba se ejecuta, cada vez que hay un cambio de estado se produce una llamada a nuestro método onDownloadStateChange()
. Esto es parte de la interfaz nsIDownloadProgressListener
.
Ese código es parecido a esto:
onDownloadStateChange: function(aState, aDownload) { var statement; switch(aDownload.state) { case Components.interfaces.nsIDownloadManager.DOWNLOAD_DOWNLOADING: // Agregar una fila para la nueva descarga que comienza; cada fila incluye // la URI del origen, tamaño y hora de comienzo. La hora de final y la velocidad de descarga // se ponen ambas a cero ya que no las conocemos aún. // status es el valor del estado llegado desde el administrador de descargas. var dbConn = this.storageService.openDatabase(this.dbFile); statement = dbConn.createStatement("REPLACE INTO items VALUES " + "(?1, ?2, ?3, 0, 0.0, 0)"); statement.bindStringParameter(0, aDownload.source.spec); statement.bindInt64Parameter(1, aDownload.size); statement.bindInt64Parameter(2, aDownload.startTime); statement.execute(); statement.reset(); dbConn.close(); break; // Registra si se ha completado (o ha fallado) la descarga case Components.interfaces.nsIDownloadManager.DOWNLOAD_FINISHED: case Components.interfaces.nsIDownloadManager.DOWNLOAD_FAILED: case Components.interfaces.nsIDownloadManager.DOWNLOAD_CANCELED: this.logTransferCompleted(aDownload); break; } },
Nos interesan cuatro estados. Si el estado de la descarga, indicado por el campo aDownload.state
es Components.interfaces.nsIDownloadManager.DOWNLOAD_DOWNLOADING
, se ha comenzado la descarga del archivo. El objeto aDownload
es un objeto nsIDownload
.
En este caso, creamos una nueva fila en nuestra base de datos para el nuevo archivo, abriendo la base de datos y construyendo una instrucción sqlite: REPLACE INTO
. Las primeras tres filas se comlpetan con el valor de la URI de origen, tamaño del archivo y hora de comienzo del objeto descarga (download). Las demás filas se ponen a cero ya que esa información no la tenemos ahora.
Si el estado de la descarga indica que ésta ha terminado, se ha cancelado o ha fallado, llamamos a nuestra rutina logTransferCompleted
para actualizar el registro indicando el cambio de estado. El código es como:
logTransferCompleted: function(aDownload) { var endTime = new Date(); // La hora actual es la hora de final // Utiliza la instrucción sqlite REPLACE para actualizar el registro. Encontramos un // registro para cada URI y hora de comienzo y actualizamos la hora de final, // tamaño y velocidad en el registro. Haciendo coincidir la URI y la hora de comienzo, // podemos tener varias entradas de múltiples descargas del mismo archivo.
var dbConn = this.storageService.openDatabase(this.dbFile); var statement = dbConn.createStatement("UPDATE items SET size=?1, " + "endTime=?2, speed=?3, status=?4 WHERE source=?5 and startTime=?6"); statement.bindInt64Parameter(0, aDownload.size); statement.bindInt64Parameter(1, endTime.getTime()); statement.bindDoubleParameter(2, aDownload.speed); statement.bindInt32Parameter(3, aDownload.state); statement.bindStringParameter(4, aDownload.source.spec); statement.bindInt64Parameter(5, aDownload.startTime); statement.execute(); statement.reset(); dbConn.close(); },
Esto simplemente abre la base de datos y ejecuta la instrucción sqlite: UPDATE
que encuentra la descarga cuya URI de origen y su hora de inicio coinciden con la descarga que se ha completado y actualiza su información. Buscando por un registro con igual URI y hora de comienzo, implementamos la posibilidad de que un usuario descargue el mismo archivo varias veces.
Mostrando el registro de descargas
El código de la ventana de registro está encapsulado en un objeto llamado downloadlogger_dlwindow
. Ya que este es un ejemplo simple, es una ventana de registro de una única descarga; no nos preocupamos de cambios posteriores del registro. Simplemente mostramos el estado de la descarga en el momento en que la ventana se abre.
Esto significa que todo el trabajo puede hacerse en el manejador del evento de carga, que es algo así:
onLoad: function() { // Abre la base de datos this.dbFile = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties) .get("ProfD", Components.interfaces.nsIFile); this.dbFile.append("downloadlogger.sqlite"); // Consigue acceso al servicio de almacenamiento y abre la base de datos this.storageService = Components.classes["@mozilla.org/storage/service;1"] .getService(Components.interfaces.mozIStorageService); var dbConn = this.storageService.openDatabase(this.dbFile); var loglist = document.getElementById("loglist"); var statement = dbConn.createStatement("SELECT * FROM items"); // Get all items in table try { while (statement.executeStep()) { var row = document.createElement('listitem'); // Agrega las celdas al registro (fila de la tabla) var cell = document.createElement('listcell'); var sourceStr = statement.getString(0); row.setAttribute("tooltiptext", sourceStr); sourceStr = sourceStr.slice(sourceStr.lastIndexOf("/")+1, sourceStr.length); cell.setAttribute("label", sourceStr); // Source row.appendChild(cell); cell = document.createElement('listcell'); cell.setAttribute("label", (statement.getInt64(1) / 1024).toFixed(1) + "KB"); // Size cell.setAttribute("style", "text-align:right"); row.appendChild(cell); var theDate = new Date(statement.getInt64(2) / 1000); // Start time cell = document.createElement('listcell'); var dateStr = theDate.toLocaleString(); cell.setAttribute("label", dateStr); row.appendChild(cell); theDate = new Date(statement.getInt64(3)); // End time cell = document.createElement('listcell'); dateStr = theDate.toLocaleString(); cell.setAttribute("label", dateStr); row.appendChild(cell); var speed = statement.getDouble(4) / 1024.0; cell = document.createElement('listcell'); cell.setAttribute("label", speed.toFixed(1) + "KB/sec"); cell.setAttribute("style", "text-align:right"); row.appendChild(cell); var status = statement.getInt32(5); var style = "color:black"; cell = document.createElement('listcell'); var statusStr; switch(status) { case 0: statusStr = "Descargando"; break; case 1: statusStr = "Completada"; style = "color:green"; break; case 2: statusStr = "Fallida"; style = "color:red"; break; case 3: statusStr = "Cancelada"; style = "color:purple"; break; case 4: statusStr = "Pausada"; style = "color:blue"; break; case 5: statusStr = "En cola"; style = "color:teal"; break; case 6: statusStr = "Bloqueada"; style = "color:white background-color:red"; break; case 7: statusStr = "Escaneando"; style = "color:silver"; break; default: statusStr = "Desconocido"; break; } cell.setAttribute("label", statusStr); cell.setAttribute("style", style); row.appendChild(cell); loglist.appendChild(row); } } finally { statement.reset(); dbConn = null; } }
Este código es bastante simple. Comienza abriendo la base de datos sqlite que contiene la información de registro y entonces crea una sentencia SQL: SELECT
para leer las entradas de la base de datos.
Para reiterar sobre los resultados, usamos un bucle while
que llama al método executeStep()
del objeto mozIStorageStatement
. Cada vez que este método es llamado, se recoge una fila de resultados.
Después de esto, se crea el objeto fila y cada entrada del resultado de la búsqueda, se pone en su respectiva celda.
Los puntos a resaltar son:
mozIStorageStatement
tiene varias rutinas para recoger datos de los resultados de la búsqueda, incluyendogetString()
,getDouble()
, ygetInt64()
. Estos métodos toman como parámetro el indice en base cero de la columna cuyo valor queremos recuperar.
- Notese que la hora de comienzo está dividida por 1000 antes de crear el objeto JavaScript
Date
para ello. Eso es, ajustar el valor almacenado en la base de datos al que espera JavaScript.
- Para justificar a la derecha los valores numéricos de las columnas, ajustamos el atributo
style
de la celda apropiada atext-align:right
.
Ejercicios para el lector
Hay algunas cosas obvias que podrían hacerse para mejorar esta extensión. Si estás aprendiendo a usar el gestor de descargas o el API Storage, hay algunas cosas que convendría que miraras para practicar:
- Añade código para actualizar la ventana del registro de descargas en vivo, en lugar de generar una lista estática cuando se abre por primera vez.
- Añade estadísticas adicionales. ¿Cuál es la velocidad media entre todas las descargas? ¿A qué hora del día obtienes el mayor rendimiento de descargas?
- Añade botones para borrar registros de la lista o para borrar todos los registros de descargas completadas.
- Añade búsquedas.
Ver también
Storage, nsIDownloadManager
, nsIDownload
, nsIDownloadProgressListener