APIs Storage

Quase todos os aspectos do desenvolvimento de apps envolvem algum elemento de envio ou recebimento de dados. Começando pelo básico, você deve usar uma estrutura MVC para ajudar a projetar e implementar seu aplicativo para que os dados sejam completamente separados da visualização do aplicativo nesses dados (consulte Arquitetura MVC).

Você também precisa pensar em como os dados são processados quando seu app está off-line. Consulte Prioridade off-line. Este documento apresenta brevemente as opções de armazenamento para enviar, receber e salvar dados localmente. O restante do documento mostra como usar as APIs File System e Sync File System do Chrome. Consulte também API fileSystem e syncFileSystem (links em inglês).

Opções de armazenamento

Os apps em pacotes usam muitos mecanismos diferentes para enviar e receber dados. Para dados externos (recursos, páginas da Web), você precisa conhecer a Política de Segurança de Conteúdo (CSP). Assim como as Extensões do Chrome, é possível usar XMLHttpRequests de origem cruzada para se comunicar com servidores remotos. Também é possível isolar páginas externas para proteger o restante do app. Consulte Incorporar páginas da Web externas.

Ao salvar dados localmente, você pode usar a API Chrome Storage para salvar pequenas quantidades de dados de string e o IndexedDB para salvar dados estruturados. Com o IndexedDB, você pode manter objetos JavaScript em um armazenamento de objetos e usar os índices dele para consultar dados. Para saber mais, consulte o Tutorial de lista de tarefas simples do HTML5 Rock. Para todos os outros tipos de dados, como binários, use as APIs Filesystem e Sync Filesystem.

As APIs Filesystem e Sync Filesystem do Chrome estendem a API HTML5 FileSystem. Com a API Filesystem do Chrome, os apps podem criar, ler, navegar e gravar em uma seção no sandbox do sistema de arquivos local do usuário. Por exemplo, um app de compartilhamento de fotos pode usar a API Filesystem para ler e gravar qualquer foto selecionada por um usuário.

Com a API Sync Filesystem do Chrome, os aplicativos podem salvar e sincronizar dados no Google Drive de um usuário para que os mesmos dados fiquem disponíveis em diferentes clientes. Por exemplo, um app editor de texto protegido pela nuvem pode sincronizar automaticamente novos arquivos de texto com a conta do Google Drive de um usuário. Quando o usuário abre o editor de texto em um novo cliente, o Google Drive envia novos arquivos de texto para essa instância do editor de texto.

Uso da API Chrome Filesystem

Adicionar permissão do sistema de arquivos

Para usar a API File System do Chrome, você precisa adicionar a permissão "fileSystem" ao manifesto. Assim, é possível receber permissão do usuário para armazenar dados persistentes.

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

Opções do usuário para selecionar arquivos

Os usuários esperam selecionar arquivos da mesma maneira que sempre fazem. No mínimo, eles esperam um botão "escolher arquivo" e um seletor de arquivos padrão. Caso seu app faça uso intenso da manipulação de arquivos, implemente também o recurso de arrastar e soltar. Veja abaixo e confira também o recurso de arrastar e soltar HTML5 nativo.

Como encontrar o caminho de uma fileEntry

Para ver o caminho completo do arquivo que o usuário selecionou, fileEntry, chame getDisplayPath():

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

Como implementar a ação de arrastar e soltar

Se você precisar implementar a seleção de arrastar e soltar, o controlador de arquivos arrastar e soltar (dnd.js) no exemplo filesystem-access é um bom ponto de partida. O controlador cria uma entrada de arquivo de um DataTransferItem usando o recurso de arrastar e soltar. Neste exemplo, a fileEntry está definida como o primeiro item descartado.

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

Como ler um arquivo

O código a seguir abre o arquivo (somente leitura) e o lê como texto usando um objeto FileReader. Se o arquivo não existir, será gerado um erro.

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);
    });
    });
});

Como gravar um arquivo

Os dois casos de uso comuns para gravar um arquivo são "Salvar" e "Salvar como". O código abaixo cria um writableEntry usando o chosenFileEntry somente leitura e grava o arquivo selecionado nele.

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

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

O código a seguir cria um novo arquivo com a funcionalidade "Salvar como" e grava o novo blob no arquivo usando o 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);
});

Usar a API Chrome Sync Filesystem

Com o armazenamento de arquivos sincronizável, os objetos de dados retornados podem ser operados da mesma maneira que os sistemas de arquivos off-line locais na API FileSystem, mas com a sincronização adicionada (e automática) desses dados com o Google Drive.

Adicionando permissão do sistema de arquivos para sincronização

Para usar a API Sync Filesystem do Chrome, você precisa adicionar a permissão "syncFileSystem" ao manifesto para receber permissão do usuário para armazenar e sincronizar dados persistentes.

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

