API de Storage

Casi todos los aspectos del desarrollo de apps implican algún elemento de envío o recepción de datos. Comenzando por los conceptos básicos, debes usar un framework de MVC para ayudarte a diseñar e implementar tu app, de modo que los datos sean completamente independientes de su vista sobre esos datos (consulta la arquitectura de MVC).

También debes pensar en cómo se administran los datos cuando tu app está sin conexión (consulta Primero el uso sin conexión). En este documento, se presentan brevemente las opciones de almacenamiento para enviar, recibir y guardar datos de forma local. En el resto del documento, se muestra cómo usar las APIs del sistema de archivos y del sistema de archivos de sincronización de Chrome (consulta también la API de FileSystem y la API de syncFileSystem).

Opciones de almacenamiento

Las apps empaquetadas usan muchos mecanismos diferentes para enviar y recibir datos. Para los datos externos (recursos, páginas web), debes conocer la Política de Seguridad del Contenido (CSP). Al igual que con las extensiones de Chrome, puedes usar XMLHttpRequests de origen cruzado para comunicarte con servidores remotos. También puedes aislar páginas externas para que el resto de tu app esté seguro (consulta Cómo incorporar páginas web externas).

Cuando guardas datos localmente, puedes usar la API de Chrome Storage para guardar pequeñas cantidades de datos de cadena y IndexedDB para guardar datos estructurados. Con IndexedDB, puedes conservar objetos JavaScript en un almacén de objetos y usar los índices del almacén para consultar datos (si quieres obtener más información, consulta el Instructivo simple de listas de tareas pendientes de HTML5 Rock). Para todos los demás tipos de datos, como los binarios, usa las APIs de Filesystem y Sync Filesystem.

Las APIs de Filesystem y Sync Filesystem de Chrome extienden la API de HTML5 FileSystem. Con la API de Filesystem de Chrome, las apps pueden crear, leer, navegar y escribir en una sección de zona de pruebas del sistema de archivos local del usuario. Por ejemplo, una app para compartir fotos puede usar la API del sistema de archivos a fin de leer y escribir cualquier foto que seleccione un usuario.

Con la API del sistema de archivos de sincronización de Chrome, las apps pueden guardar y sincronizar datos en la unidad de Google Drive de un usuario para que los mismos datos estén disponibles en diferentes clientes. Por ejemplo, una app de editor de texto respaldada por la nube puede sincronizar automáticamente archivos de texto nuevos con la cuenta de Google Drive de un usuario. Cuando el usuario abre el editor de texto en un cliente nuevo, Google Drive envía archivos de texto nuevos a esa instancia del editor de texto.

Cómo usar la API del sistema de archivos de Chrome

Agrega permisos para el sistema de archivos

Para usar la API de File System de Chrome, debes agregar el permiso "fileSystem" al manifiesto, de modo que puedas obtener el permiso del usuario para almacenar datos persistentes.

"permissions": [
  "...",
  "fileSystem"
]

Opciones del usuario para seleccionar archivos

Los usuarios esperan seleccionar los archivos de la misma manera que siempre lo hacen. Como mínimo, esperan un botón “elegir archivo” y un selector de archivos estándar. Si tu app hace un uso intensivo de la administración de archivos, también debes implementar la función de arrastrar y soltar (consulta a continuación y también consulta Arrastrar y soltar HTML5 nativo).

Cómo obtener la ruta de acceso de un fileEntry

Para obtener la ruta de acceso completa del archivo que seleccionó el usuario, fileEntry, llama a getDisplayPath():

function displayPath(fileEntry) {
  chrome.fileSystem.getDisplayPath(fileEntry, function(path) {
    console.log(path)
  });
}

Cómo implementar la función de arrastrar y soltar

Si necesitas implementar la selección de arrastrar y soltar, el controlador de archivos de arrastrar y soltar (dnd.js) en la muestra filesystem-access es un buen punto de partida. El controlador crea una entrada de archivo a partir de un objeto DataTransferItem mediante la función de arrastrar y soltar. En este ejemplo, fileEntry se establece en el primer elemento descartado.

