/* 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 } }