Como iniciar o armazenamento de arquivos sincronizável

Para iniciar o armazenamento de arquivos sincronizáveis no seu aplicativo, basta chamar syncFileSystem.requestFileSystem. Este método retorna um sistema de arquivos sincronizável com suporte do Google Drive, por exemplo:

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

Sobre o status de sincronização de arquivos

Use syncFileSystem.getFileStatus para ver o status de sincronização de um arquivo atual:

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

Os valores de status de sincronização de arquivos podem ser um dos seguintes: 'synced', 'pending' ou 'conflicting'. "Sincronizado" significa que o arquivo está totalmente sincronizado. Não há alterações locais pendentes que não tenham sido sincronizadas com o Google Drive. No entanto, pode haver mudanças pendentes no lado do Google Drive que ainda não foram buscadas.

"Pendente" significa que o arquivo tem alterações pendentes ainda não sincronizadas com o Google Drive. Se o app estiver sendo executado on-line, as alterações locais serão (quase) imediatamente sincronizadas com o Google Drive e o evento syncFileSystem.onFileStatusChanged será acionado com o status 'synced'. Confira mais detalhes abaixo.

O syncFileSystem.onFileStatusChanged é disparado quando o status de um arquivo é alterado para 'conflicting'. "Conflito" significa que há alterações conflitantes no armazenamento local e no Google Drive. Um arquivo só poderá ficar nesse estado se a política de resolução de conflitos estiver definida como 'manual'. A política padrão é 'last_write_win', e os conflitos são resolvidos automaticamente com uma política simples de última gravação. A política de resolução de conflitos do sistema pode ser alterada por syncFileSystem.setConflictResolutionPolicy.

Se a política de resolução de conflitos for definida como 'manual' e um arquivo resultar no estado 'conflicting', o app ainda poderá ler e gravar o arquivo como um arquivo off-line local, mas as mudanças não serão sincronizadas, e o arquivo será mantido separado das mudanças remotas feitas em outros clientes até que o conflito seja resolvido. A maneira mais fácil de resolver um conflito é excluir ou renomear a versão local do arquivo. Isso força a sincronização da versão remota, o estado conflitante é resolvido e o evento onFileStatusChanged é disparado com o status 'synced'.

Como detectar mudanças no status sincronizado

O evento syncFileSystem.onFileStatusChanged é disparado quando o status de sincronização de um arquivo é alterado. Por exemplo, suponha que um arquivo tenha alterações pendentes e esteja no estado "pendente". O app pode estar no estado off-line, de modo que a mudança está prestes a ser sincronizada. Quando o serviço de sincronização detecta a alteração local pendente e faz upload dela para o Google Drive, ele aciona o evento onFileStatusChanged com os seguintes valores: { fileEntry:a fileEntry for the file, status: 'synced', action: 'updated', direction: 'local_to_remote' }.

Da mesma forma, independentemente das atividades locais, o serviço de sincronização pode detectar alterações remotas feitas por outro cliente e fazer o download das alterações do Google Drive para o armazenamento local. Se a mudança remota for adicionar um novo arquivo, um evento com os seguintes valores será disparado: { fileEntry: a fileEntry for the file, status: 'synced', action: 'added', direction: 'remote_to_local' }.

Se os lados local e remoto tiverem alterações conflitantes para o mesmo arquivo e se a política de resolução de conflitos estiver definida como 'manual', o status do arquivo será alterado para o estado conflicting, separado do serviço de sincronização e não será sincronizado até que o conflito seja resolvido. Nesse caso, um evento com os seguintes valores é acionado: { fileEntry: a fileEntry for the file, status: 'conflicting', action: null, direction: null }.

Você pode adicionar um listener a esse evento que responda a qualquer alteração de status. Por exemplo, o app Chrome Music Player detecta novas músicas sincronizadas no Google Drive, mas que ainda não foram importadas para o armazenamento local do usuário em um cliente específico. Todas as músicas encontradas são sincronizadas com esse 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);
      }
    }
  }
});

Como verificar o uso da API

Para verificar a quantidade de dados em uso pela API, consulte o diretório no modo sandbox local do app ou os bytes de uso retornados por syncFileSystem.getUsageAndQuota:

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

Também é possível verificar o armazenamento do serviço de back-end de sincronização do usuário (no Google Drive). Os arquivos sincronizados são salvos em uma pasta oculta do Google Drive, a Chrome Syncable FileSystem. A pasta não será exibida na lista "Meu Drive", mas poderá ser acessada pesquisando o nome dela na caixa de pesquisa. Observe que não há garantia de que o layout da pasta remota vai permanecer compatível com versões anteriores entre as versões.