var dnd = new DnDFileController('body', function(data) {
  var fileEntry = data.items[0].webkitGetAsEntry();
  displayPath(fileEntry);
});

Lee un archivo

El siguiente código abre el archivo (solo lectura) y lo lee como texto mediante un objeto FileReader. Si el archivo no existe, se mostrará un error.

var chosenFileEntry = null;

chooseFileButton.addEventListener('click', function(e) {
  chrome.fileSystem.chooseEntry({type: 'openFile'}, function(readOnlyEntry) {

    readOnlyEntry.file(function(file) {
      var reader = new FileReader();

      reader.onerror = errorHandler;
      reader.onloadend = function(e) {
        console.log(e.target.result);
      };

      reader.readAsText(file);
    });
    });
});

Escribe un archivo

Los dos casos de uso comunes para escribir un archivo son "Guardar" y "Guardar como". El siguiente código crea un writableEntry a partir del chosenFileEntry de solo lectura y escribe en él el archivo seleccionado.

 chrome.fileSystem.getWritableEntry(chosenFileEntry, function(writableFileEntry) {
    writableFileEntry.createWriter(function(writer) {
      writer.onerror = errorHandler;
      writer.onwriteend = callback;

    chosenFileEntry.file(function(file) {
      writer.write(file);
    });
  }, errorHandler);
});

Con el siguiente código, se crea un archivo nuevo con la funcionalidad “Guardar como” y se escribe el BLOB nuevo en el archivo con el método writer.write().

chrome.fileSystem.chooseEntry({type: 'saveFile'}, function(writableFileEntry) {
    writableFileEntry.createWriter(function(writer) {
      writer.onerror = errorHandler;
      writer.onwriteend = function(e) {
        console.log('write complete');
      };
      writer.write(new Blob(['1234567890'], {type: 'text/plain'}));
    }, errorHandler);
});

Cómo usar la API del sistema de archivos de sincronización de Chrome

Con el almacenamiento de archivos sincronizable, los objetos de datos que se muestran se pueden operar de la misma manera que los sistemas de archivos sin conexión locales en la API de FileSystem, pero con la sincronización agregada (y automática) de esos datos en Google Drive.

Agregando permiso de sincronización del sistema de archivos

Para usar la API de Sync Filesystem de Chrome, debes agregar el permiso "syncFileSystem" al manifiesto, de modo que puedas obtener permiso del usuario para almacenar y sincronizar datos persistentes.

"permissions": [
  "...",
  "syncFileSystem"
]

Cómo iniciar un almacenamiento de archivos sincronizable

Para iniciar el almacenamiento de archivos sincronizables en tu app, simplemente llama a syncFileSystem.requestFileSystem. Este método muestra un sistema de archivos sincronizable respaldado por Google Drive, por ejemplo:

chrome.syncFileSystem.requestFileSystem(function (fs) {
   // FileSystem API should just work on the returned 'fs'.
   fs.root.getFile('test.txt', {create:true}, getEntryCallback, errorCallback);
});

Información sobre el estado de sincronización de archivos

Usa syncFileSystem.getFileStatus para obtener el estado de sincronización de un archivo actual:

chrome.syncFileSystem.getFileStatus(entry, function(status) {...});

Los valores de estado de sincronización de archivos pueden ser uno de los siguientes: 'synced', 'pending' o 'conflicting'. “Sincronizado” significa que el archivo está completamente sincronizado. No hay cambios locales pendientes que no se hayan sincronizado con Google Drive. Sin embargo, puede haber cambios pendientes en Google Drive que aún no se hayan recuperado.

"Pendiente" significa que el archivo tiene cambios pendientes que aún no se sincronizaron con Google Drive. Si la app se ejecuta en línea, los cambios locales se sincronizan (casi) de inmediato con Google Drive, y el evento syncFileSystem.onFileStatusChanged se activa con el estado 'synced' (consulta a continuación para obtener más detalles).

