diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/export | |
parent | Initial commit. (diff) | |
download | thunderbird-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.js | 154 | ||||
-rw-r--r-- | comm/mailnews/export/content/exportDialog.xhtml | 49 | ||||
-rw-r--r-- | comm/mailnews/export/modules/ProfileExporter.jsm | 114 | ||||
-rw-r--r-- | comm/mailnews/export/modules/moz.build | 8 |
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", +] |