summaryrefslogtreecommitdiffstats
path: root/browser/components/backup/resources/BackupResource.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/backup/resources/BackupResource.sys.mjs')
-rw-r--r--browser/components/backup/resources/BackupResource.sys.mjs170
1 files changed, 164 insertions, 6 deletions
diff --git a/browser/components/backup/resources/BackupResource.sys.mjs b/browser/components/backup/resources/BackupResource.sys.mjs
index d851eb5199..5be6314a60 100644
--- a/browser/components/backup/resources/BackupResource.sys.mjs
+++ b/browser/components/backup/resources/BackupResource.sys.mjs
@@ -2,6 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
+});
+
// Convert from bytes to kilobytes (not kibibytes).
export const BYTES_IN_KB = 1000;
@@ -50,6 +58,19 @@ export class BackupResource {
}
/**
+ * This can be overridden to return a number indicating the priority the
+ * resource should have in the backup order.
+ *
+ * Resources with a higher priority will be backed up first.
+ * The default priority of 0 indicates it can be processed in any order.
+ *
+ * @returns {number}
+ */
+ static get priority() {
+ return 0;
+ }
+
+ /**
* Get the size of a file.
*
* @param {string} filePath - path to a file.
@@ -129,6 +150,75 @@ export class BackupResource {
return size;
}
+ /**
+ * Copy a set of SQLite databases safely from a source directory to a
+ * destination directory. A new read-only connection is opened for each
+ * database, and then a backup is created. If the source database does not
+ * exist, it is ignored.
+ *
+ * @param {string} sourcePath
+ * Path to the source directory of the SQLite databases.
+ * @param {string} destPath
+ * Path to the destination directory where the SQLite databases should be
+ * copied to.
+ * @param {Array<string>} sqliteDatabases
+ * An array of filenames of the SQLite databases to copy.
+ * @returns {Promise<undefined>}
+ */
+ static async copySqliteDatabases(sourcePath, destPath, sqliteDatabases) {
+ for (let fileName of sqliteDatabases) {
+ let sourceFilePath = PathUtils.join(sourcePath, fileName);
+
+ if (!(await IOUtils.exists(sourceFilePath))) {
+ continue;
+ }
+
+ let destFilePath = PathUtils.join(destPath, fileName);
+ let connection;
+
+ try {
+ connection = await lazy.Sqlite.openConnection({
+ path: sourceFilePath,
+ readOnly: true,
+ });
+
+ await connection.backup(
+ destFilePath,
+ BackupResource.SQLITE_PAGES_PER_STEP,
+ BackupResource.SQLITE_STEP_DELAY_MS
+ );
+ } finally {
+ await connection?.close();
+ }
+ }
+ }
+
+ /**
+ * A helper function to copy a set of files from a source directory to a
+ * destination directory. Callers should ensure that the source files can be
+ * copied safely before invoking this function. Files that do not exist will
+ * be ignored. Callers that wish to copy SQLite databases should use
+ * copySqliteDatabases() instead.
+ *
+ * @param {string} sourcePath
+ * Path to the source directory of the files to be copied.
+ * @param {string} destPath
+ * Path to the destination directory where the files should be
+ * copied to.
+ * @param {string[]} fileNames
+ * An array of filenames of the files to copy.
+ * @returns {Promise<undefined>}
+ */
+ static async copyFiles(sourcePath, destPath, fileNames) {
+ for (let fileName of fileNames) {
+ let sourceFilePath = PathUtils.join(sourcePath, fileName);
+ let destFilePath = PathUtils.join(destPath, fileName);
+ if (await IOUtils.exists(sourceFilePath)) {
+ await IOUtils.copy(sourceFilePath, destFilePath, { recursive: true });
+ }
+ }
+ }
+
constructor() {}
/**
@@ -144,12 +234,12 @@ export class BackupResource {
}
/**
- * Perform a safe copy of the resource(s) and write them into the backup
- * database. The Promise should resolve with an object that can be serialized
- * to JSON, as it will be written to the manifest file. This same object will
- * be deserialized and passed to restore() when restoring the backup. This
- * object can be null if no additional information is needed to restore the
- * backup.
+ * Perform a safe copy of the datastores that this resource manages and write
+ * them into the backup database. The Promise should resolve with an object
+ * that can be serialized to JSON, as it will be written to the manifest file.
+ * This same object will be deserialized and passed to restore() when
+ * restoring the backup. This object can be null if no additional information
+ * is needed to restore the backup.
*
* @param {string} stagingPath
* The path to the staging folder where copies of the datastores for this
@@ -167,4 +257,72 @@ export class BackupResource {
async backup(stagingPath, profilePath = null) {
throw new Error("BackupResource::backup must be overridden");
}
+
+ /**
+ * Recovers the datastores that this resource manages from a backup archive
+ * that has been decompressed into the recoveryPath. A pre-existing unlocked
+ * user profile should be available to restore into, and destProfilePath
+ * should point at its location on the file system.
+ *
+ * This method is not expected to be running in an app connected to the
+ * destProfilePath. If the BackupResource needs to run some operations
+ * while attached to the recovery profile, it should do that work inside of
+ * postRecovery(). If data needs to be transferred to postRecovery(), it
+ * should be passed as a JSON serializable object in the return value of this
+ * method.
+ *
+ * @see BackupResource.postRecovery()
+ * @param {object|null} manifestEntry
+ * The object that was returned by the backup() method when the backup was
+ * created. This object can be null if no additional information was needed
+ * for recovery.
+ * @param {string} recoveryPath
+ * The path to the resource directory where the backup archive has been
+ * decompressed.
+ * @param {string} destProfilePath
+ * The path to the profile directory where the backup should be restored to.
+ * @returns {Promise<object|null>}
+ * This should return a JSON serializable object that will be passed to
+ * postRecovery() if any data needs to be passed to it. This object can be
+ * null if no additional information is needed for postRecovery().
+ */
+ // eslint-disable-next-line no-unused-vars
+ async recover(manifestEntry, recoveryPath, destProfilePath) {
+ throw new Error("BackupResource::recover must be overridden");
+ }
+
+ /**
+ * Perform any post-recovery operations that need to be done after the
+ * recovery has been completed and the recovered profile has been attached
+ * to.
+ *
+ * This method is running in an app connected to the recovered profile. The
+ * profile is locked, but this postRecovery method can be used to insert
+ * data into connected datastores, or perform any other operations that can
+ * only occur within the context of the recovered profile.
+ *
+ * @see BackupResource.recover()
+ * @param {object|null} postRecoveryEntry
+ * The object that was returned by the recover() method when the recovery
+ * was originally done. This object can be null if no additional information
+ * is needed for post-recovery.
+ */
+ // eslint-disable-next-line no-unused-vars
+ async postRecovery(postRecoveryEntry) {
+ // no-op by default
+ }
}
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ BackupResource,
+ "SQLITE_PAGES_PER_STEP",
+ "browser.backup.sqlite.pages_per_step",
+ 5
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ BackupResource,
+ "SQLITE_STEP_DELAY_MS",
+ "browser.backup.sqlite.step_delay_ms",
+ 250
+);