syncFileSystem.onFileStatusChanged se activa cuando el estado de un archivo cambia a 'conflicting'. “En conflicto” significa que hay cambios conflictivos tanto en el almacenamiento local como en Google Drive. Un archivo puede estar en este estado solo si la política de resolución de conflictos se establece en 'manual'. La política predeterminada es 'last_write_win' y los conflictos se resuelven de forma automática con una política simple de último tipo de ganar. La política de resolución de conflictos del sistema se puede cambiar con syncFileSystem.setConflictResolutionPolicy.

Si la política de resolución de conflictos se establece en 'manual' y un archivo da como resultado el estado 'conflicting', la app aún podrá leer y escribir el archivo como un archivo local sin conexión, pero los cambios no se sincronizarán y el archivo se mantendrá separado de los cambios remotos realizados en otros clientes hasta que se resuelva el conflicto. La manera más sencilla de resolver un conflicto es borrar la versión local del archivo o cambiarle el nombre. Esto fuerza la sincronización de la versión remota, el estado en conflicto se resuelve y el evento onFileStatusChanged se activa con el estado 'synced'.

Escuchando cambios en el estado de sincronización

El evento syncFileSystem.onFileStatusChanged se activa cuando cambia el estado de sincronización de un archivo. Por ejemplo, supongamos que un archivo tiene cambios pendientes y está en estado “pendiente”. Es posible que la app haya estado sin conexión para que el cambio esté a punto de sincronizarse. Cuando el servicio de sincronización detecta el cambio local pendiente y lo sube a Google Drive, el servicio activa el evento onFileStatusChanged con los siguientes valores: { fileEntry:a fileEntry for the file, status: 'synced', action: 'updated', direction: 'local_to_remote' }.

Del mismo modo, independientemente de las actividades locales, el servicio de sincronización puede detectar cambios remotos realizados por otro cliente y descargar los cambios de Google Drive al almacenamiento local. Si el cambio remoto fue para agregar un archivo nuevo, se activa un evento con los siguientes valores: { fileEntry: a fileEntry for the file, status: 'synced', action: 'added', direction: 'remote_to_local' }.

Si tanto el lado local como el remoto tienen cambios conflictivos para el mismo archivo y si la política de resolución de conflictos se establece en 'manual', el estado del archivo cambia a conflicting, se desconecta del servicio de sincronización y no se sincronizará hasta que se resuelva el conflicto. En este caso, se activa un evento con los siguientes valores: { fileEntry: a fileEntry for the file, status: 'conflicting', action: null, direction: null }.

Puedes agregar un objeto de escucha para este evento que responda a cualquier cambio en el estado. Por ejemplo, la app del Reproductor de música de Chrome detecta cualquier música nueva sincronizada desde Google Drive, pero aún no se importa al almacenamiento local del usuario en un cliente en particular. Toda la música que se encuentra se sincroniza con ese cliente:

chrome.syncFileSystem.onFileStatusChanged.addListener(function(fileInfo) {
  if (fileInfo.status === 'synced') {
    if (fileInfo.direction === 'remote_to_local') {
      if (fileInfo.action === 'added') {
        db.add(fileInfo.fileEntry);
      } else if (fileInfo.action === 'deleted') {
        db.remove(fileInfo.fileEntry);
      }
    }
  }
});

Comprobando el uso de la API

Para verificar la cantidad de datos que utiliza la API, consulta el directorio de la zona de pruebas local de la app o los bytes de uso que muestra syncFileSystem.getUsageAndQuota:

chrome.syncFileSystem.getUsageAndQuota(fileSystem, function (storageInfo) {
   updateUsageInfo(storageInfo.usageBytes);
   updateQuotaInfo(storageInfo.quotaBytes);
});

También puedes ver el almacenamiento del servicio de backend de sincronización del usuario (en Google Drive). Los archivos sincronizados se guardan en una carpeta oculta de Google Drive, Sistema de archivos sincronizables de Chrome. La carpeta no se mostrará en tu lista “Mi unidad”, pero puedes acceder a ella buscando su nombre en el cuadro de búsqueda. (Ten en cuenta que no se garantiza que el diseño de la carpeta remota siga siendo retrocompatible entre las versiones).