summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/export
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/export
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/export')
-rw-r--r--comm/mailnews/export/content/exportDialog.js154
-rw-r--r--comm/mailnews/export/content/exportDialog.xhtml49
-rw-r--r--comm/mailnews/export/modules/ProfileExporter.jsm114
-rw-r--r--comm/mailnews/export/modules/moz.build8
4 files changed, 325 insertions, 0 deletions
diff --git a/comm/mailnews/export/content/exportDialog.js b/comm/mailnews/export/content/exportDialog.js
new file mode 100644
index 0000000000..4887526865
--- /dev/null
+++ b/comm/mailnews/export/content/exportDialog.js
@@ -0,0 +1,154 @@
+/* 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 { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+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",
+ "parent.lock",
+ "shader-cache",
+ "saved-telemetry-pings",
+ "security_state",
+ "storage",
+ "xulstore",
+];
+
+var zipW;
+
+var logger = console.createInstance({
+ prefix: "mail.export",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.export.loglevel",
+});
+
+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) {
+ logger.debug("Adding entry file:", path);
+ 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);
+ let separator = Services.appinfo.OS == "WINNT" ? "\\" : "/";
+ 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(separator);
+ if (IGNORE_PATHS.includes(parts[1])) {
+ continue;
+ }
+ // Path separator inside a zip file is always "/".
+ zipEntryMap.set(parts.join("/"), file);
+ }
+ }
+}
diff --git a/comm/mailnews/export/content/exportDialog.xhtml b/comm/mailnews/export/content/exportDialog.xhtml
new file mode 100644
index 0000000000..9cc28fb064
--- /dev/null
+++ b/comm/mailnews/export/content/exportDialog.xhtml
@@ -0,0 +1,49 @@
+<?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/colors.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ scrolling="false"
+ lightweightthemes="true"
+>
+ <head>
+ <link rel="localization" href="branding/brand.ftl" />
+ <link rel="localization" href="messenger/exportDialog.ftl" />
+ <title data-l10n-id="export-dialog-title"></title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/exportDialog.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/dialogShadowDom.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog
+ buttons="accept,cancel"
+ style="min-height: 12em; width: 500px"
+ data-l10n-id="export-dialog"
+ data-l10n-attrs="buttonlabelaccept"
+ >
+ <description data-l10n-id="export-dialog-description1"></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>
+ </html:body>
+</html>
diff --git a/comm/mailnews/export/modules/ProfileExporter.jsm b/comm/mailnews/export/modules/ProfileExporter.jsm
new file mode 100644
index 0000000000..6348471fbd
--- /dev/null
+++ b/comm/mailnews/export/modules/ProfileExporter.jsm
@@ -0,0 +1,114 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["ProfileExporter"];
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ setTimeout: "resource://gre/modules/Timer.sys.mjs",
+});
+
+// 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",
+ "parent.lock",
+ "shader-cache",
+ "saved-telemetry-pings",
+ "security_state",
+ "storage",
+ "xulstore",
+];
+
+/**
+ * A module to export the current profile to a zip file.
+ */
+class ProfileExporter {
+ _logger = console.createInstance({
+ prefix: "mail.export",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.export.loglevel",
+ });
+
+ /**
+ * Callback for progress updates.
+ *
+ * @param {number} current - Current imported items count.
+ * @param {number} total - Total items count.
+ */
+ onProgress = () => {};
+
+ /**
+ * Export the current profile to the specified target zip file.
+ *
+ * @param {nsIFile} targetFile - A target zip file to write to.
+ */
+ async startExport(targetFile) {
+ let 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 rootPathCount = PathUtils.split(profileDir.parent.path).length;
+ let zipEntryMap = new Map();
+ await this._collectFilesToZip(zipEntryMap, rootPathCount, profileDir);
+
+ let totalEntries = zipEntryMap.size;
+ let i = 0;
+ for (let [path, file] of zipEntryMap) {
+ try {
+ zipW.addEntryFile(
+ path,
+ 0, // no compression, bigger file but much faster
+ file,
+ false
+ );
+ } catch (e) {
+ this._logger.error(`Failed to add ${path}`, e);
+ }
+ if (++i % 10 === 0) {
+ this.onProgress(i, totalEntries);
+ await new Promise(resolve => lazy.setTimeout(resolve));
+ }
+ }
+ this.onProgress(totalEntries, totalEntries);
+ zipW.close();
+ }
+
+ /**
+ * Recursively collect files to be zipped, save the entries into zipEntryMap.
+ *
+ * @param {Map<string, nsIFile>} zipEntryMap - Collection of files to be zipped.
+ * @param {number} rootPathCount - The count of rootPath parts.
+ * @param {nsIFile} folder - The folder to search for files to zip.
+ */
+ async _collectFilesToZip(zipEntryMap, rootPathCount, folder) {
+ for (let file of folder.directoryEntries) {
+ if (!file.exists()) {
+ continue;
+ }
+ if (file.isDirectory()) {
+ await this._collectFilesToZip(zipEntryMap, rootPathCount, file);
+ } else {
+ // We don't want to include the rootPath part in the zip file.
+ let parts = PathUtils.split(file.path).slice(rootPathCount);
+ // Parts look like this: ["profile-default", "lock"].
+ if (IGNORE_PATHS.includes(parts[1])) {
+ continue;
+ }
+ // Path separator inside a zip file is always "/".
+ zipEntryMap.set(parts.join("/"), file);
+ }
+ }
+ }
+}
diff --git a/comm/mailnews/export/modules/moz.build b/comm/mailnews/export/modules/moz.build
new file mode 100644
index 0000000000..a6d704b287
--- /dev/null
+++ b/comm/mailnews/export/modules/moz.build
@@ -0,0 +1,8 @@
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_JS_MODULES += [
+ "ProfileExporter.jsm",
+]