Bug 271863 - Support exporting the current profile to a zip file. r=darktrojan

- Add an Export menu item to the main menu and app menu
- Add an exportDialog.xhtml

Depends on D119024.

Differential Revision: https://phabricator.services.mozilla.com/D119168
This commit is contained in:
Ping Chen 2021-07-08 04:51:39 +00:00
Родитель 03872db0e6
Коммит 5abaf55a6c
8 изменённых файлов: 219 добавлений и 1 удалений

Просмотреть файл

@ -1,4 +1,4 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
/* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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 http://mozilla.org/MPL/2.0/. */
@ -518,6 +518,14 @@ function toImport() {
);
}
function toExport() {
window.openDialog(
"chrome://messenger/content/exportDialog.xhtml",
"exportDialog",
"chrome, modal, titlebar, centerscreen"
);
}
function toSanitize() {
let sanitizerScope = {};
Services.scriptloader.loadSubScript(

Просмотреть файл

@ -1094,6 +1094,9 @@
<menuitem id="menu_import" label="&importCmd.label;"
accesskey="&importCmd.accesskey;"
oncommand="toImport();"/>
<menuitem id="menu_export" label="&exportCmd.label;"
accesskey="&exportCmd.accesskey;"
oncommand="toExport();"/>
#ifdef MOZ_OPENPGP
<menuitem id="manageKeysOpenPGP" class="openpgp-item"
data-l10n-id="openpgp-manage-keys-openpgp-cmd"

Просмотреть файл

@ -1669,6 +1669,10 @@
class="subviewbutton subviewbutton-iconic"
label="&importCmd.label;"
oncommand="toImport();"/>
<toolbarbutton id="appmenu_export"
class="subviewbutton subviewbutton-iconic"
label="&exportCmd.label;"
oncommand="toExport();"/>
#ifdef MOZ_OPENPGP
<toolbarbutton id="appmenu_manageKeysOpenPGP"
class="subviewbutton subviewbutton-iconic openpgp-item"

Просмотреть файл

@ -494,6 +494,8 @@
<!ENTITY deleteJunk.accesskey "D">
<!ENTITY importCmd.label "Import…">
<!ENTITY importCmd.accesskey "m">
<!ENTITY exportCmd.label "Export…">
<!ENTITY exportCmd.accesskey "x">
<!ENTITY clearRecentHistory.label "Clear Recent History…">
<!ENTITY clearRecentHistory.accesskey "H">
<!ENTITY accountManagerCmd2.label "Account Settings">

Просмотреть файл

@ -0,0 +1,23 @@
# 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 http://mozilla.org/MPL/2.0/.
export-dialog-brand-name = { -brand-product-name }
export-dialog-window =
.title = Export
export-dialog =
.buttonlabelaccept = Next
export-dialog-button-finish = Finish
export-dialog-file-picker = Export to a zip file
export-dialog-desc1 = Export mail accounts, mail messages, address books, preferences to a zip file.
export-dialog-desc2 = When needed, you can import the zip file to restore your profile.
export-dialog-exporting = Exporting…
export-dialog-exported = Exported!

Просмотреть файл

@ -0,0 +1,145 @@
/* 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 http://mozilla.org/MPL/2.0/. */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
var nsFile = Components.Constructor(
"@mozilla.org/file/local;1",
"nsIFile",
"initWithPath"
);
// No need to backup those paths, they are not used when importing.
const IGNORE_PATHS = [
"cache2",
"chrome_debugger_profile",
"crashes",
"datareporting",
"extensions",
"extension-store",
"logs",
"lock",
"minidumps",
"saved-telemetry-pings",
"security_state",
"storage",
"xulstore",
];
var zipW;
document.addEventListener("dialogaccept", async event => {
if (zipW) {
// This will close the dialog.
return;
}
// Do not close the dialog, but open a FilePicker to set the output location.
event.preventDefault();
let [filePickerTitle, brandName] = await document.l10n.formatValues([
"export-dialog-file-picker",
"export-dialog-brand-name",
]);
let filePicker = Components.Constructor(
"@mozilla.org/filepicker;1",
"nsIFilePicker"
)();
filePicker.init(window, filePickerTitle, Ci.nsIFilePicker.modeSave);
filePicker.defaultString = `${brandName}_profile_backup.zip`;
filePicker.defaultExtension = "zip";
filePicker.appendFilter("", "*.zip");
filePicker.open(rv => {
if (
[Ci.nsIFilePicker.returnOK, Ci.nsIFilePicker.returnReplace].includes(
rv
) &&
filePicker.file
) {
exportCurrentProfile(filePicker.file);
} else {
window.close();
}
});
});
/**
* Export the current profile to the specified target zip file.
* @param {nsIFile} targetFile - A target zip file to write to.
*/
async function exportCurrentProfile(targetFile) {
let [
progressExporting,
progressExported,
buttonLabelFinish,
] = await document.l10n.formatValues([
"export-dialog-exporting",
"export-dialog-exported",
"export-dialog-button-finish",
]);
document.getElementById("progressBar").hidden = false;
let progressStatus = document.getElementById("progressStatus");
progressStatus.value = progressExporting;
let buttonAccept = document.querySelector("dialog").getButton("accept");
buttonAccept.disabled = true;
document.querySelector("dialog").getButton("cancel").hidden = true;
zipW = Components.Constructor("@mozilla.org/zipwriter;1", "nsIZipWriter")();
// MODE_WRONLY (0x02) and MODE_CREATE (0x08)
zipW.open(targetFile, 0x02 | 0x08);
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
let rootPath = profileDir.parent.path;
let zipEntryMap = new Map();
await collectFilesToZip(zipEntryMap, rootPath, profileDir);
let progressElement = document.getElementById("progress");
progressElement.max = zipEntryMap.size;
let i = 0;
for (let [path, file] of zipEntryMap) {
zipW.addEntryFile(
path,
0, // no compression, bigger file but much faster
file,
false
);
if (++i % 10 === 0) {
progressElement.value = i;
await new Promise(resolve => setTimeout(resolve));
}
}
progressElement.value = progressElement.max;
zipW.close();
progressStatus.value = progressExported;
buttonAccept.disabled = false;
buttonAccept.label = buttonLabelFinish;
}
/**
* Recursively collect files to be zipped, save the entries into zipEntryMap.
* @param {Map<string, nsIFile>} zipEntryMap - Collection of files to be zipped.
* @param {string} rootPath - The rootPath to zip from.
* @param {nsIFile} folder - The folder to search for files to zip.
*/
async function collectFilesToZip(zipEntryMap, rootPath, folder) {
let entries = await IOUtils.getChildren(folder.path);
for (let entry of entries) {
let file = nsFile(entry);
if (file.isDirectory()) {
await collectFilesToZip(zipEntryMap, rootPath, file);
} else {
// We don't want to include the rootPath part in the zip file.
let path = entry.slice(rootPath.length + 1);
// path now looks like this: profile-default/lock.
let parts = path.split("/");
if (IGNORE_PATHS.includes(parts[1])) {
continue;
}
zipEntryMap.set(path, file);
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTf-8"?>
<!-- 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 http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/input-fields.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
data-l10n-id="export-dialog-window"
lightweightthemes="true"
width="500">
<dialog buttons="accept,cancel" style="min-height: 12em"
data-l10n-id="export-dialog"
data-l10n-attrs="buttonlabelaccept">
<html:link rel="localization" href="branding/brand.ftl" />
<html:link rel="localization" href="messenger/exportDialog.ftl"/>
<script src="chrome://messenger/content/exportDialog.js"/>
<description data-l10n-id="export-dialog-desc1"></description>
<description data-l10n-id="export-dialog-desc2"></description>
<separator/>
<vbox id="progressBar" hidden="true">
<html:progress id="progress" value="0" max="100" style="flex:1;"/>
<label id="progressStatus" crop="center"/>
</vbox>
</dialog>
</window>

Просмотреть файл

@ -105,6 +105,8 @@ messenger.jar:
content/messenger/messengercompose/askSendFormat.xhtml (compose/content/askSendFormat.xhtml)
content/messenger/messengercompose/sendProgress.xhtml (compose/content/sendProgress.xhtml)
content/messenger/messengercompose/sendProgress.js (compose/content/sendProgress.js)
content/messenger/exportDialog.js (export/content/exportDialog.js)
content/messenger/exportDialog.xhtml (export/content/exportDialog.xhtml)
content/messenger/importDialog.js (import/content/importDialog.js)
* content/messenger/importDialog.xhtml (import/content/importDialog.xhtml)
* content/messenger/fieldMapImport.xhtml (import/content/fieldMapImport.xhtml)