Bug 1910953 - Use a Web Lock to sequence deletions and creations of backups. r=backup-reviewers,fchasen
This allows us to avoid creating a backup while we're in the middle of deleting one, and deleting a backup when we're in the middle of creating one. An AbortController is used to clear the lock's request queue on shutdown in the (unlikely) event that a whole slew of backup creation and deletion requests have queued up. Differential Revision: https://phabricator.services.mozilla.com/D218240
This commit is contained in:
@@ -648,6 +648,14 @@ export class BackupService extends EventTarget {
|
|||||||
*/
|
*/
|
||||||
#encState = undefined;
|
#encState = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AbortController used to abort any queued requests to create or delete
|
||||||
|
* backups that might be waiting on the WRITE_BACKUP_LOCK_NAME lock.
|
||||||
|
*
|
||||||
|
* @type {AbortController}
|
||||||
|
*/
|
||||||
|
#backupWriteAbortController = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path of the default parent directory for saving backups.
|
* The path of the default parent directory for saving backups.
|
||||||
* The current default is the Documents directory.
|
* The current default is the Documents directory.
|
||||||
@@ -840,6 +848,16 @@ export class BackupService extends EventTarget {
|
|||||||
return AppConstants.MOZ_APP_BASENAME + " Backup Recovery Storage";
|
return AppConstants.MOZ_APP_BASENAME + " Backup Recovery Storage";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the exclusive Web Lock that will be requested and held when
|
||||||
|
* creating or deleting a backup.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static get WRITE_BACKUP_LOCK_NAME() {
|
||||||
|
return "write-backup";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference to a BackupService singleton. If this is the first time
|
* Returns a reference to a BackupService singleton. If this is the first time
|
||||||
* that this getter is accessed, this causes the BackupService singleton to be
|
* that this getter is accessed, this causes the BackupService singleton to be
|
||||||
@@ -897,6 +915,7 @@ export class BackupService extends EventTarget {
|
|||||||
let { promise, resolve } = Promise.withResolvers();
|
let { promise, resolve } = Promise.withResolvers();
|
||||||
this.#postRecoveryPromise = promise;
|
this.#postRecoveryPromise = promise;
|
||||||
this.#postRecoveryResolver = resolve;
|
this.#postRecoveryResolver = resolve;
|
||||||
|
this.#backupWriteAbortController = new AbortController();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1069,205 +1088,221 @@ export class BackupService extends EventTarget {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#backupInProgress = true;
|
return locks.request(
|
||||||
const backupTimer = Glean.browserBackup.totalBackupTime.start();
|
BackupService.WRITE_BACKUP_LOCK_NAME,
|
||||||
|
{ signal: this.#backupWriteAbortController.signal },
|
||||||
|
async () => {
|
||||||
|
this.#backupInProgress = true;
|
||||||
|
const backupTimer = Glean.browserBackup.totalBackupTime.start();
|
||||||
|
|
||||||
try {
|
|
||||||
lazy.logConsole.debug(`Creating backup for profile at ${profilePath}`);
|
|
||||||
|
|
||||||
let archiveDestFolderPath = await this.resolveArchiveDestFolderPath(
|
|
||||||
lazy.backupDirPref
|
|
||||||
);
|
|
||||||
lazy.logConsole.debug(
|
|
||||||
`Destination for archive: ${archiveDestFolderPath}`
|
|
||||||
);
|
|
||||||
|
|
||||||
let manifest = await this.#createBackupManifest();
|
|
||||||
|
|
||||||
// First, check to see if a `backups` directory already exists in the
|
|
||||||
// profile.
|
|
||||||
let backupDirPath = PathUtils.join(
|
|
||||||
profilePath,
|
|
||||||
BackupService.PROFILE_FOLDER_NAME,
|
|
||||||
BackupService.SNAPSHOTS_FOLDER_NAME
|
|
||||||
);
|
|
||||||
lazy.logConsole.debug("Creating backups folder");
|
|
||||||
|
|
||||||
// ignoreExisting: true is the default, but we're being explicit that it's
|
|
||||||
// okay if this folder already exists.
|
|
||||||
await IOUtils.makeDirectory(backupDirPath, {
|
|
||||||
ignoreExisting: true,
|
|
||||||
createAncestors: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
let stagingPath = await this.#prepareStagingFolder(backupDirPath);
|
|
||||||
|
|
||||||
// Sort resources be priority.
|
|
||||||
let sortedResources = Array.from(this.#resources.values()).sort(
|
|
||||||
(a, b) => {
|
|
||||||
return b.priority - a.priority;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let encState = await this.loadEncryptionState(profilePath);
|
|
||||||
let encryptionEnabled = !!encState;
|
|
||||||
lazy.logConsole.debug("Encryption enabled: ", encryptionEnabled);
|
|
||||||
|
|
||||||
// Perform the backup for each resource.
|
|
||||||
for (let resourceClass of sortedResources) {
|
|
||||||
try {
|
try {
|
||||||
lazy.logConsole.debug(
|
lazy.logConsole.debug(
|
||||||
`Backing up resource with key ${resourceClass.key}. ` +
|
`Creating backup for profile at ${profilePath}`
|
||||||
`Requires encryption: ${resourceClass.requiresEncryption}`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resourceClass.requiresEncryption && !encryptionEnabled) {
|
let archiveDestFolderPath = await this.resolveArchiveDestFolderPath(
|
||||||
lazy.logConsole.debug(
|
lazy.backupDirPref
|
||||||
"Encryption is not currently enabled. Skipping."
|
);
|
||||||
);
|
lazy.logConsole.debug(
|
||||||
continue;
|
`Destination for archive: ${archiveDestFolderPath}`
|
||||||
}
|
);
|
||||||
|
|
||||||
let resourcePath = PathUtils.join(stagingPath, resourceClass.key);
|
let manifest = await this.#createBackupManifest();
|
||||||
await IOUtils.makeDirectory(resourcePath);
|
|
||||||
|
|
||||||
// `backup` on each BackupResource should return us a ManifestEntry
|
// First, check to see if a `backups` directory already exists in the
|
||||||
// that we eventually write to a JSON manifest file, but for now,
|
// profile.
|
||||||
// we're just going to log it.
|
let backupDirPath = PathUtils.join(
|
||||||
let manifestEntry = await new resourceClass().backup(
|
|
||||||
resourcePath,
|
|
||||||
profilePath,
|
profilePath,
|
||||||
encryptionEnabled
|
BackupService.PROFILE_FOLDER_NAME,
|
||||||
|
BackupService.SNAPSHOTS_FOLDER_NAME
|
||||||
|
);
|
||||||
|
lazy.logConsole.debug("Creating backups folder");
|
||||||
|
|
||||||
|
// ignoreExisting: true is the default, but we're being explicit that it's
|
||||||
|
// okay if this folder already exists.
|
||||||
|
await IOUtils.makeDirectory(backupDirPath, {
|
||||||
|
ignoreExisting: true,
|
||||||
|
createAncestors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let stagingPath = await this.#prepareStagingFolder(backupDirPath);
|
||||||
|
|
||||||
|
// Sort resources be priority.
|
||||||
|
let sortedResources = Array.from(this.#resources.values()).sort(
|
||||||
|
(a, b) => {
|
||||||
|
return b.priority - a.priority;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (manifestEntry === undefined) {
|
let encState = await this.loadEncryptionState(profilePath);
|
||||||
lazy.logConsole.error(
|
let encryptionEnabled = !!encState;
|
||||||
`Backup of resource with key ${resourceClass.key} returned undefined
|
lazy.logConsole.debug("Encryption enabled: ", encryptionEnabled);
|
||||||
as its ManifestEntry instead of null or an object`
|
|
||||||
);
|
// Perform the backup for each resource.
|
||||||
} else {
|
for (let resourceClass of sortedResources) {
|
||||||
lazy.logConsole.debug(
|
try {
|
||||||
`Backup of resource with key ${resourceClass.key} completed`,
|
lazy.logConsole.debug(
|
||||||
manifestEntry
|
`Backing up resource with key ${resourceClass.key}. ` +
|
||||||
);
|
`Requires encryption: ${resourceClass.requiresEncryption}`
|
||||||
manifest.resources[resourceClass.key] = manifestEntry;
|
);
|
||||||
|
|
||||||
|
if (resourceClass.requiresEncryption && !encryptionEnabled) {
|
||||||
|
lazy.logConsole.debug(
|
||||||
|
"Encryption is not currently enabled. Skipping."
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let resourcePath = PathUtils.join(stagingPath, resourceClass.key);
|
||||||
|
await IOUtils.makeDirectory(resourcePath);
|
||||||
|
|
||||||
|
// `backup` on each BackupResource should return us a ManifestEntry
|
||||||
|
// that we eventually write to a JSON manifest file, but for now,
|
||||||
|
// we're just going to log it.
|
||||||
|
let manifestEntry = await new resourceClass().backup(
|
||||||
|
resourcePath,
|
||||||
|
profilePath,
|
||||||
|
encryptionEnabled
|
||||||
|
);
|
||||||
|
|
||||||
|
if (manifestEntry === undefined) {
|
||||||
|
lazy.logConsole.error(
|
||||||
|
`Backup of resource with key ${resourceClass.key} returned undefined
|
||||||
|
as its ManifestEntry instead of null or an object`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
lazy.logConsole.debug(
|
||||||
|
`Backup of resource with key ${resourceClass.key} completed`,
|
||||||
|
manifestEntry
|
||||||
|
);
|
||||||
|
manifest.resources[resourceClass.key] = manifestEntry;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
lazy.logConsole.error(
|
||||||
|
`Failed to backup resource: ${resourceClass.key}`,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
lazy.logConsole.error(
|
// Ensure that the manifest abides by the current schema, and log
|
||||||
`Failed to backup resource: ${resourceClass.key}`,
|
// an error if somehow it doesn't. We'll want to collect telemetry for
|
||||||
e
|
// this case to make sure it's not happening in the wild. We debated
|
||||||
|
// throwing an exception here too, but that's not meaningfully better
|
||||||
|
// than creating a backup that's not schema-compliant. At least in this
|
||||||
|
// case, a user so-inclined could theoretically repair the manifest
|
||||||
|
// to make it valid.
|
||||||
|
let manifestSchema = await BackupService.MANIFEST_SCHEMA;
|
||||||
|
let schemaValidationResult = lazy.JsonSchema.validate(
|
||||||
|
manifest,
|
||||||
|
manifestSchema
|
||||||
);
|
);
|
||||||
|
if (!schemaValidationResult.valid) {
|
||||||
|
lazy.logConsole.error(
|
||||||
|
"Backup manifest does not conform to schema:",
|
||||||
|
manifest,
|
||||||
|
manifestSchema,
|
||||||
|
schemaValidationResult
|
||||||
|
);
|
||||||
|
// TODO: Collect telemetry for this case. (bug 1891817)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the manifest to the staging folder.
|
||||||
|
let manifestPath = PathUtils.join(
|
||||||
|
stagingPath,
|
||||||
|
BackupService.MANIFEST_FILE_NAME
|
||||||
|
);
|
||||||
|
await IOUtils.writeJSON(manifestPath, manifest);
|
||||||
|
|
||||||
|
let renamedStagingPath = await this.#finalizeStagingFolder(
|
||||||
|
stagingPath
|
||||||
|
);
|
||||||
|
lazy.logConsole.log(
|
||||||
|
"Wrote backup to staging directory at ",
|
||||||
|
renamedStagingPath
|
||||||
|
);
|
||||||
|
|
||||||
|
// Record the total size of the backup staging directory
|
||||||
|
let totalSizeKilobytes = await BackupResource.getDirectorySize(
|
||||||
|
renamedStagingPath
|
||||||
|
);
|
||||||
|
let totalSizeBytesNearestMebibyte = MeasurementUtils.fuzzByteSize(
|
||||||
|
totalSizeKilobytes * BYTES_IN_KILOBYTE,
|
||||||
|
1 * BYTES_IN_MEBIBYTE
|
||||||
|
);
|
||||||
|
lazy.logConsole.debug(
|
||||||
|
"total staging directory size in bytes: " +
|
||||||
|
totalSizeBytesNearestMebibyte
|
||||||
|
);
|
||||||
|
|
||||||
|
Glean.browserBackup.totalBackupSize.accumulate(
|
||||||
|
totalSizeBytesNearestMebibyte / BYTES_IN_MEBIBYTE
|
||||||
|
);
|
||||||
|
|
||||||
|
let compressedStagingPath = await this.#compressStagingFolder(
|
||||||
|
renamedStagingPath,
|
||||||
|
backupDirPath
|
||||||
|
).finally(async () => {
|
||||||
|
await IOUtils.remove(renamedStagingPath, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now create the single-file archive. For now, we'll stash this in the
|
||||||
|
// backups folder while it gets written. Once that's done, we'll attempt
|
||||||
|
// to move it to the user's configured backup path.
|
||||||
|
let archiveTmpPath = PathUtils.join(backupDirPath, "archive.html");
|
||||||
|
lazy.logConsole.log(
|
||||||
|
"Exporting single-file archive to ",
|
||||||
|
archiveTmpPath
|
||||||
|
);
|
||||||
|
await this.createArchive(
|
||||||
|
archiveTmpPath,
|
||||||
|
BackupService.ARCHIVE_TEMPLATE,
|
||||||
|
compressedStagingPath,
|
||||||
|
this.#encState,
|
||||||
|
manifest.meta
|
||||||
|
).finally(async () => {
|
||||||
|
await IOUtils.remove(compressedStagingPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Record the size of the complete single-file archive
|
||||||
|
let archiveSizeKilobytes = await BackupResource.getFileSize(
|
||||||
|
archiveTmpPath
|
||||||
|
);
|
||||||
|
let archiveSizeBytesNearestMebibyte = MeasurementUtils.fuzzByteSize(
|
||||||
|
archiveSizeKilobytes * BYTES_IN_KILOBYTE,
|
||||||
|
1 * BYTES_IN_MEBIBYTE
|
||||||
|
);
|
||||||
|
lazy.logConsole.debug(
|
||||||
|
"backup archive size in bytes: " + archiveSizeBytesNearestMebibyte
|
||||||
|
);
|
||||||
|
|
||||||
|
Glean.browserBackup.compressedArchiveSize.accumulate(
|
||||||
|
archiveSizeBytesNearestMebibyte / BYTES_IN_MEBIBYTE
|
||||||
|
);
|
||||||
|
|
||||||
|
let archivePath = await this.finalizeSingleFileArchive(
|
||||||
|
archiveTmpPath,
|
||||||
|
archiveDestFolderPath,
|
||||||
|
manifest.meta
|
||||||
|
);
|
||||||
|
|
||||||
|
let nowSeconds = Math.floor(Date.now() / 1000);
|
||||||
|
Services.prefs.setIntPref(
|
||||||
|
LAST_BACKUP_TIMESTAMP_PREF_NAME,
|
||||||
|
nowSeconds
|
||||||
|
);
|
||||||
|
this.#_state.lastBackupDate = nowSeconds;
|
||||||
|
Glean.browserBackup.totalBackupTime.stopAndAccumulate(backupTimer);
|
||||||
|
|
||||||
|
return { manifest, archivePath };
|
||||||
|
} catch {
|
||||||
|
Glean.browserBackup.totalBackupTime.cancel(backupTimer);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
this.#backupInProgress = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
// Ensure that the manifest abides by the current schema, and log
|
|
||||||
// an error if somehow it doesn't. We'll want to collect telemetry for
|
|
||||||
// this case to make sure it's not happening in the wild. We debated
|
|
||||||
// throwing an exception here too, but that's not meaningfully better
|
|
||||||
// than creating a backup that's not schema-compliant. At least in this
|
|
||||||
// case, a user so-inclined could theoretically repair the manifest
|
|
||||||
// to make it valid.
|
|
||||||
let manifestSchema = await BackupService.MANIFEST_SCHEMA;
|
|
||||||
let schemaValidationResult = lazy.JsonSchema.validate(
|
|
||||||
manifest,
|
|
||||||
manifestSchema
|
|
||||||
);
|
|
||||||
if (!schemaValidationResult.valid) {
|
|
||||||
lazy.logConsole.error(
|
|
||||||
"Backup manifest does not conform to schema:",
|
|
||||||
manifest,
|
|
||||||
manifestSchema,
|
|
||||||
schemaValidationResult
|
|
||||||
);
|
|
||||||
// TODO: Collect telemetry for this case. (bug 1891817)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the manifest to the staging folder.
|
|
||||||
let manifestPath = PathUtils.join(
|
|
||||||
stagingPath,
|
|
||||||
BackupService.MANIFEST_FILE_NAME
|
|
||||||
);
|
|
||||||
await IOUtils.writeJSON(manifestPath, manifest);
|
|
||||||
|
|
||||||
let renamedStagingPath = await this.#finalizeStagingFolder(stagingPath);
|
|
||||||
lazy.logConsole.log(
|
|
||||||
"Wrote backup to staging directory at ",
|
|
||||||
renamedStagingPath
|
|
||||||
);
|
|
||||||
|
|
||||||
// Record the total size of the backup staging directory
|
|
||||||
let totalSizeKilobytes = await BackupResource.getDirectorySize(
|
|
||||||
renamedStagingPath
|
|
||||||
);
|
|
||||||
let totalSizeBytesNearestMebibyte = MeasurementUtils.fuzzByteSize(
|
|
||||||
totalSizeKilobytes * BYTES_IN_KILOBYTE,
|
|
||||||
1 * BYTES_IN_MEBIBYTE
|
|
||||||
);
|
|
||||||
lazy.logConsole.debug(
|
|
||||||
"total staging directory size in bytes: " +
|
|
||||||
totalSizeBytesNearestMebibyte
|
|
||||||
);
|
|
||||||
|
|
||||||
Glean.browserBackup.totalBackupSize.accumulate(
|
|
||||||
totalSizeBytesNearestMebibyte / BYTES_IN_MEBIBYTE
|
|
||||||
);
|
|
||||||
|
|
||||||
let compressedStagingPath = await this.#compressStagingFolder(
|
|
||||||
renamedStagingPath,
|
|
||||||
backupDirPath
|
|
||||||
).finally(async () => {
|
|
||||||
await IOUtils.remove(renamedStagingPath, { recursive: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now create the single-file archive. For now, we'll stash this in the
|
|
||||||
// backups folder while it gets written. Once that's done, we'll attempt
|
|
||||||
// to move it to the user's configured backup path.
|
|
||||||
let archiveTmpPath = PathUtils.join(backupDirPath, "archive.html");
|
|
||||||
lazy.logConsole.log("Exporting single-file archive to ", archiveTmpPath);
|
|
||||||
await this.createArchive(
|
|
||||||
archiveTmpPath,
|
|
||||||
BackupService.ARCHIVE_TEMPLATE,
|
|
||||||
compressedStagingPath,
|
|
||||||
this.#encState,
|
|
||||||
manifest.meta
|
|
||||||
).finally(async () => {
|
|
||||||
await IOUtils.remove(compressedStagingPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Record the size of the complete single-file archive
|
|
||||||
let archiveSizeKilobytes = await BackupResource.getFileSize(
|
|
||||||
archiveTmpPath
|
|
||||||
);
|
|
||||||
let archiveSizeBytesNearestMebibyte = MeasurementUtils.fuzzByteSize(
|
|
||||||
archiveSizeKilobytes * BYTES_IN_KILOBYTE,
|
|
||||||
1 * BYTES_IN_MEBIBYTE
|
|
||||||
);
|
|
||||||
lazy.logConsole.debug(
|
|
||||||
"backup archive size in bytes: " + archiveSizeBytesNearestMebibyte
|
|
||||||
);
|
|
||||||
|
|
||||||
Glean.browserBackup.compressedArchiveSize.accumulate(
|
|
||||||
archiveSizeBytesNearestMebibyte / BYTES_IN_MEBIBYTE
|
|
||||||
);
|
|
||||||
|
|
||||||
let archivePath = await this.finalizeSingleFileArchive(
|
|
||||||
archiveTmpPath,
|
|
||||||
archiveDestFolderPath,
|
|
||||||
manifest.meta
|
|
||||||
);
|
|
||||||
|
|
||||||
let nowSeconds = Math.floor(Date.now() / 1000);
|
|
||||||
Services.prefs.setIntPref(LAST_BACKUP_TIMESTAMP_PREF_NAME, nowSeconds);
|
|
||||||
this.#_state.lastBackupDate = nowSeconds;
|
|
||||||
Glean.browserBackup.totalBackupTime.stopAndAccumulate(backupTimer);
|
|
||||||
|
|
||||||
return { manifest, archivePath };
|
|
||||||
} catch {
|
|
||||||
Glean.browserBackup.totalBackupTime.cancel(backupTimer);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
this.#backupInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3121,6 +3156,7 @@ export class BackupService extends EventTarget {
|
|||||||
}
|
}
|
||||||
case "quit-application-granted": {
|
case "quit-application-granted": {
|
||||||
this.uninitBackupScheduler();
|
this.uninitBackupScheduler();
|
||||||
|
this.#backupWriteAbortController.abort();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3279,38 +3315,44 @@ export class BackupService extends EventTarget {
|
|||||||
* @returns {Promise<undefined>}
|
* @returns {Promise<undefined>}
|
||||||
*/
|
*/
|
||||||
async deleteLastBackup() {
|
async deleteLastBackup() {
|
||||||
if (this.#_state.lastBackupFileName) {
|
return locks.request(
|
||||||
let backupFilePath = PathUtils.join(
|
BackupService.WRITE_BACKUP_LOCK_NAME,
|
||||||
lazy.backupDirPref,
|
{ signal: this.#backupWriteAbortController.signal },
|
||||||
this.#_state.lastBackupFileName
|
async () => {
|
||||||
);
|
if (this.#_state.lastBackupFileName) {
|
||||||
|
let backupFilePath = PathUtils.join(
|
||||||
|
lazy.backupDirPref,
|
||||||
|
this.#_state.lastBackupFileName
|
||||||
|
);
|
||||||
|
|
||||||
lazy.logConsole.log(
|
lazy.logConsole.log(
|
||||||
"Attempting to delete last backup file at ",
|
"Attempting to delete last backup file at ",
|
||||||
backupFilePath
|
backupFilePath
|
||||||
);
|
);
|
||||||
await IOUtils.remove(backupFilePath, { ignoreAbsent: true });
|
await IOUtils.remove(backupFilePath, { ignoreAbsent: true });
|
||||||
|
|
||||||
this.#_state.lastBackupDate = null;
|
this.#_state.lastBackupDate = null;
|
||||||
Services.prefs.clearUserPref(LAST_BACKUP_TIMESTAMP_PREF_NAME);
|
Services.prefs.clearUserPref(LAST_BACKUP_TIMESTAMP_PREF_NAME);
|
||||||
|
|
||||||
this.#_state.lastBackupFileName = "";
|
this.#_state.lastBackupFileName = "";
|
||||||
Services.prefs.clearUserPref(LAST_BACKUP_FILE_NAME_PREF_NAME);
|
Services.prefs.clearUserPref(LAST_BACKUP_FILE_NAME_PREF_NAME);
|
||||||
|
|
||||||
this.stateUpdate();
|
this.stateUpdate();
|
||||||
} else {
|
} else {
|
||||||
lazy.logConsole.log(
|
lazy.logConsole.log(
|
||||||
"Not deleting last backup file, since none is known about."
|
"Not deleting last backup file, since none is known about."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await IOUtils.exists(lazy.backupDirPref)) {
|
if (await IOUtils.exists(lazy.backupDirPref)) {
|
||||||
// See if there are any other files lingering around in the destination
|
// See if there are any other files lingering around in the destination
|
||||||
// folder. If not, delete that folder too.
|
// folder. If not, delete that folder too.
|
||||||
let children = await IOUtils.getChildren(lazy.backupDirPref);
|
let children = await IOUtils.getChildren(lazy.backupDirPref);
|
||||||
if (!children.length) {
|
if (!children.length) {
|
||||||
await IOUtils.remove(lazy.backupDirPref);
|
await IOUtils.remove(lazy.backupDirPref);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user