存储 API

几乎应用开发的每一方面都会涉及到一些数据的发送与接收。从最基本的开始,您应该使用 MVC 框架来帮助您设计和实现您的应用,以便使数据完全与应用的数据视图分离(参见 MVC 架构)。

您还需要考虑当您的应用离线时(参见首先考虑离线)如何处理数据。该文档简要地介绍了用于在本地发送、接收和保存数据的存储选择,文档的剩下部分向您演示了如何使用 Chrome 浏览器的文件系统 API 和同步文件系统 API(请参见文件系统 API同步文件系统 API)。

API 示例: 想试试这些代码吗?请参见 filesystem-accesssyncfs-editor storage 示例。

存储的选择

Chrome 应用可以使用很多不同的机制发送和接收数据。对于外部数据(资源、网页),您需要意识到内容安全策略(CSP)的存在。与 Chrome 扩展程序类似,您可以使用跨站 XMLHttpRequest 与远程服务器通信。您还可以隔离外部页面,以确保您的应用的其他部分是安全的(参见嵌入外部网页)。

需要在本地保存数据时,您可以使用 Chrome 浏览器的存储 API 来保存少量字符串数据,并使用 IndexedDB 保存结构化的数据。通过 IndexedDB,您可以将 JavaScript 对象持久存储在对象存储区中并使用存储区的索引查询数据(要了解更多内容,请参见 HTML5 Rocks 的待办事项简要列表教程)。对于所有其他类型的数据,例如二进制数据,请使用文件系统 API 和同步文件系统 API。

Chrome 浏览器的文件系统 API 和同步文件系统 API 扩展了 HTML5 文件系统 API。使用 Chrome 浏览器的文件系统 API,应用程序可以创建、读取、导航及写入用户本地文件系统中经过沙盒屏蔽的一个区域。通过 Chrome 浏览器的文件系统 API,Chrome 应用可以读取或写入用户选择位置的文件。例如,照片分享应用可以使用文件系统 API 读取和写入用户选择的任何照片。

使用 Chrome 浏览器的同步文件系统 API,应用可以在用户的 Google 云端硬盘上保存和同步数据,这样相同的数据可以在不同的客户端之间使用。例如,一个云端支持的文本编辑器应用可以自动将新的文本文件同步至用户的 Google 云端硬盘账户。当用户在新的客户端打开文本编辑器时,Google 云端硬盘会将新的文本文件推送至该文本编辑器的实例。

注意:与普通的文件系统 API 不同,Chrome 浏览器的同步文件系统 API 当前还不支持目录操作,除了在根目录中读取目录项。尝试在同步文件系统中创建目录会导致 INVALID_MODIFICATION_ERROR 错误。

使用 Chrome 浏览器的文件系统 API

添加文件系统权限

要使用 Chrome 浏览器的文件系统 API,您需要在清单文件中添加 "fileSystem" 权限,使您可以从用户获取持久储存数据的权限。

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

选择文件的用户选项

用户期望以习惯的方式选择文件,至少他们期望一个“选择文件”按钮以及标准的文件选择器。如果您的应用大量使用文件选择,您还应该实现拖放支持(参见如下内容以及本机 HTML5 拖放)。

获取文件项(fileEntry)的路径

要从文件项(fileEntry)获取用户选定文件的全路径,请调用 getDisplayPath()

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

实现拖放支持

如果您需要实现拖放选择,filesystem-access 示例中的拖放文件控制器(dnd.js)是一个很好的起点。通过拖放控制器从 DataTransferItem 创建一个文件项。在这一例子中,fileEntry 设置为拖放的第一个项目。

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

读取文件

如下代码打开文件(只读)并使用 FileReader 对象以文本形式读取。如果文件不存在,则会引发错误。

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

写入文件

用于写入文件的两种常见使用情况为“保存”与“另存为”。如下代码从只读的 chosenFileEntry 创建一个 writableEntry,并将选定文件写入它。

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

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

如下代码具有“另存为”功能,创建一个新文件并使用 writer.write() 方法将新的 Blob(二进制大型物件)写入文件。

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

使用 Chrome 浏览器的同步文件系统 API

使用同步文件存储,返回的数据对象可以和文件系统 API 中的本地离线文件系统一样使用,但是增加了(自动)将数据同步到 Google 云端硬盘的功能。

添加同步文件系统权限

