高级架构

BackupService 模块的架构设计使得通过单元测试相对容易地测试其各个组件。

主要入口点位于名为 BackupService.sys.mjs 的模块中,该模块通过 BrowserGlue 空闲任务列表中的调用并初始化

BackupService 管理两个高级操作:创建备份和从这些备份中恢复。

BackupService 图

这是一个非详尽的架构图,试图说明围绕 BackupService 的各个高级组件之间的关系。

../../../../_images/architecture.svg

围绕 BackupService 的这组组件是相当自包含的,并且在 browser/components/backup 之外,很少有地方使用 BackupService 或任何其他这些组件。例外情况是 BrowserGlue 用于启动初始化,以及嵌入小部件以控制 BackupService 的首选项 UI。

创建备份

BackupService 初始化时会收集一些遥测数据,并在之后设置计划机制,如果用户已配置,则生成备份。

创建备份的过程分为三个阶段

  1. 将资源复制到临时暂存文件夹

  2. 创建存档文件

  3. 清理

将资源复制到临时暂存文件夹

BackupServiceProfD/backups/staging 中创建(或覆盖如果预先存在)一个文件夹。对于每个 BackupResource,将在该 staging 文件夹下创建一个文件夹。然后将该文件夹的路径传递给每个 BackupResource,然后 BackupResource 将负责安全地将 BackupResource 代表的数据存储复制到新文件夹中。BackupResource 也可以在写入这些副本之前对其进行预处理。

注意:如果用户已配置备份进行加密,则某些 BackupResource 子类仅调用 backup。这是因为我们希望尽最大努力保护可能通过不受信任的渠道发送的备份文件——例如,通过网络上传到 Dropbox 或 Google Drive,甚至通过电子邮件发送。只有在启用加密时,才会将最敏感的数据(密码、Cookie 和付款方式)包含在备份中。

对于以原子方式写入的 JSON 或二进制文件,每个 BackupResource 都使用简单的文件复制操作。对于 SQLite 数据库,使用 SQLite 在线备份 API 以确保创建完整的工作副本。此 API 通过 Sqlite.sys.mjs 提供。

staging 文件夹中还包含一个 backup-manifest.json 文件,描述存储的资源以及有关创建的备份的其他元数据。

创建单文件存档

一旦所有 BackupResource 都有机会创建其副本,staging 文件夹的内容就会被压缩并打包成一个单文件存档。如果用户已配置备份以包含敏感数据,则此存档可能会被加密。

清理

创建单文件存档后,它将移动到用户配置的位置,覆盖特定配置文件的任何预先存在的备份文件。

完成此操作后,将删除 backups/staging 文件夹。

从备份恢复

从备份恢复的过程分为三个阶段

  1. 从单文件存档中提取和解压缩

  2. 恢复到新创建的配置文件文件夹

  3. 恢复后操作

提取

第一步是从单文件存档中提取(如果已加密,则解密)备份的数据。此步骤的最终结果是在 ProfD/backups/recovery 下创建一个新文件夹,并将备份内容解压缩到其中。recovery 文件夹是创建备份时 staging 文件夹的精确镜像。

恢复到新创建的配置文件文件夹

BackupService 将创建一个新的用户配置文件,然后对于 backup-manifest.json 中列出的每个资源,将实例化关联的 BackupResource 并将其传递到与 resource 关联的 recovery 下的子文件夹的路径。BackupResource 然后负责将数据存储从该文件夹复制到新创建的配置文件文件夹中。

每个 BackupResource 在恢复过程中可能会发出一些信息,这些信息会被写入到 post-recovery.json 文件中,该文件也会写入到配置文件文件夹中。

完成后,将启动新创建的配置文件并关闭当前配置文件。

恢复后

BackupService 初始化还会检查启动的配置文件是否刚刚从备份中恢复,如果是,则可能会发生恢复后操作——例如,只能在恢复的配置文件运行时发生的数据存储更新。

这是通过检查当前配置文件目录中的 post-recovery.json 文件来完成的。如果找到此文件,则将实例化每个 BackupResource 并传递在恢复期间发出并存储在 JSON 文件中的恢复后数据。

当某些操作只能在恢复的配置文件运行时执行时,这很有用。例如,我们只能在附加到这些数据库的应用程序中将数据插入 IndexedDB 数据库。