Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
SelectableProfileService:
"resource:///modules/profiles/SelectableProfileService.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "l10n", () => {
return new Localization(["browser/backupSettings.ftl"]);
});
ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
return console.createInstance({
prefix: "SelectableProfileBackupResource",
maxLogLevel: Services.prefs.getBoolPref("browser.backup.log", false)
? "Debug"
: "Warn",
});
});
const SELECTABLE_PROFILE_STAGING_FOLDER_NAME = "selectable_profile_metadata";
const SELECTABLE_PROFILE_METADATA_FILE_NAME = "profile_metadata.json";
/**
* Class representing data needed for backing up/restoring a selectable profile. This resource
* is only to be used if we are
* a. backing up a selectable profile
* b. recovering a selectable profile type backup. This is guaranteed if (a) was true when backing up.
*/
export class SelectableProfileBackupResource extends BackupResource {
static get key() {
return "selectable_profile";
}
static get requiresEncryption() {
return false;
}
static get canBackupResource() {
// If the current profile is not a Selectable Profile - we don't want to backup these items
return lazy.SelectableProfileService.currentProfile;
}
async backup(
stagingPath,
profilePath = PathUtils.profileDir,
_isEncrypting = false
) {
let selectableProfile = await lazy.SelectableProfileService.currentProfile;
if (!selectableProfile) {
// No selectable profile for this path - nothing to backup
lazy.logConsole.warn(
`There is no valid selectable profile in this path ${profilePath}`
);
return null;
}
// Create a selectable profile directory in the staging path
let stagingSelectableProfilesDirectoryPath = PathUtils.join(
stagingPath,
SELECTABLE_PROFILE_STAGING_FOLDER_NAME
);
await IOUtils.makeDirectory(stagingSelectableProfilesDirectoryPath);
lazy.logConsole.debug(
`Created staging directory at ${stagingSelectableProfilesDirectoryPath}`
);
// add the avatar file if there is a custom avatar being used
if (selectableProfile.hasCustomAvatar) {
let avatarDirectoryPath = PathUtils.parent(
lazy.SelectableProfileService.currentProfile.getAvatarPath()
);
// Copy over the custom avatar file into this folder, the size doesn't matter here
await BackupResource.copyFiles(
avatarDirectoryPath,
stagingSelectableProfilesDirectoryPath,
[selectableProfile.avatar]
);
lazy.logConsole.debug(`Copied over the custom avatar file into staging`);
}
// Create a json with the name, theme, avatar to store
let metadata = {
name: selectableProfile.name,
theme: selectableProfile.theme,
avatar: selectableProfile.avatar,
hasCustomAvatar: selectableProfile.hasCustomAvatar,
deviceName: Services.sysinfo.get("device") || Services.dns.myHostName,
};
let metadataFile = PathUtils.join(
stagingSelectableProfilesDirectoryPath,
SELECTABLE_PROFILE_METADATA_FILE_NAME
);
await IOUtils.writeJSON(metadataFile, metadata);
lazy.logConsole.debug(
`Finished backing up selectable profile data with metadata ${JSON.stringify(metadata)}`
);
return null;
}
async recover(_manifestEntry, recoveryPath, destProfilePath) {
// Guard against calling this recover method if the current profile
// is not a selectable profile type.
if (!lazy.SelectableProfileService.hasCreatedSelectableProfiles()) {
lazy.logConsole.error(
`We can only call recover on SelectableProfileBackupResource if the current profile is a selectable profile`
);
return null;
}
// Read the data!
let selectableProfileRecoveryDirectoryPath = PathUtils.join(
recoveryPath,
SELECTABLE_PROFILE_STAGING_FOLDER_NAME
);
let selectableProfileMetadataFilePath = PathUtils.join(
selectableProfileRecoveryDirectoryPath,
SELECTABLE_PROFILE_METADATA_FILE_NAME
);
let metadata;
try {
metadata = await IOUtils.readJSON(selectableProfileMetadataFilePath);
} catch (e) {
lazy.logConsole.error(`Had trouble reading JSON ${e}`);
return null;
}
let newProfile = await lazy.SelectableProfileService.getProfileByPath(
await IOUtils.getFile(destProfilePath)
);
if (!newProfile) {
lazy.logConsole.error(
`No selectable profile found at ${destProfilePath}`
);
return null;
}
lazy.logConsole.debug(
`Set the new profile's data with backed up metadata ${JSON.stringify(metadata)}`
);
// Before we set the name, let's make sure it's unique
let profilesArray = await lazy.SelectableProfileService.getAllProfiles();
let isDupe = profilesArray.some(
p => p.id !== newProfile.id && p.name === metadata.name
);
if (isDupe) {
metadata.name = await lazy.l10n.formatValue(
"backup-restored-profile-name",
{
deviceName: metadata.deviceName || "backup",
date: Date.now(),
}
);
}
await newProfile.setNameAsync(metadata.name);
await newProfile.setThemeAsync(metadata.theme);
if (metadata.hasCustomAvatar) {
// Ensure that there is an avatar file in the recovery directory
let avatarRecoveryFilePath = PathUtils.join(
selectableProfileRecoveryDirectoryPath,
metadata.avatar
);
if (!(await IOUtils.exists(avatarRecoveryFilePath))) {
lazy.logConsole.error(
`We expect a custom avatar, but there is no file in the recovery dest: ${avatarRecoveryFilePath}`
);
return null;
}
let avatarFile = await File.createFromFileName(avatarRecoveryFilePath);
await newProfile.setAvatar(avatarFile);
} else {
await newProfile.setAvatar(metadata.avatar);
}
lazy.logConsole.debug("Finished recovery of selectable profile data");
return null;
}
async measure(_profilePath) {
// No measurements needed here
}
}