要使用 Chrome 浏览器的同步文件系统 API,您需要在清单文件中添加 "syncFileSystem" 权限,这样您才能从用户那里获取存储和同步持久数据的权限。

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

请求同步文件存储

要在您的应用中请求同步文件存储,只要调用 syncFileSystem.requestFileSystem。该方法返回一个由 Google 云端硬盘支持的同步文件系统。例如:

chrome.syncFileSystem.requestFileSystem(function (fs) {
   // 文件系统 API 应该可以直接在返回的“fs”上使用。
   fs.root.getFile('test.txt', {create:true}, getEntryCallback, errorCallback);
});

关于文件同步状态

使用 syncFileSystem.getFileStatus 获取当前文件的同步状态:

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

文件同步状态可以为如下值之一:'synced'(已同步)、'pending'(待定)或 'conflicting'

'synced' 意味着文件已经完全同步,没有还未同步到 Google 云端硬盘的待定本地更改,然而在 Google 云端硬盘上可能有还未获取的待定更改。

'pending' 意味着文件有待定的更改,还未同步到 Google 云端硬盘。如果应用以在线模式运行,本地更改(几乎)会立即同步到 Google 云端硬盘,并将产生 syncFileSystem.onFileStatusChanged 事件,传递 'synced' 状态(更多详情请往下阅读)。

当文件状态更改为 'conflicting' 时也会产生 syncFileSystem.onFileStatusChanged 事件。'conflicting' 意味着本地存储和 Google 云端硬盘之间的更改有冲突。只有当冲突解决策略设置为 'manual'(手动)时文件才有可能处于这种状态,默认策略是 'last_write_win'(最后写入优先),冲突会按照最后写入优先的策略自动解决。系统的冲突解决策略可以通过 syncFileSystem.setConflictResolutionPolicy 修改。

如果冲突解决策略设置为 'manual'(手动),并且文件处于 'conflicting'(冲突)状态,应用仍然可以继续读取和写入该文件,将它作为一个本地离线文件,但是更改不会被同步,并且该文件将保持脱离状态,不受其他客户端所作的远程更改的影响,直到冲突解决。解决冲突最简单的办法就是删除或重命名本地版本的文件,这样会强制同步远程版本,解决冲突状态,并产生 onFileStatusChanged 事件,传递 'synced' 状态。

监听同步状态的更改

当文件的同步状态更改时,例如假设文件有待定的更改并处于 'pending' 状态,会产生 syncFileSystem.onFileStatusChanged 事件。应用可能已经处于离线状态,所以更改还未被同步。当同步服务检测到本地更改并将更改上传到 Google 云端硬盘时,该服务会产生 onFileStatusChanged 事件,并传递下列值: { fileEntry: 文件的文件项, status: 'synced', action: 'updated', direction: 'local_to_remote' }.

类似地,无论本地有什么活动,同步服务可能会检测到另一个客户端作出的远程更改,并将更改从 Google 云端硬盘下载到本地存储中。如果远程更改是添加了一个新文件,将产生一个事件,传递下列值:{ fileEntry: 文件的文件项, status: 'synced', action: 'added', direction: 'remote_to_local' }.

如果本地和远程两端都有对同一文件冲突的更改,而且冲突解决策略设置为 'manual'(手动),文件状态就会变成 conflicting(冲突)状态,并从同步服务脱离,只有当冲突解决后才会同步。在这种情况下,将会产生一个事件,传递下列值:{ fileEntry: 文件的文件项, status: 'conflicting', action: null, direction: null }.

您可以为该事件添加一个监听器,响应状态的更改。例如,Chrome 音乐播放器应用监听从 Google 云端硬盘同步,但是还未导入至某个客户端上用户本地存储的新音乐,找到的音乐会同步到这一客户端。

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

检查 API 用量

要检查该 API 使用的数据量,查询应用在本地经过沙盒屏蔽的目录,或 syncFileSystem.getUsageAndQuota 返回的占用字节数:

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

您也可以查看用户的同步后端存储(在 Google 云端硬盘中),同步的文件保存在一个隐藏的 Google 云端硬盘文件夹中:Chrome Syncable FileSystem。文件夹不会在您的“我的云端硬盘”列表中显示,但是可以通过在搜索框中搜索文件夹名称来访问。(注意,远程文件夹的布局不保证在不同版本间向后兼容。)