diff options
Diffstat (limited to 'comm/mail/components/migration')
21 files changed, 4018 insertions, 0 deletions
diff --git a/comm/mail/components/migration/content/migration.js b/comm/mail/components/migration/content/migration.js new file mode 100644 index 0000000000..10e43600d1 --- /dev/null +++ b/comm/mail/components/migration/content/migration.js @@ -0,0 +1,464 @@ +/* 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 { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +var kIMig = Ci.nsIMailProfileMigrator; +var kIPStartup = Ci.nsIProfileStartup; +var kProfileMigratorContractIDPrefix = + "@mozilla.org/profile/migrator;1?app=mail&type="; + +var MigrationWizard = { + _source: "", // Source Profile Migrator ContractID suffix + _itemsFlags: kIMig.ALL, // Selected Import Data Sources (16-bit bitfield) + _selectedProfile: null, // Selected Profile name to import from + _wiz: null, + _migrator: null, + _autoMigrate: null, + + init() { + document + .querySelector("wizard") + .addEventListener("wizardback", this.onBack.bind(this)); + document + .querySelector("wizard") + .addEventListener("wizardcancel", this.onCancel.bind(this)); + + let importSourcePage = document.getElementById("importSource"); + importSourcePage.addEventListener( + "pageadvanced", + this.onImportSourcePageAdvanced.bind(this) + ); + + let selectProfilePage = document.getElementById("selectProfile"); + selectProfilePage.addEventListener( + "pageshow", + this.onSelectProfilePageShow.bind(this) + ); + selectProfilePage.addEventListener( + "pagerewound", + this.onSelectProfilePageRewound.bind(this) + ); + selectProfilePage.addEventListener( + "pageadvanced", + this.onSelectProfilePageAdvanced.bind(this) + ); + + let importItemsPage = document.getElementById("importItems"); + importItemsPage.addEventListener( + "pageshow", + this.onImportItemsPageShow.bind(this) + ); + importItemsPage.addEventListener( + "pagerewound", + this.onImportItemsPageAdvanced.bind(this) + ); + importItemsPage.addEventListener( + "pageadvanced", + this.onImportItemsPageAdvanced.bind(this) + ); + + let migratingPage = document.getElementById("migrating"); + migratingPage.addEventListener( + "pageshow", + this.onMigratingPageShow.bind(this) + ); + + let donePage = document.getElementById("done"); + donePage.addEventListener("pageshow", this.onDonePageShow.bind(this)); + + let failedPage = document.getElementById("failed"); + failedPage.addEventListener("pageshow", () => (this._failed = true)); + failedPage.addEventListener("pagerewound", () => (this._failed = false)); + + Services.obs.addObserver(this, "Migration:Started"); + Services.obs.addObserver(this, "Migration:ItemBeforeMigrate"); + Services.obs.addObserver(this, "Migration:ItemAfterMigrate"); + Services.obs.addObserver(this, "Migration:Ended"); + Services.obs.addObserver(this, "Migration:Progress"); + + this._wiz = document.querySelector("wizard"); + + if ("arguments" in window && !window.arguments[3]) { + this._source = window.arguments[0]; + this._migrator = window.arguments[1] + ? window.arguments[1].QueryInterface(kIMig) + : null; + this._autoMigrate = window.arguments[2].QueryInterface(kIPStartup); + + // Show the "nothing" option in the automigrate case to provide an + // easily identifiable way to avoid migration and create a new profile. + var nothing = document.getElementById("nothing"); + nothing.hidden = false; + } + + this.onImportSourcePageShow(); + + // Behavior alert! If we were given a migrator already, then we are going to perform migration + // with that migrator, skip the wizard screen where we show all of the migration sources and + // jump right into migration. + if (this._migrator) { + if (this._migrator.sourceHasMultipleProfiles) { + this._wiz.goTo("selectProfile"); + } else { + var sourceProfiles = this._migrator.sourceProfiles; + this._selectedProfile = sourceProfiles[0]; + this._wiz.goTo("migrating"); + } + } + }, + + uninit() { + Services.obs.removeObserver(this, "Migration:Started"); + Services.obs.removeObserver(this, "Migration:ItemBeforeMigrate"); + Services.obs.removeObserver(this, "Migration:ItemAfterMigrate"); + Services.obs.removeObserver(this, "Migration:Ended"); + Services.obs.removeObserver(this, "Migration:Progress"); + + // Imported accounts don't show up without restarting. + if (this._wiz.onLastPage && !this._failed) { + MailUtils.restartApplication(); + } + }, + + // 1 - Import Source + onImportSourcePageShow() { + this._wiz.canRewind = false; + this._wiz.canAdvance = false; + + // Figure out what source apps are are available to import from: + var group = document.getElementById("importSourceGroup"); + for (let childNode of group.children) { + let suffix = childNode.id; + if (suffix != "nothing") { + var contractID = + kProfileMigratorContractIDPrefix + suffix.split("-")[0]; + var migrator = Cc[contractID].createInstance(kIMig); + if (!migrator.sourceExists) { + childNode.hidden = true; + if (this._source == suffix) { + this._source = null; + } + } + } + } + + var firstNonDisabled = null; + for (let childNode of group.children) { + if (!childNode.hidden && !childNode.disabled) { + firstNonDisabled = childNode; + break; + } + } + group.selectedItem = + this._source == "" + ? firstNonDisabled + : document.getElementById(this._source); + + if (firstNonDisabled) { + this._wiz.canAdvance = true; + document.getElementById("importSourceFound").hidden = false; + return; + } + // If no usable import module was found, inform user and enable back button. + document.getElementById("importSourceNotFound").hidden = false; + this._wiz.canRewind = true; + this._wiz.getButton("back").setAttribute("hidden", "false"); + }, + + onImportSourcePageAdvanced() { + var newSource = + document.getElementById("importSourceGroup").selectedItem.id; + + if (newSource == "nothing") { + document.querySelector("wizard").cancel(); + return; + } + + if (!this._migrator || newSource != this._source) { + // Create the migrator for the selected source. + var contractID = + kProfileMigratorContractIDPrefix + newSource.split("-")[0]; + this._migrator = Cc[contractID].createInstance(kIMig); + + this._itemsFlags = kIMig.ALL; + this._selectedProfile = null; + } + + this._source = newSource; + + // check for more than one source profile + if (this._migrator.sourceHasMultipleProfiles) { + this._wiz.currentPage.next = "selectProfile"; + } else { + this._wiz.currentPage.next = "migrating"; + var sourceProfiles = this._migrator.sourceProfiles; + if (sourceProfiles && sourceProfiles.length == 1) { + this._selectedProfile = sourceProfiles[0]; + } else { + this._selectedProfile = ""; + } + } + }, + + // 2 - [Profile Selection] + onSelectProfilePageShow() { + // Disabling this for now, since we ask about import sources in automigration + // too and don't want to disable the back button + // if (this._autoMigrate) + // document.querySelector("wizard").getButton("back").disabled = true; + + var profiles = document.getElementById("profiles"); + while (profiles.hasChildNodes()) { + profiles.lastChild.remove(); + } + + if (!this._migrator) { + return; + } + var sourceProfiles = this._migrator.sourceProfiles; + var count = sourceProfiles.length; + for (var i = 0; i < count; ++i) { + var item = document.createXULElement("radio"); + item.id = sourceProfiles[i]; + item.setAttribute("label", item.id); + profiles.appendChild(item); + } + + profiles.selectedItem = this._selectedProfile + ? document.getElementById(this._selectedProfile) + : profiles.firstElementChild; + }, + + onSelectProfilePageRewound() { + var profiles = document.getElementById("profiles"); + this._selectedProfile = profiles.selectedItem.id; + }, + + onSelectProfilePageAdvanced() { + var profiles = document.getElementById("profiles"); + this._selectedProfile = profiles.selectedItem.id; + + // If we're automigrating, don't show the item selection page, just grab everything. + if (this._autoMigrate) { + this._wiz.currentPage.next = "migrating"; + } + }, + + // 3 - ImportItems + onImportItemsPageShow() { + var dataSources = document.getElementById("dataSources"); + while (dataSources.hasChildNodes()) { + dataSources.lastChild.remove(); + } + + var bundle = document.getElementById("bundle"); + + var items = this._migrator.getMigrateData( + this._selectedProfile, + this._autoMigrate + ); + for (var i = 0; i < 16; ++i) { + var itemID = (items >> i) & 0x1 ? Math.pow(2, i) : 0; + if (itemID > 0) { + var checkbox = document.createXULElement("checkbox"); + checkbox.id = itemID; + checkbox.setAttribute( + "label", + bundle.getString(itemID + "_" + this._source.split("-")[0]) + ); + dataSources.appendChild(checkbox); + if (!this._itemsFlags || this._itemsFlags & itemID) { + checkbox.checked = true; + } + } + } + }, + + onImportItemsPageAdvanced() { + var dataSources = document.getElementById("dataSources"); + this._itemsFlags = 0; + for (var i = 0; i < dataSources.children.length; ++i) { + var checkbox = dataSources.children[i]; + if (checkbox.localName == "checkbox" && checkbox.checked) { + this._itemsFlags |= parseInt(checkbox.id); + } + } + }, + + onImportItemCommand(aEvent) { + var items = document.getElementById("dataSources"); + var checkboxes = items.getElementsByTagName("checkbox"); + + var oneChecked = false; + for (var i = 0; i < checkboxes.length; ++i) { + if (checkboxes[i].checked) { + oneChecked = true; + break; + } + } + + this._wiz.canAdvance = oneChecked; + }, + + // 4 - Migrating + async onMigratingPageShow() { + this._wiz.getButton("cancel").disabled = true; + this._wiz.canRewind = false; + this._wiz.canAdvance = false; + + // When automigrating or migrating all, show all of the data that can + // be received from this source. + if (this._autoMigrate || this._itemsFlags == kIMig.ALL) { + this._itemsFlags = this._migrator.getMigrateData( + this._selectedProfile, + this._autoMigrate + ); + } + + this._listItems("migratingItems"); + try { + await this.onMigratingMigrate(); + } catch (e) { + switch (e.message) { + case "file-picker-cancelled": + this._wiz.canRewind = true; + this._wiz.rewind(); + this._wiz.canAdvance = true; + return; + case "zip-file-too-big": + this._wiz.canRewind = true; + this._wiz.rewind(); + this._wiz.canAdvance = true; + let [zipFileTooBigTitle, zipFileTooBigMessage] = + await document.l10n.formatValues([ + "zip-file-too-big-title", + "zip-file-too-big-message", + ]); + Services.prompt.alert( + window, + zipFileTooBigTitle, + zipFileTooBigMessage + ); + document.getElementById("importSourceGroup").selectedItem = + document.getElementById("thunderbird-dir"); + return; + default: + document.getElementById("failed-message-default").hidden = e.message; + document.getElementById("failed-message").hidden = !e.message; + document.getElementById("failed-message").textContent = + e.message || ""; + this._wiz.canAdvance = true; + this._wiz.advance("failed"); + throw e; + } + } + }, + + async onMigratingMigrate(aOuter) { + let [source, type] = this._source.split("-"); + if (source == "thunderbird") { + // Ask user for the profile directory location. + await this._migrator.wrappedJSObject.getProfileDir(window, type); + await this._migrator.wrappedJSObject.asyncMigrate(); + return; + } + this._migrator.migrate( + this._itemsFlags, + this._autoMigrate, + this._selectedProfile + ); + }, + + _listItems(aID) { + var items = document.getElementById(aID); + while (items.hasChildNodes()) { + items.lastChild.remove(); + } + + var bundle = document.getElementById("bundle"); + for (var i = 0; i < 16; ++i) { + var itemID = (this._itemsFlags >> i) & 0x1 ? Math.pow(2, i) : 0; + if (itemID > 0) { + var label = document.createXULElement("label"); + label.id = itemID + "_migrated"; + try { + label.setAttribute( + "value", + "- " + bundle.getString(itemID + "_" + this._source.split("-")[0]) + ); + items.appendChild(label); + } catch (e) { + // if the block above throws, we've enumerated all the import data types we + // currently support and are now just wasting time, break. + break; + } + } + } + }, + + observe(aSubject, aTopic, aData) { + switch (aTopic) { + case "Migration:Started": + dump("*** started\n"); + break; + case "Migration:ItemBeforeMigrate": { + dump("*** before " + aData + "\n"); + let label = document.getElementById(aData + "_migrated"); + if (label) { + label.setAttribute("style", "font-weight: bold"); + } + break; + } + case "Migration:ItemAfterMigrate": { + dump("*** after " + aData + "\n"); + let label = document.getElementById(aData + "_migrated"); + if (label) { + label.removeAttribute("style"); + } + break; + } + case "Migration:Ended": + dump("*** done\n"); + if (this._autoMigrate) { + // We're done now. + this._wiz.canAdvance = true; + this._wiz.advance(); + setTimeout(window.close, 5000); + } else { + this._wiz.canAdvance = true; + var nextButton = this._wiz.getButton("next"); + nextButton.click(); + } + break; + case "Migration:Progress": + document.getElementById("progressBar").value = aData; + break; + } + }, + + onDonePageShow() { + this._wiz.getButton("cancel").disabled = true; + this._wiz.canRewind = false; + this._listItems("doneItems"); + }, + + onBack(event) { + this._wiz.goTo("importSource"); + this._wiz.canRewind = false; + event.preventDefault(); + }, + + onCancel() { + // If .closeMigration is false, the user clicked Back button, + // then do not change its value. + if ( + window.arguments[3] && + "closeMigration" in window.arguments[3] && + window.arguments[3].closeMigration !== false + ) { + window.arguments[3].closeMigration = true; + } + }, +}; diff --git a/comm/mail/components/migration/content/migration.xhtml b/comm/mail/components/migration/content/migration.xhtml new file mode 100644 index 0000000000..4171d02f63 --- /dev/null +++ b/comm/mail/components/migration/content/migration.xhtml @@ -0,0 +1,89 @@ +<?xml version="1.0"?> +# 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"?> + +<!DOCTYPE window SYSTEM "chrome://messenger/locale/migration/migration.dtd" > + +<window id="migrationWizard" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="&migrationWizard.title;" + onload="MigrationWizard.init()" + onunload="MigrationWizard.uninit()" + style="width: 40em;" + branded="true" + buttons="accept,cancel"> + <linkset> + <html:link rel="localization" href="toolkit/global/wizard.ftl"/> + <html:link rel="localization" href="messenger/importDialog.ftl"/> + </linkset> + + <script src="chrome://messenger/content/migration/migration.js"/> + + <stringbundle id="bundle" src="chrome://messenger/locale/migration/migration.properties"/> + + <wizard> + <wizardpage id="importSource" pageid="importSource" next="selectProfile" + label="&importSource.title;"> + <vbox id="importSourceFound" hidden="true"> +#ifdef XP_WIN + <label control="importSourceGroup">&importFromWin.label;</label> +#else + <label control="importSourceGroup">&importFromNonWin.label;</label> +#endif + <radiogroup id="importSourceGroup"> + <radio id="thunderbird-zip" data-l10n-id="import-from-thunderbird-zip"/> + <radio id="thunderbird-dir" data-l10n-id="import-from-thunderbird-dir"/> + <radio id="seamonkey" label="&importFromSeamonkey3.label;" + accesskey="&importFromSeamonkey3.accesskey;"/> +#ifdef XP_WIN + <radio id="outlook" label="&importFromOutlook.label;" + accesskey="&importFromOutlook.accesskey;"/> +#endif + <radio id="nothing" label="&importFromNothing.label;" + accesskey="&importFromNothing.accesskey;" hidden="true"/> + </radiogroup> + </vbox> + <label id="importSourceNotFound" hidden="true">&importSourceNotFound.label;</label> + </wizardpage> + + <wizardpage id="selectProfile" pageid="selectProfile" label="&selectProfile.title;" + next="importItems"> + <label control="profiles">&selectProfile.label;</label> + <radiogroup id="profiles" align="start"/> + </wizardpage> + + <wizardpage id="importItems" pageid="importItems" label="&importItems.title;" + next="migrating" + oncommand="MigrationWizard.onImportItemCommand();"> + <description>&importItems.label;</description> + <vbox id="dataSources" + style="overflow: auto; appearance: auto; -moz-default-appearance: listbox" + align="start" flex="1"/> + </wizardpage> + + <wizardpage id="migrating" pageid="migrating" label="&migrating.title;" + next="done"> + <description>&migrating.label;</description> + <separator class="thin"/> + <vbox id="migratingItems" class="indent" style="overflow: auto;" flex="1" align="start"/> + <separator class="thin"/> + <html:progress class="progressmeter-statusbar" id="progressBar" flex="1" value="0" max="100"/> + </wizardpage> + + <wizardpage id="done" pageid="done" label="&done.title;"> + <description>&done.label;</description> + <separator class="thin"/> + <vbox id="doneItems" class="indent" style="overflow: auto;" align="start"/> + </wizardpage> + + <wizardpage id="failed" pageid="failed" data-l10n-id="wizardpage-failed"> + <description id="failed-message-default" + data-l10n-id="wizardpage-failed-message"></description> + <description id="failed-message"></description> + </wizardpage> + </wizard> +</window> diff --git a/comm/mail/components/migration/jar.mn b/comm/mail/components/migration/jar.mn new file mode 100644 index 0000000000..db93c8847e --- /dev/null +++ b/comm/mail/components/migration/jar.mn @@ -0,0 +1,7 @@ +# 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/. + +messenger.jar: +* content/messenger/migration/migration.xhtml (content/migration.xhtml) + content/messenger/migration/migration.js (content/migration.js) diff --git a/comm/mail/components/migration/moz.build b/comm/mail/components/migration/moz.build new file mode 100644 index 0000000000..110c0645dd --- /dev/null +++ b/comm/mail/components/migration/moz.build @@ -0,0 +1,11 @@ +# 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/. + +DIRS += [ + "public", + "src", +] + +JAR_MANIFESTS += ["jar.mn"] diff --git a/comm/mail/components/migration/public/moz.build b/comm/mail/components/migration/public/moz.build new file mode 100644 index 0000000000..20c4c0e5c5 --- /dev/null +++ b/comm/mail/components/migration/public/moz.build @@ -0,0 +1,12 @@ +# 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/. + +XPIDL_SOURCES += [ + "nsIMailProfileMigrator.idl", +] + +XPIDL_MODULE = "mailprofilemigration" + +EXPORTS += [] diff --git a/comm/mail/components/migration/public/nsIMailProfileMigrator.idl b/comm/mail/components/migration/public/nsIMailProfileMigrator.idl new file mode 100644 index 0000000000..398857b459 --- /dev/null +++ b/comm/mail/components/migration/public/nsIMailProfileMigrator.idl @@ -0,0 +1,70 @@ +/* -*- Mode: C++; 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/. */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIProfileStartup; + +[scriptable, uuid(fca38a7a-c43f-4b28-adbd-61e5cc942508)] +interface nsIMailProfileMigrator : nsISupports +{ + /** + * profile items to migrate. use with migrate(). + */ + const unsigned short ALL = 0x0000; + const unsigned short SETTINGS = 0x0001; + const unsigned short ACCOUNT_SETTINGS = 0x0002; + const unsigned short ADDRESSBOOK_DATA = 0x0004; + const unsigned short JUNKTRAINING = 0x0008; + const unsigned short PASSWORDS = 0x0010; + const unsigned short OTHERDATA = 0x0020; + const unsigned short NEWSDATA = 0x0040; + const unsigned short MAILDATA = 0x0080; + const unsigned short FILTERS = 0x0100; + + /** + * Copy user profile information to the current active profile. + * @param aItems list of data items to migrate. see above for values. + * @param aReplace replace or append current data where applicable. + * @param aProfile profile to migrate from, if there is more than one. + */ + void migrate(in unsigned short aItems, in nsIProfileStartup aStartup, in wstring aProfile); + + /** + * A bit field containing profile items that this migrator + * offers for import. + * @param aProfile the profile that we are looking for available data + * to import + * @param aStarting "true" if the profile is not currently being used. + * @returns bit field containing profile items (see above) + */ + unsigned short getMigrateData(in wstring aProfile, in boolean aDoingStartup); + + /** + * Whether or not there is any data that can be imported from this + * mailer (i.e. whether or not it is installed, and there exists + * a user profile) + */ + readonly attribute boolean sourceExists; + + /** + * Whether or not the import source implementing this interface + * has multiple user profiles configured. + */ + readonly attribute boolean sourceHasMultipleProfiles; + + /** + * An array of available profile names. If the import source does not support + * profiles, this attribute is empty. + */ + readonly attribute Array<AString> sourceProfiles; + + /** + * An array of available profile locations. If the import source does not + * support profiles, this attribute is empty. + */ + readonly attribute Array<nsIFile> sourceProfileLocations; +}; diff --git a/comm/mail/components/migration/src/ThunderbirdProfileMigrator.jsm b/comm/mail/components/migration/src/ThunderbirdProfileMigrator.jsm new file mode 100644 index 0000000000..6827ea5523 --- /dev/null +++ b/comm/mail/components/migration/src/ThunderbirdProfileMigrator.jsm @@ -0,0 +1,869 @@ +/* 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 = ["ThunderbirdProfileMigrator"]; + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const lazy = {}; +XPCOMUtils.defineLazyGetter( + lazy, + "l10n", + () => new Localization(["messenger/importDialog.ftl"]) +); + +// Pref branches that need special handling. +const MAIL_IDENTITY = "mail.identity."; +const MAIL_SERVER = "mail.server."; +const MAIL_ACCOUNT = "mail.account."; +const IM_ACCOUNT = "messenger.account."; +const SMTP_SERVER = "mail.smtpserver."; +const ADDRESS_BOOK = "ldap_2.servers."; +const LDAP_AUTO_COMPLETE = "ldap_2.autoComplete."; +const CALENDAR = "calendar.registry."; + +// Prefs (branches) that we do not want to copy directly. +const IGNORE_PREFS = [ + "app.update.", + "browser.", + "calendar.list.sortOrder", + "calendar.timezone", + "devtools.", + "extensions.", + "mail.accountmanager.", + "mail.cloud_files.accounts.", + "mail.newsrc_root", + "mail.root.", + "mail.smtpservers", + "messenger.accounts", + "print.", + "services.", + "toolkit.telemetry.", +]; + +// When importing from a zip file, ignoring these folders. +const IGNORE_DIRS = [ + "chrome_debugger_profile", + "crashes", + "datareporting", + "extensions", + "extension-store", + "logs", + "minidumps", + "saved-telemetry-pings", + "security_state", + "storage", + "xulstore", +]; + +/** + * A pref is represented as [type, name, value]. + * + * @typedef {["Bool"|"Char"|"Int", string, number|string|boolean]} PrefItem + * + * A map from source smtp server key to target smtp server key. + * @typedef {Map<string, string>} SmtpServerKeyMap + * + * A map from source identity key to target identity key. + * @typedef {Map<string, string>} IdentityKeyMap + * + * A map from source IM account key to target IM account key. + * @typedef {Map<string, string>} IMAccountKeyMap + * + * A map from source incoming server key to target incoming server key. + * @typedef {Map<string, string>} IncomingServerKeyMap + */ + +/** + * A class to support importing from a Thunderbird profile directory. + * + * @implements {nsIMailProfileMigrator} + */ +class ThunderbirdProfileMigrator { + QueryInterface = ChromeUtils.generateQI(["nsIMailProfileMigrator"]); + + get wrappedJSObject() { + return this; + } + + _logger = console.createInstance({ + prefix: "mail.import", + maxLogLevel: "Warn", + maxLogLevelPref: "mail.import.loglevel", + }); + + get sourceExists() { + return true; + } + + get sourceProfiles() { + return this._sourceProfileDir ? [this._sourceProfileDir.path] : []; + } + + get sourceHasMultipleProfiles() { + return false; + } + + /** + * Other profile migrators try known install directories to get a source + * profile dir. But in this class, we always ask user for the profile + * location. + */ + async getProfileDir(window, type) { + let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance( + Ci.nsIFilePicker + ); + let [filePickerTitleZip, filePickerTitleDir] = await lazy.l10n.formatValues( + ["import-select-profile-zip", "import-select-profile-dir"] + ); + switch (type) { + case "zip": + filePicker.init(window, filePickerTitleZip, filePicker.modeOpen); + filePicker.appendFilter("", "*.zip"); + break; + case "dir": + filePicker.init(window, filePickerTitleDir, filePicker.modeGetFolder); + break; + default: + throw new Error(`Unsupported type: ${type}`); + } + let selectedFile = await new Promise((resolve, reject) => { + filePicker.open(rv => { + if (rv != Ci.nsIFilePicker.returnOK || !filePicker.file) { + reject(new Error("file-picker-cancelled")); + return; + } + resolve(filePicker.file); + }); + }); + if (selectedFile.isDirectory()) { + this._sourceProfileDir = selectedFile; + } else { + if (selectedFile.fileSize > 2147483647) { + // nsIZipReader only supports zip file less than 2GB. + // throw new Error(zipFileTooBigMessage); + throw new Error("zip-file-too-big"); + } + this._importingFromZip = true; + // Extract the zip file to a tmp dir. + let targetDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + targetDir.append("tmp-profile"); + targetDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + let ZipReader = Components.Constructor( + "@mozilla.org/libjar/zip-reader;1", + "nsIZipReader", + "open" + ); + let zip = ZipReader(filePicker.file); + for (let entry of zip.findEntries(null)) { + let parts = entry.split("/"); + if (IGNORE_DIRS.includes(parts[1]) || entry.endsWith("/")) { + continue; + } + // Folders can not be unzipped recursively, have to iterate and + // extract all file entries one by one. + let target = targetDir.clone(); + for (let part of parts.slice(1)) { + // Drop the root folder name in the zip file. + target.append(part); + } + if (!target.parent.exists()) { + target.parent.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + try { + this._logger.debug(`Extracting ${entry} to ${target.path}`); + zip.extract(entry, target); + } catch (e) { + this._logger.error(e); + } + } + // Use the tmp dir as source profile dir. + this._sourceProfileDir = targetDir; + } + } + + getMigrateData() { + return ( + Ci.nsIMailProfileMigrator.ACCOUNT_SETTINGS | + Ci.nsIMailProfileMigrator.MAILDATA | + Ci.nsIMailProfileMigrator.NEWSDATA | + Ci.nsIMailProfileMigrator.ADDRESSBOOK_DATA | + Ci.nsIMailProfileMigrator.SETTINGS + ); + } + + migrate(items, startup, profile) { + throw new Error("migrate not implemented"); + } + + async asyncMigrate() { + Services.obs.notifyObservers(null, "Migration:Started"); + try { + await this._importPreferences(); + } finally { + if (this._importingFromZip) { + IOUtils.remove(this._sourceProfileDir.path, { recursive: true }); + } + } + Services.obs.notifyObservers(null, "Migration:Ended"); + } + + /** + * Collect interested prefs from this._sourceProfileDir, then import them one + * by one. + */ + async _importPreferences() { + // A Map to collect all prefs in interested pref branches. + // @type {Map<string, PrefItem[]>} + let branchPrefsMap = new Map([ + [MAIL_IDENTITY, []], + [MAIL_SERVER, []], + [MAIL_ACCOUNT, []], + [IM_ACCOUNT, []], + [SMTP_SERVER, []], + [ADDRESS_BOOK, []], + [CALENDAR, []], + ]); + let accounts; + let defaultAccount; + let defaultSmtpServer; + let ldapAutoComplete = {}; + let otherPrefs = []; + + let sourcePrefsFile = this._sourceProfileDir.clone(); + sourcePrefsFile.append("prefs.js"); + let sourcePrefsBuffer = await IOUtils.read(sourcePrefsFile.path); + + let savePref = (type, name, value) => { + for (let [branchName, branchPrefs] of branchPrefsMap) { + if (name.startsWith(branchName)) { + branchPrefs.push([type, name.slice(branchName.length), value]); + return; + } + } + if (name == "mail.accountmanager.accounts") { + accounts = value; + return; + } + if (name == "mail.accountmanager.defaultaccount") { + defaultAccount = value; + return; + } + if (name == "mail.smtp.defaultserver") { + defaultSmtpServer = value; + return; + } + if (name.startsWith(LDAP_AUTO_COMPLETE)) { + ldapAutoComplete[name.slice(LDAP_AUTO_COMPLETE.length)] = value; + return; + } + if (IGNORE_PREFS.some(ignore => name.startsWith(ignore))) { + return; + } + // Collect all the other prefs. + otherPrefs.push([type, name, value]); + }; + + Services.prefs.parsePrefsFromBuffer(sourcePrefsBuffer, { + onStringPref: (kind, name, value) => savePref("Char", name, value), + onIntPref: (kind, name, value) => savePref("Int", name, value), + onBoolPref: (kind, name, value) => savePref("Bool", name, value), + onError: msg => { + throw new Error(msg); + }, + }); + + Services.obs.notifyObservers( + null, + "Migration:ItemBeforeMigrate", + Ci.nsIMailProfileMigrator.ACCOUNT_SETTINGS + ); + await new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); + + // Import SMTP servers first, the importing order is important. + let smtpServerKeyMap = this._importSmtpServers( + branchPrefsMap.get(SMTP_SERVER), + defaultSmtpServer + ); + // mail.identity.idN.smtpServer depends on transformed smtp server key. + let identityKeyMap = this._importIdentities( + branchPrefsMap.get(MAIL_IDENTITY), + smtpServerKeyMap + ); + let imAccountKeyMap = await this._importIMAccounts( + branchPrefsMap.get(IM_ACCOUNT) + ); + // mail.server.serverN.imAccount depends on transformed im account key. + let incomingServerKeyMap = await this._importIncomingServers( + branchPrefsMap.get(MAIL_SERVER), + imAccountKeyMap + ); + // mail.account.accountN.{identities, server} depends on previous steps. + this._importAccounts( + branchPrefsMap.get(MAIL_ACCOUNT), + accounts, + defaultAccount, + identityKeyMap, + incomingServerKeyMap + ); + Services.obs.notifyObservers( + null, + "Migration:ItemAfterMigrate", + Ci.nsIMailProfileMigrator.ACCOUNT_SETTINGS + ); + Services.obs.notifyObservers(null, "Migration:Progress", "25"); + Services.obs.notifyObservers( + null, + "Migration:ItemBeforeMigrate", + Ci.nsIMailProfileMigrator.MAILDATA + ); + await new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); + + await this._copyMailFolders(incomingServerKeyMap); + Services.obs.notifyObservers( + null, + "Migration:ItemAfterMigrate", + Ci.nsIMailProfileMigrator.MAILDATA + ); + Services.obs.notifyObservers(null, "Migration:Progress", "50"); + Services.obs.notifyObservers( + null, + "Migration:ItemBeforeMigrate", + Ci.nsIMailProfileMigrator.ADDRESSBOOK_DATA + ); + await new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); + + this._importAddressBooks( + branchPrefsMap.get(ADDRESS_BOOK), + ldapAutoComplete + ); + Services.obs.notifyObservers( + null, + "Migration:ItemAfterMigrate", + Ci.nsIMailProfileMigrator.ADDRESSBOOK_DATA + ); + Services.obs.notifyObservers(null, "Migration:Progress", "75"); + Services.obs.notifyObservers( + null, + "Migration:ItemBeforeMigrate", + Ci.nsIMailProfileMigrator.SETTINGS + ); + await new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); + + this._importPasswords(); + this._importOtherPrefs(otherPrefs); + this._importCalendars(branchPrefsMap.get(CALENDAR)); + Services.obs.notifyObservers( + null, + "Migration:ItemAfterMigrate", + Ci.nsIMailProfileMigrator.SETTINGS + ); + Services.obs.notifyObservers(null, "Migration:Progress", "100"); + } + + /** + * Import SMTP servers. + * + * @param {PrefItem[]} prefs - All source prefs in the SMTP_SERVER branch. + * @param {string} sourceDefaultServer - The value of mail.smtp.defaultserver + * in the source profile. + * @returns {smtpServerKeyMap} A map from source server key to new server key. + */ + _importSmtpServers(prefs, sourceDefaultServer) { + let smtpServerKeyMap = new Map(); + let branch = Services.prefs.getBranch(SMTP_SERVER); + for (let [type, name, value] of prefs) { + let key = name.split(".")[0]; + let newServerKey = smtpServerKeyMap.get(key); + if (!newServerKey) { + // For every smtp server, create a new one to avoid conflicts. + let server = MailServices.smtp.createServer(); + newServerKey = server.key; + smtpServerKeyMap.set(key, newServerKey); + this._logger.debug( + `Mapping SMTP server from ${key} to ${newServerKey}` + ); + } + + let newName = `${newServerKey}${name.slice(key.length)}`; + branch[`set${type}Pref`](newName, value); + } + + // Set defaultserver if it doesn't already exist. + let defaultServer = Services.prefs.getCharPref( + "mail.smtp.defaultserver", + "" + ); + if (sourceDefaultServer && !defaultServer) { + Services.prefs.setCharPref( + "mail.smtp.defaultserver", + smtpServerKeyMap.get(sourceDefaultServer) + ); + } + return smtpServerKeyMap; + } + + /** + * Import mail identites. + * + * @param {PrefItem[]} prefs - All source prefs in the MAIL_IDENTITY branch. + * @param {SmtpServerKeyMap} smtpServerKeyMap - A map from the source SMTP + * server key to new SMTP server key. + * @returns {IdentityKeyMap} A map from the source identity key to new identity + * key. + */ + _importIdentities(prefs, smtpServerKeyMap) { + let identityKeyMap = new Map(); + let branch = Services.prefs.getBranch(MAIL_IDENTITY); + for (let [type, name, value] of prefs) { + let key = name.split(".")[0]; + let newIdentityKey = identityKeyMap.get(key); + if (!newIdentityKey) { + // For every identity, create a new one to avoid conflicts. + let identity = MailServices.accounts.createIdentity(); + newIdentityKey = identity.key; + identityKeyMap.set(key, newIdentityKey); + this._logger.debug(`Mapping identity from ${key} to ${newIdentityKey}`); + } + + let newName = `${newIdentityKey}${name.slice(key.length)}`; + let newValue = value; + if (name.endsWith(".smtpServer")) { + newValue = smtpServerKeyMap.get(value) || newValue; + } + branch[`set${type}Pref`](newName, newValue); + } + return identityKeyMap; + } + + /** + * Import IM accounts. + * + * @param {Array<[string, string, number|string|boolean]>} prefs - All source + * prefs in the IM_ACCOUNT branch. + * @returns {IMAccountKeyMap} A map from the source account key to new account + * key. + */ + async _importIMAccounts(prefs) { + let imAccountKeyMap = new Map(); + let branch = Services.prefs.getBranch(IM_ACCOUNT); + + let lastKey = 1; + function _getUniqueAccountKey() { + let key = `account${lastKey++}`; + if (Services.prefs.getCharPref(`messenger.account.${key}.name`, "")) { + return _getUniqueAccountKey(); + } + return key; + } + + for (let [type, name, value] of prefs) { + let key = name.split(".")[0]; + let newAccountKey = imAccountKeyMap.get(key); + if (!newAccountKey) { + // For every account, create a new one to avoid conflicts. + newAccountKey = _getUniqueAccountKey(); + imAccountKeyMap.set(key, newAccountKey); + this._logger.debug( + `Mapping IM account from ${key} to ${newAccountKey}` + ); + } + + let newName = `${newAccountKey}${name.slice(key.length)}`; + branch[`set${type}Pref`](newName, value); + } + + return imAccountKeyMap; + } + + /** + * Import incoming servers. + * + * @param {PrefItem[]} prefs - All source prefs in the MAIL_SERVER branch. + * @param {IMAccountKeyMap} imAccountKeyMap - A map from the source account + * key to new account key. + * @returns {IncomingServerKeyMap} A map from the source server key to new + * server key. + */ + async _importIncomingServers(prefs, imAccountKeyMap) { + let incomingServerKeyMap = new Map(); + let branch = Services.prefs.getBranch(MAIL_SERVER); + + let lastKey = 1; + function _getUniqueIncomingServerKey() { + let key = `server${lastKey++}`; + if (branch.getCharPref(`${key}.type`, "")) { + return _getUniqueIncomingServerKey(); + } + return key; + } + + for (let [type, name, value] of prefs) { + let key = name.split(".")[0]; + let newServerKey = incomingServerKeyMap.get(key); + if (!newServerKey) { + // For every incoming server, create a new one to avoid conflicts. + newServerKey = _getUniqueIncomingServerKey(); + incomingServerKeyMap.set(key, newServerKey); + this._logger.debug(`Mapping server from ${key} to ${newServerKey}`); + } + + let newName = `${newServerKey}${name.slice(key.length)}`; + let newValue = value; + if (newName.endsWith(".imAccount")) { + newValue = imAccountKeyMap.get(value); + } + branch[`set${type}Pref`](newName, newValue || value); + } + return incomingServerKeyMap; + } + + /** + * Copy mail folders from this._sourceProfileDir to the current profile dir. + * + * @param {PrefKeyMap} incomingServerKeyMap - A map from the source server key + * to new server key. + */ + async _copyMailFolders(incomingServerKeyMap) { + for (let key of incomingServerKeyMap.values()) { + let branch = Services.prefs.getBranch(`${MAIL_SERVER}${key}.`); + if (!branch) { + continue; + } + let type = branch.getCharPref("type", ""); + let hostname = branch.getCharPref("hostname", ""); + if (!type || !hostname) { + continue; + } + + // Use .directory-rel instead of .directory because .directory is an + // absolute path which may not exists. + let directoryRel = branch.getCharPref("directory-rel", ""); + if (!directoryRel.startsWith("[ProfD]")) { + continue; + } + directoryRel = directoryRel.slice("[ProfD]".length); + + let targetDir = Services.dirsvc.get("ProfD", Ci.nsIFile); + if (type == "imap") { + targetDir.append("ImapMail"); + } else if (type == "nntp") { + targetDir.append("News"); + } else if (["none", "pop3", "rss"].includes(type)) { + targetDir.append("Mail"); + } else { + continue; + } + + // Use the hostname as mail folder name and ensure it's unique. + targetDir.append(hostname); + targetDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + // Remove the folder so that nsIFile.copyTo doesn't copy into targetDir. + targetDir.remove(false); + + let sourceDir = this._sourceProfileDir.clone(); + for (let part of directoryRel.split("/")) { + sourceDir.append(part); + } + if ( + sourceDir.exists() && + (type != "imap" || Services.appinfo.OS != "WINNT") + ) { + // For some reasons, if mail folders are copied on Windows, + // `errorGettingDB` is thrown after imported and restarted. IMAP folders + // will be downloaded automatically, better than a broken account. + this._logger.debug(`Copying ${sourceDir.path} to ${targetDir.path}`); + sourceDir.copyTo(targetDir.parent, targetDir.leafName); + } + branch.setCharPref("directory", targetDir.path); + // .directory-rel may be outdated, it will be created when first needed. + branch.clearUserPref("directory-rel"); + + if (type == "nntp") { + // Use .file-rel instead of .file because .file is an absolute path + // which may not exists. + let fileRel = branch.getCharPref("newsrc.file-rel", ""); + if (!fileRel.startsWith("[ProfD]")) { + continue; + } + fileRel = fileRel.slice("[ProfD]".length); + let sourceNewsrc = this._sourceProfileDir.clone(); + for (let part of fileRel.split("/")) { + sourceNewsrc.append(part); + } + let targetNewsrc = Services.dirsvc.get("ProfD", Ci.nsIFile); + targetNewsrc.append("News"); + targetNewsrc.append(`newsrc-${hostname}`); + targetNewsrc.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + this._logger.debug( + `Copying ${sourceNewsrc.path} to ${targetNewsrc.path}` + ); + sourceNewsrc.copyTo(targetNewsrc.parent, targetNewsrc.leafName); + branch.setCharPref("newsrc.file", targetNewsrc.path); + // .file-rel may be outdated, it will be created when first needed. + branch.clearUserPref("newsrc.file-rel"); + } + } + } + + /** + * Import mail accounts. + * + * @param {PrefItem[]} prefs - All source prefs in the MAIL_ACCOUNT branch. + * @param {string} sourceAccounts - The value of mail.accountmanager.accounts + * in the source profile. + * @param {string} sourceDefaultAccount - The value of + * mail.accountmanager.defaultaccount in the source profile. + * @param {IdentityKeyMap} identityKeyMap - A map from the source identity key + * to new identity key. + * @param {IncomingServerKeyMap} incomingServerKeyMap - A map from the source + * server key to new server key. + */ + _importAccounts( + prefs, + sourceAccounts, + sourceDefaultAccount, + identityKeyMap, + incomingServerKeyMap + ) { + let accountKeyMap = new Map(); + let branch = Services.prefs.getBranch(MAIL_ACCOUNT); + for (let [type, name, value] of prefs) { + let key = name.split(".")[0]; + if (key == "lastKey") { + continue; + } + let newAccountKey = accountKeyMap.get(key); + if (!newAccountKey) { + // For every account, create a new one to avoid conflicts. + newAccountKey = MailServices.accounts.getUniqueAccountKey(); + accountKeyMap.set(key, newAccountKey); + } + + let newName = `${newAccountKey}${name.slice(key.length)}`; + let newValue = value; + if (name.endsWith(".identities")) { + newValue = identityKeyMap.get(value); + } else if (name.endsWith(".server")) { + newValue = incomingServerKeyMap.get(value); + } + branch[`set${type}Pref`](newName, newValue || value); + } + + // Append newly create accounts to mail.accountmanager.accounts. + let accounts = Services.prefs + .getCharPref("mail.accountmanager.accounts", "") + .split(","); + if (accounts.length == 1 && accounts[0] == "") { + accounts.length = 0; + } + if (sourceAccounts) { + for (let sourceAccountKey of sourceAccounts.split(",")) { + accounts.push(accountKeyMap.get(sourceAccountKey)); + } + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + accounts.join(",") + ); + } + + // Set defaultaccount if it doesn't already exist. + let defaultAccount = Services.prefs.getCharPref( + "mail.accountmanager.defaultaccount", + "" + ); + if (sourceDefaultAccount && !defaultAccount) { + Services.prefs.setCharPref( + "mail.accountmanager.defaultaccount", + accountKeyMap.get(sourceDefaultAccount) + ); + } + } + + /** + * Import address books. + * + * @param {PrefItem[]} prefs - All source prefs in the ADDRESS_BOOK branch. + * @param {object} ldapAutoComplete - Pref values of LDAP_AUTO_COMPLETE branch. + * @param {boolean} ldapAutoComplete.useDirectory + * @param {string} ldapAutoComplete.directoryServer + */ + _importAddressBooks(prefs, ldapAutoComplete) { + let keyMap = new Map(); + let branch = Services.prefs.getBranch(ADDRESS_BOOK); + for (let [type, name, value] of prefs) { + let key = name.split(".")[0]; + if (["pab", "history"].includes(key)) { + continue; + } + let newKey = keyMap.get(key); + if (!newKey) { + // For every address book, create a new one to avoid conflicts. + let uniqueCount = 0; + newKey = key; + while (true) { + if (!branch.getCharPref(`${newKey}.filename`, "")) { + break; + } + newKey = `${key}${++uniqueCount}`; + } + keyMap.set(key, newKey); + } + + let newName = `${newKey}${name.slice(key.length)}`; + branch[`set${type}Pref`](newName, value); + } + + // Transform the value of ldap_2.autoComplete.directoryServer if needed. + if ( + ldapAutoComplete.useDirectory && + ldapAutoComplete.directoryServer && + !Services.prefs.getBoolPref(`${LDAP_AUTO_COMPLETE}useDirectory`, false) + ) { + let key = ldapAutoComplete.directoryServer.split("/").slice(-1)[0]; + let newKey = keyMap.get(key); + if (newKey) { + Services.prefs.setBoolPref(`${LDAP_AUTO_COMPLETE}useDirectory`, true); + Services.prefs.setCharPref( + `${LDAP_AUTO_COMPLETE}directoryServer`, + `ldap_2.servers.${newKey}` + ); + } + } + + this._copyAddressBookDatabases(keyMap); + } + + /** + * Copy sqlite files from this._sourceProfileDir to the current profile dir. + * + * @param {Map<string, string>} keyMap - A map from the source address + * book key to new address book key. + */ + _copyAddressBookDatabases(keyMap) { + // Copy user created address books. + for (let key of keyMap.values()) { + let branch = Services.prefs.getBranch(`${ADDRESS_BOOK}${key}.`); + let filename = branch.getCharPref("filename", ""); + if (!filename) { + continue; + } + let sourceFile = this._sourceProfileDir.clone(); + sourceFile.append(filename); + if (!sourceFile.exists()) { + this._logger.debug( + `Ignoring non-existing address boook file ${sourceFile.path}` + ); + continue; + } + + let targetFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + targetFile.append(sourceFile.leafName); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + this._logger.debug(`Copying ${sourceFile.path} to ${targetFile.path}`); + sourceFile.copyTo(targetFile.parent, targetFile.leafName); + + branch.setCharPref("filename", targetFile.leafName); + } + + // Copy or import Personal Address Book. + this._importAddressBookDatabase("abook.sqlite"); + // Copy or import Collected Addresses. + this._importAddressBookDatabase("history.sqlite"); + } + + /** + * Copy a sqlite file from this._sourceProfileDir to the current profile dir. + * + * @param {string} filename - The name of the sqlite file. + */ + _importAddressBookDatabase(filename) { + let sourceFile = this._sourceProfileDir.clone(); + sourceFile.append(filename); + let targetFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + targetFile.append(filename); + + if (!sourceFile.exists()) { + return; + } + + if (!targetFile.exists()) { + sourceFile.copyTo(targetFile.parent, ""); + return; + } + + let dirId = MailServices.ab.newAddressBook( + "tmp", + "", + Ci.nsIAbManager.JS_DIRECTORY_TYPE + ); + let tmpDirectory = MailServices.ab.getDirectoryFromId(dirId); + sourceFile.copyTo(targetFile.parent, tmpDirectory.fileName); + + let targetDirectory = MailServices.ab.getDirectory( + `jsaddrbook://${filename}` + ); + for (let card of tmpDirectory.childCards) { + targetDirectory.addCard(card); + } + + MailServices.ab.deleteAddressBook(tmpDirectory.URI); + } + + /** + * Import logins.json and key4.db. + */ + _importPasswords() { + let sourceLoginsJson = this._sourceProfileDir.clone(); + sourceLoginsJson.append("logins.json"); + let sourceKeyDb = this._sourceProfileDir.clone(); + sourceKeyDb.append("key4.db"); + let targetLoginsJson = Services.dirsvc.get("ProfD", Ci.nsIFile); + targetLoginsJson.append("logins.json"); + + if ( + sourceLoginsJson.exists() && + sourceKeyDb.exists() && + !targetLoginsJson.exists() + ) { + // Only copy if logins.json doesn't exist in the current profile. + sourceLoginsJson.copyTo(targetLoginsJson.parent, ""); + sourceKeyDb.copyTo(targetLoginsJson.parent, ""); + } + } + + /** + * Import a pref from source only when this pref has no user value in the + * current profile. + * + * @param {PrefItem[]} prefs - All source prefs to try to import. + */ + _importOtherPrefs(prefs) { + for (let [type, name, value] of prefs) { + if (!Services.prefs.prefHasUserValue(name)) { + Services.prefs[`set${type}Pref`](name, value); + } + } + } + + /** + * Import calendars. + * + * For storage calendars, we need to import everything from the source + * local.sqlite to the target local.sqlite, which is not implemented yet, see + * bug 1719582. + * + * @param {PrefItem[]} prefs - All source prefs in the CALENDAR branch. + */ + _importCalendars(prefs) { + let branch = Services.prefs.getBranch(CALENDAR); + for (let [type, name, value] of prefs) { + branch[`set${type}Pref`](name, value); + } + } +} diff --git a/comm/mail/components/migration/src/components.conf b/comm/mail/components/migration/src/components.conf new file mode 100644 index 0000000000..85b754c645 --- /dev/null +++ b/comm/mail/components/migration/src/components.conf @@ -0,0 +1,38 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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/. + +Classes = [ + { + "cid": "{adb2e3a7-8df4-484a-b787-6c2184eb9756}", + "contract_ids": ["@mozilla.org/profile/migrator;1?app=mail&type=thunderbird"], + "jsm": "resource:///modules/ThunderbirdProfileMigrator.jsm", + "constructor": "ThunderbirdProfileMigrator", + }, + { + "cid": "{b3c78baf-3a52-41d2-9718-c319bef9affc}", + "contract_ids": ["@mozilla.org/toolkit/profile-migrator;1"], + "type": "nsProfileMigrator", + "headers": ["/comm/mail/components/migration/src/nsProfileMigrator.h"], + }, + { + "cid": "{62c6e1f9-3dc3-4b68-9c39-ad2f6d471ac0}", + "contract_ids": ["@mozilla.org/profile/migrator;1?app=mail&type=seamonkey"], + "type": "nsSeamonkeyProfileMigrator", + "headers": ["/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h"], + }, +] + +if buildconfig.substs["OS_ARCH"] == "WINNT": + Classes += [ + { + "cid": "{910b6453-0719-41e8-a4c9-0319bb34c8ff}", + "contract_ids": ["@mozilla.org/profile/migrator;1?app=mail&type=outlook"], + "type": "nsOutlookProfileMigrator", + "headers": [ + "/comm/mail/components/migration/src/nsOutlookProfileMigrator.h" + ], + }, + ] diff --git a/comm/mail/components/migration/src/moz.build b/comm/mail/components/migration/src/moz.build new file mode 100644 index 0000000000..cfcc8d8239 --- /dev/null +++ b/comm/mail/components/migration/src/moz.build @@ -0,0 +1,32 @@ +# 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/. + +SOURCES += [ + "nsMailProfileMigratorUtils.cpp", + "nsNetscapeProfileMigratorBase.cpp", + "nsProfileMigrator.cpp", + "nsSeamonkeyProfileMigrator.cpp", +] + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "nsOutlookProfileMigrator.cpp", + "nsProfileMigratorBase.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "nsProfileMigratorBase.cpp", + ] + +FINAL_LIBRARY = "mailcomps" + +EXTRA_JS_MODULES += [ + "ThunderbirdProfileMigrator.jsm", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] diff --git a/comm/mail/components/migration/src/nsMailProfileMigratorUtils.cpp b/comm/mail/components/migration/src/nsMailProfileMigratorUtils.cpp new file mode 100644 index 0000000000..795cd514f4 --- /dev/null +++ b/comm/mail/components/migration/src/nsMailProfileMigratorUtils.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; 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/. */ + +#include "nsMailProfileMigratorUtils.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsIProfileMigrator.h" + +#include "nsServiceManagerUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsXPCOMCID.h" + +void SetProxyPref(const nsACString& aHostPort, const char* aPref, + const char* aPortPref, nsIPrefBranch* aPrefs) { + nsAutoCString hostPort(aHostPort); + int32_t portDelimOffset = hostPort.RFindChar(':'); + if (portDelimOffset > 0) { + nsAutoCString host(Substring(hostPort, 0, portDelimOffset)); + nsAutoCString port(Substring(hostPort, portDelimOffset + 1, + hostPort.Length() - (portDelimOffset + 1))); + + aPrefs->SetCharPref(aPref, host); + nsresult stringErr; + int32_t portValue = port.ToInteger(&stringErr); + aPrefs->SetIntPref(aPortPref, portValue); + } else + aPrefs->SetCharPref(aPref, hostPort); +} + +void ParseOverrideServers(const char* aServers, nsIPrefBranch* aBranch) { + // Windows (and Opera) formats its proxy override list in the form: + // server;server;server where server is a server name or ip address, + // or "<local>". Mozilla's format is server,server,server, and <local> + // must be translated to "localhost,127.0.0.1" + nsAutoCString override(aServers); + int32_t left = 0, right = 0; + for (;;) { + right = override.FindChar(';', right); + const nsACString& host = Substring( + override, left, (right < 0 ? override.Length() : right) - left); + if (host.Equals("<local>")) + override.Replace(left, 7, "localhost,127.0.0.1"_ns); + if (right < 0) break; + left = right + 1; + override.Replace(right, 1, ","_ns); + } + aBranch->SetCharPref("network.proxy.no_proxies_on", override); +} + +void GetMigrateDataFromArray(MigrationData* aDataArray, + int32_t aDataArrayLength, bool aReplace, + nsIFile* aSourceProfile, uint16_t* aResult) { + nsCOMPtr<nsIFile> sourceFile; + bool exists; + MigrationData* cursor; + MigrationData* end = aDataArray + aDataArrayLength; + for (cursor = aDataArray; cursor < end && cursor->fileName; ++cursor) { + // When in replace mode, all items can be imported. + // When in non-replace mode, only items that do not require file replacement + // can be imported. + if (aReplace || !cursor->replaceOnly) { + aSourceProfile->Clone(getter_AddRefs(sourceFile)); + sourceFile->Append(nsDependentString(cursor->fileName)); + sourceFile->Exists(&exists); + if (exists) *aResult |= cursor->sourceFlag; + } + free(cursor->fileName); + cursor->fileName = nullptr; + } +} + +void GetProfilePath(nsIProfileStartup* aStartup, + nsCOMPtr<nsIFile>& aProfileDir) { + if (aStartup) { + aStartup->GetDirectory(getter_AddRefs(aProfileDir)); + } else { + nsCOMPtr<nsIProperties> dirSvc( + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (dirSvc) { + dirSvc->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(aProfileDir)); + } + } +} diff --git a/comm/mail/components/migration/src/nsMailProfileMigratorUtils.h b/comm/mail/components/migration/src/nsMailProfileMigratorUtils.h new file mode 100644 index 0000000000..01f21d0d72 --- /dev/null +++ b/comm/mail/components/migration/src/nsMailProfileMigratorUtils.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef mailprofilemigratorutils___h___ +#define mailprofilemigratorutils___h___ + +#define MIGRATION_ITEMBEFOREMIGRATE "Migration:ItemBeforeMigrate" +#define MIGRATION_ITEMAFTERMIGRATE "Migration:ItemAfterMigrate" +#define MIGRATION_STARTED "Migration:Started" +#define MIGRATION_ENDED "Migration:Ended" +#define MIGRATION_PROGRESS "Migration:Progress" + +#define NOTIFY_OBSERVERS(message, item) \ + mObserverService->NotifyObservers(nullptr, message, item) + +#define COPY_DATA(func, replace, itemIndex) \ + if (NS_SUCCEEDED(rv) && (aItems & itemIndex || !aItems)) { \ + nsAutoString index; \ + index.AppendInt(itemIndex); \ + NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get()); \ + rv = func(replace); \ + NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get()); \ + } + +#include "nsIPrefBranch.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsCOMPtr.h" +class nsIProfileStartup; + +// Proxy utilities shared by the Opera and IE migrators +void ParseOverrideServers(const char* aServers, nsIPrefBranch* aBranch); +void SetProxyPref(const nsACString& aHostPort, const char* aPref, + const char* aPortPref, nsIPrefBranch* aPrefs); + +struct MigrationData { + char16_t* fileName; + uint32_t sourceFlag; + bool replaceOnly; +}; + +class nsIFile; +void GetMigrateDataFromArray(MigrationData* aDataArray, + int32_t aDataArrayLength, bool aReplace, + nsIFile* aSourceProfile, uint16_t* aResult); + +// get the base directory of the *target* profile +// this is already cloned, modify it to your heart's content +void GetProfilePath(nsIProfileStartup* aStartup, + nsCOMPtr<nsIFile>& aProfileDir); + +#endif diff --git a/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.cpp b/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.cpp new file mode 100644 index 0000000000..b4a7affe03 --- /dev/null +++ b/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; 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/. */ + +#include "nsAppDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsILineInputStream.h" +#include "nsIPrefBranch.h" +#include "nsIPrefLocalizedString.h" +#include "nsIPrefService.h" +#include "nsIServiceManager.h" +#include "nsIURL.h" +#include "nsNetscapeProfileMigratorBase.h" +#include "nsNetUtil.h" +#include "prtime.h" +#include "prprf.h" +#include "nsITimer.h" +#include "nsINIParser.h" +#include "nsMailProfileMigratorUtils.h" +#include "nsIDirectoryEnumerator.h" +#include "nsServiceManagerUtils.h" + +#define MIGRATION_BUNDLE \ + "chrome://messenger/locale/migration/migration.properties" + +#define FILE_NAME_PREFS_5X u"prefs.js"_ns + +/////////////////////////////////////////////////////////////////////////////// +// nsNetscapeProfileMigratorBase +nsNetscapeProfileMigratorBase::nsNetscapeProfileMigratorBase() { + mObserverService = do_GetService("@mozilla.org/observer-service;1"); + mMaxProgress = 0; + mCurrentProgress = 0; + mFileCopyTransactionIndex = 0; +} + +NS_IMPL_ISUPPORTS(nsNetscapeProfileMigratorBase, nsIMailProfileMigrator, + nsITimerCallback) + +nsresult nsNetscapeProfileMigratorBase::GetProfileDataFromProfilesIni( + nsIFile* aDataDir, nsTArray<nsString>& aProfileNames, + nsTArray<RefPtr<nsIFile>>& aProfileLocations) { + nsCOMPtr<nsIFile> profileIni; + nsresult rv = aDataDir->Clone(getter_AddRefs(profileIni)); + NS_ENSURE_SUCCESS(rv, rv); + + profileIni->Append(u"profiles.ini"_ns); + + // Does it exist? + bool profileFileExists = false; + rv = profileIni->Exists(&profileFileExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!profileFileExists) return NS_ERROR_FILE_NOT_FOUND; + + nsINIParser parser; + rv = parser.Init(profileIni); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString buffer, filePath; + bool isRelative; + + // This is an infinite loop that is broken when we no longer find profiles + // for profileID with IsRelative option. + for (unsigned int c = 0; true; ++c) { + nsAutoCString profileID("Profile"); + profileID.AppendInt(c); + + if (NS_FAILED(parser.GetString(profileID.get(), "IsRelative", buffer))) + break; + + isRelative = buffer.EqualsLiteral("1"); + + rv = parser.GetString(profileID.get(), "Path", filePath); + if (NS_FAILED(rv)) { + NS_ERROR("Malformed profiles.ini: Path= not found"); + continue; + } + + rv = parser.GetString(profileID.get(), "Name", buffer); + if (NS_FAILED(rv)) { + NS_ERROR("Malformed profiles.ini: Name= not found"); + continue; + } + + nsCOMPtr<nsIFile> rootDir; + rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(rootDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = isRelative ? rootDir->SetRelativeDescriptor(aDataDir, filePath) + : rootDir->SetPersistentDescriptor(filePath); + if (NS_FAILED(rv)) continue; + + bool exists = false; + rootDir->Exists(&exists); + + if (exists) { + aProfileLocations.AppendElement(rootDir); + aProfileNames.AppendElement(NS_ConvertUTF8toUTF16(buffer)); + } + } + return NS_OK; +} + +#define GETPREF(xform, method, value) \ + nsresult rv = aBranch->method(xform->sourcePrefName, value); \ + if (NS_SUCCEEDED(rv)) xform->prefHasValue = true; \ + return rv; + +#define SETPREF(xform, method, value) \ + if (xform->prefHasValue) { \ + return aBranch->method( \ + xform->targetPrefName ? xform->targetPrefName : xform->sourcePrefName, \ + value); \ + } \ + return NS_OK; + +nsresult nsNetscapeProfileMigratorBase::GetString(PrefTransform* aTransform, + nsIPrefBranch* aBranch) { + PrefTransform* xform = (PrefTransform*)aTransform; + nsCString str; + nsresult rv = aBranch->GetCharPref(xform->sourcePrefName, str); + if (NS_SUCCEEDED(rv)) { + xform->prefHasValue = true; + xform->stringValue = moz_xstrdup(str.get()); + } + return rv; +} + +nsresult nsNetscapeProfileMigratorBase::SetString(PrefTransform* aTransform, + nsIPrefBranch* aBranch) { + PrefTransform* xform = (PrefTransform*)aTransform; + SETPREF(xform, SetCharPref, nsDependentCString(xform->stringValue)); +} + +nsresult nsNetscapeProfileMigratorBase::GetBool(PrefTransform* aTransform, + nsIPrefBranch* aBranch) { + PrefTransform* xform = (PrefTransform*)aTransform; + GETPREF(xform, GetBoolPref, &xform->boolValue); +} + +nsresult nsNetscapeProfileMigratorBase::SetBool(PrefTransform* aTransform, + nsIPrefBranch* aBranch) { + PrefTransform* xform = (PrefTransform*)aTransform; + SETPREF(xform, SetBoolPref, xform->boolValue); +} + +nsresult nsNetscapeProfileMigratorBase::GetInt(PrefTransform* aTransform, + nsIPrefBranch* aBranch) { + PrefTransform* xform = (PrefTransform*)aTransform; + GETPREF(xform, GetIntPref, &xform->intValue); +} + +nsresult nsNetscapeProfileMigratorBase::SetInt(PrefTransform* aTransform, + nsIPrefBranch* aBranch) { + PrefTransform* xform = (PrefTransform*)aTransform; + SETPREF(xform, SetIntPref, xform->intValue); +} + +nsresult nsNetscapeProfileMigratorBase::CopyFile( + const nsAString& aSourceFileName, const nsAString& aTargetFileName) { + nsCOMPtr<nsIFile> sourceFile; + mSourceProfile->Clone(getter_AddRefs(sourceFile)); + + sourceFile->Append(aSourceFileName); + bool exists = false; + sourceFile->Exists(&exists); + if (!exists) return NS_OK; + + nsCOMPtr<nsIFile> targetFile; + mTargetProfile->Clone(getter_AddRefs(targetFile)); + + targetFile->Append(aTargetFileName); + targetFile->Exists(&exists); + if (exists) targetFile->Remove(false); + + return sourceFile->CopyTo(mTargetProfile, aTargetFileName); +} + +nsresult nsNetscapeProfileMigratorBase::GetSignonFileName( + bool aReplace, nsACString& aFileName) { + nsresult rv; + if (aReplace) { + // Find out what the signons file was called, this is stored in a pref + // in Seamonkey. + nsCOMPtr<nsIPrefService> psvc(do_GetService(NS_PREFSERVICE_CONTRACTID)); + psvc->ResetPrefs(); + + nsCOMPtr<nsIFile> sourcePrefsName; + mSourceProfile->Clone(getter_AddRefs(sourcePrefsName)); + sourcePrefsName->Append(FILE_NAME_PREFS_5X); + psvc->ReadUserPrefsFromFile(sourcePrefsName); + + nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(psvc)); + rv = branch->GetCharPref("signon.SignonFileName", aFileName); + } else + rv = LocateSignonsFile(aFileName); + return rv; +} + +nsresult nsNetscapeProfileMigratorBase::LocateSignonsFile(nsACString& aResult) { + nsCOMPtr<nsIDirectoryEnumerator> entries; + nsresult rv = mSourceProfile->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_FAILED(rv)) return rv; + + nsAutoCString fileName; + bool hasMore = false; + while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsIFile> currFile; + rv = entries->GetNextFile(getter_AddRefs(currFile)); + if (NS_FAILED(rv)) break; + + nsCOMPtr<nsIURI> uri; + rv = NS_NewFileURI(getter_AddRefs(uri), currFile); + if (NS_FAILED(rv)) break; + nsCOMPtr<nsIURL> url(do_QueryInterface(uri)); + + nsAutoCString extn; + url->GetFileExtension(extn); + + if (extn.EqualsIgnoreCase("s")) { + url->GetFileName(fileName); + break; + } + } + + aResult = fileName; + + return NS_OK; +} + +// helper function, copies the contents of srcDir into destDir. +// destDir will be created if it doesn't exist. + +nsresult nsNetscapeProfileMigratorBase::RecursiveCopy(nsIFile* srcDir, + nsIFile* destDir) { + nsresult rv; + bool isDir; + + rv = srcDir->IsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + if (!isDir) return NS_ERROR_INVALID_ARG; + + bool exists; + rv = destDir->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIDirectoryEnumerator> dirIterator; + rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); + if (NS_FAILED(rv)) return rv; + + bool hasMore = false; + while (NS_SUCCEEDED(dirIterator->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsIFile> dirEntry; + rv = dirIterator->GetNextFile(getter_AddRefs(dirEntry)); + if (NS_SUCCEEDED(rv) && dirEntry) { + rv = dirEntry->IsDirectory(&isDir); + if (NS_SUCCEEDED(rv)) { + if (isDir) { + nsCOMPtr<nsIFile> newChild; + rv = destDir->Clone(getter_AddRefs(newChild)); + if (NS_SUCCEEDED(rv)) { + nsAutoString leafName; + dirEntry->GetLeafName(leafName); + newChild->AppendRelativePath(leafName); + rv = newChild->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) { + rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775); + if (NS_FAILED(rv)) return rv; + } + rv = RecursiveCopy(dirEntry, newChild); + } + } else { + // we aren't going to do any actual file copying here. Instead, add + // this to our file transaction list so we can copy files + // asynchronously... + fileTransactionEntry fileEntry; + fileEntry.srcFile = dirEntry; + fileEntry.destFile = destDir; + + mFileCopyTransactions.AppendElement(fileEntry); + } + } + } + } + + return rv; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsITimerCallback + +NS_IMETHODIMP +nsNetscapeProfileMigratorBase::Notify(nsITimer* timer) { + CopyNextFolder(); + return NS_OK; +} + +NS_IMETHODIMP +nsNetscapeProfileMigratorBase::GetName(nsACString& aName) { + aName.AssignLiteral("nsNetscapeProfileMigratorBase"); + return NS_OK; +} + +void nsNetscapeProfileMigratorBase::CopyNextFolder() { + if (mFileCopyTransactionIndex < mFileCopyTransactions.Length()) { + fileTransactionEntry fileTransaction = + mFileCopyTransactions.ElementAt(mFileCopyTransactionIndex++); + + // copy the file + fileTransaction.srcFile->CopyTo(fileTransaction.destFile, + fileTransaction.newName); + + // add to our current progress + int64_t fileSize; + fileTransaction.srcFile->GetFileSize(&fileSize); + mCurrentProgress += fileSize; + + uint32_t percentage = (uint32_t)(mCurrentProgress * 100 / mMaxProgress); + + nsAutoString index; + index.AppendInt(percentage); + + NOTIFY_OBSERVERS(MIGRATION_PROGRESS, index.get()); + + // fire a timer to handle the next one. + nsresult rv = NS_NewTimerWithCallback( + getter_AddRefs(mFileIOTimer), static_cast<nsITimerCallback*>(this), + percentage == 100 ? 500 : 0, nsITimer::TYPE_ONE_SHOT, nullptr); + if (NS_FAILED(rv)) { + NS_WARNING("Could not start mFileIOTimer timer"); + } + } else + EndCopyFolders(); + + return; +} + +void nsNetscapeProfileMigratorBase::EndCopyFolders() { + mFileCopyTransactions.Clear(); + mFileCopyTransactionIndex = 0; + + // notify the UI that we are done with the migration process + nsAutoString index; + index.AppendInt(nsIMailProfileMigrator::MAILDATA); + NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get()); + + NOTIFY_OBSERVERS(MIGRATION_ENDED, nullptr); +} + +NS_IMETHODIMP +nsNetscapeProfileMigratorBase::GetSourceHasMultipleProfiles(bool* aResult) { + nsTArray<nsString> profiles; + GetSourceProfiles(profiles); + + *aResult = profiles.Length() > 1; + return NS_OK; +} + +NS_IMETHODIMP +nsNetscapeProfileMigratorBase::GetSourceExists(bool* aResult) { + nsTArray<nsString> profiles; + GetSourceProfiles(profiles); + + *aResult = profiles.Length() > 0; + return NS_OK; +} diff --git a/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.h b/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.h new file mode 100644 index 0000000000..5227673532 --- /dev/null +++ b/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef netscapeprofilemigratorbase___h___ +#define netscapeprofilemigratorbase___h___ + +#include "nsAttrValue.h" +#include "nsIFile.h" +#include "nsIStringBundle.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsIObserverService.h" +#include "nsITimer.h" +#include "nsIMailProfileMigrator.h" + +class nsIPrefBranch; + +struct fileTransactionEntry { + nsCOMPtr<nsIFile> srcFile; // the src path including leaf name + nsCOMPtr<nsIFile> destFile; // the destination path + nsString + newName; // only valid if the file should be renamed after getting copied +}; + +#define F(a) nsNetscapeProfileMigratorBase::a + +#define MAKEPREFTRANSFORM(pref, newpref, getmethod, setmethod) \ + { \ + pref, newpref, F(Get##getmethod), F(Set##setmethod), false, { -1 } \ + } + +#define MAKESAMETYPEPREFTRANSFORM(pref, method) \ + { \ + pref, 0, F(Get##method), F(Set##method), false, { -1 } \ + } + +class nsNetscapeProfileMigratorBase : public nsIMailProfileMigrator, + public nsITimerCallback, + public nsINamed + +{ + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + nsNetscapeProfileMigratorBase(); + + NS_IMETHOD GetSourceHasMultipleProfiles(bool* aResult) override; + NS_IMETHOD GetSourceExists(bool* aResult) override; + + struct PrefTransform; + typedef nsresult (*prefConverter)(PrefTransform*, nsIPrefBranch*); + + struct PrefTransform { + const char* sourcePrefName; + const char* targetPrefName; + prefConverter prefGetterFunc; + prefConverter prefSetterFunc; + bool prefHasValue; + union { + int32_t intValue; + bool boolValue; + char* stringValue; + }; + }; + + struct PrefBranchStruct { + char* prefName; + int32_t type; + union { + char* stringValue; + int32_t intValue; + bool boolValue; + }; + }; + + typedef nsTArray<PrefBranchStruct*> PBStructArray; + + static nsresult GetString(PrefTransform* aTransform, nsIPrefBranch* aBranch); + static nsresult SetString(PrefTransform* aTransform, nsIPrefBranch* aBranch); + static nsresult GetBool(PrefTransform* aTransform, nsIPrefBranch* aBranch); + static nsresult SetBool(PrefTransform* aTransform, nsIPrefBranch* aBranch); + static nsresult GetInt(PrefTransform* aTransform, nsIPrefBranch* aBranch); + static nsresult SetInt(PrefTransform* aTransform, nsIPrefBranch* aBranch); + + nsresult RecursiveCopy(nsIFile* srcDir, nsIFile* destDir); // helper routine + + protected: + virtual ~nsNetscapeProfileMigratorBase() {} + void CopyNextFolder(); + void EndCopyFolders(); + + nsresult GetProfileDataFromProfilesIni( + nsIFile* aDataDir, nsTArray<nsString>& aProfileNames, + nsTArray<RefPtr<nsIFile>>& aProfileLocations); + + nsresult CopyFile(const nsAString& aSourceFileName, + const nsAString& aTargetFileName); + + nsresult GetSignonFileName(bool aReplace, nsACString& aFileName); + nsresult LocateSignonsFile(nsACString& aResult); + + nsCOMPtr<nsIFile> mSourceProfile; + nsCOMPtr<nsIFile> mTargetProfile; + + // List of src/destination files we still have to copy into the new profile + // directory. + nsTArray<fileTransactionEntry> mFileCopyTransactions; + uint32_t mFileCopyTransactionIndex; + + int64_t mMaxProgress; + int64_t mCurrentProgress; + + nsCOMPtr<nsIObserverService> mObserverService; + nsCOMPtr<nsITimer> mFileIOTimer; +}; + +#endif diff --git a/comm/mail/components/migration/src/nsOutlookProfileMigrator.cpp b/comm/mail/components/migration/src/nsOutlookProfileMigrator.cpp new file mode 100644 index 0000000000..dd7535e257 --- /dev/null +++ b/comm/mail/components/migration/src/nsOutlookProfileMigrator.cpp @@ -0,0 +1,135 @@ +/* -*- Mode: C++; 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/. */ + +#include "nsMailProfileMigratorUtils.h" +#include "nsIServiceManager.h" +#include "nsOutlookProfileMigrator.h" +#include "nsIProfileMigrator.h" +#include "nsIImportSettings.h" +#include "nsIFile.h" +#include "nsITimer.h" +#include "nsComponentManagerUtils.h" + +NS_IMPL_ISUPPORTS(nsOutlookProfileMigrator, nsIMailProfileMigrator, + nsITimerCallback) + +nsOutlookProfileMigrator::nsOutlookProfileMigrator() { + mProcessingMailFolders = false; + // get the import service + mImportModule = do_CreateInstance("@mozilla.org/import/import-outlook;1"); +} + +nsOutlookProfileMigrator::~nsOutlookProfileMigrator() {} + +nsresult nsOutlookProfileMigrator::ContinueImport() { return Notify(nullptr); } + +/////////////////////////////////////////////////////////////////////////////// +// nsITimerCallback + +NS_IMETHODIMP +nsOutlookProfileMigrator::Notify(nsITimer* timer) { + int32_t progress; + mGenericImporter->GetProgress(&progress); + + nsAutoString index; + index.AppendInt(progress); + NOTIFY_OBSERVERS(MIGRATION_PROGRESS, index.get()); + + if (progress == 100) // are we done yet? + { + if (mProcessingMailFolders) + return FinishCopyingMailFolders(); + else + return FinishCopyingAddressBookData(); + } else { + // fire a timer to handle the next one. + nsresult rv = NS_NewTimerWithCallback( + getter_AddRefs(mFileIOTimer), static_cast<nsITimerCallback*>(this), 100, + nsITimer::TYPE_ONE_SHOT, nullptr); + if (NS_FAILED(rv)) { + NS_WARNING("Could not start mFileIOTimer timer"); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsOutlookProfileMigrator::GetName(nsACString& aName) { + aName.AssignLiteral("nsOutlookProfileMigrator"); + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIMailProfileMigrator + +NS_IMETHODIMP +nsOutlookProfileMigrator::Migrate(uint16_t aItems, nsIProfileStartup* aStartup, + const char16_t* aProfile) { + nsresult rv = NS_OK; + + if (aStartup) { + rv = aStartup->DoStartup(); + NS_ENSURE_SUCCESS(rv, rv); + } + + NOTIFY_OBSERVERS(MIGRATION_STARTED, nullptr); + + rv = ImportSettings(mImportModule); + + // now import address books + // this routine will asynchronously import address book data and it will then + // kick off the final migration step, copying the mail folders over. + rv = ImportAddressBook(mImportModule); + + // don't broadcast an on end migration here. We aren't done until our asynch + // import process says we are done. + return rv; +} + +NS_IMETHODIMP +nsOutlookProfileMigrator::GetMigrateData(const char16_t* aProfile, + bool aReplace, uint16_t* aResult) { + // There's no harm in assuming everything is available. + *aResult = nsIMailProfileMigrator::ACCOUNT_SETTINGS | + nsIMailProfileMigrator::ADDRESSBOOK_DATA | + nsIMailProfileMigrator::MAILDATA; + return NS_OK; +} + +NS_IMETHODIMP +nsOutlookProfileMigrator::GetSourceExists(bool* aResult) { + *aResult = false; + + nsCOMPtr<nsISupports> supports; + mImportModule->GetImportInterface(NS_IMPORT_SETTINGS_STR, + getter_AddRefs(supports)); + nsCOMPtr<nsIImportSettings> importSettings = do_QueryInterface(supports); + + if (importSettings) { + nsString description; + nsCOMPtr<nsIFile> location; + importSettings->AutoLocate(getter_Copies(description), + getter_AddRefs(location), aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsOutlookProfileMigrator::GetSourceHasMultipleProfiles(bool* aResult) { + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsOutlookProfileMigrator::GetSourceProfiles(nsTArray<nsString>& aResult) { + return NS_OK; +} + +NS_IMETHODIMP +nsOutlookProfileMigrator::GetSourceProfileLocations( + nsTArray<RefPtr<nsIFile>>& aResult) { + return NS_OK; +} diff --git a/comm/mail/components/migration/src/nsOutlookProfileMigrator.h b/comm/mail/components/migration/src/nsOutlookProfileMigrator.h new file mode 100644 index 0000000000..6de79f98dd --- /dev/null +++ b/comm/mail/components/migration/src/nsOutlookProfileMigrator.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef outlookprofilemigrator___h___ +#define outlookprofilemigrator___h___ + +#include "nsIMailProfileMigrator.h" +#include "nsITimer.h" +#include "nsProfileMigratorBase.h" + +class nsOutlookProfileMigrator : public nsIMailProfileMigrator, + public nsITimerCallback, + public nsProfileMigratorBase, + public nsINamed { + public: + NS_DECL_NSIMAILPROFILEMIGRATOR + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + nsOutlookProfileMigrator(); + virtual nsresult ContinueImport(); + + private: + virtual ~nsOutlookProfileMigrator(); +}; + +#endif diff --git a/comm/mail/components/migration/src/nsProfileMigrator.cpp b/comm/mail/components/migration/src/nsProfileMigrator.cpp new file mode 100644 index 0000000000..c6fa2bc867 --- /dev/null +++ b/comm/mail/components/migration/src/nsProfileMigrator.cpp @@ -0,0 +1,121 @@ +/* -*- Mode: C++; 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/. */ + +#include "nsIFile.h" +#include "mozIDOMWindow.h" +#include "nsIProfileMigrator.h" +#include "nsIPrefService.h" +#include "nsIServiceManager.h" +#include "nsIToolkitProfile.h" +#include "nsIToolkitProfileService.h" +#include "nsIWindowWatcher.h" +#include "nsISupportsPrimitives.h" +#include "nsIMutableArray.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIProperties.h" +#include "nsDirectoryServiceDefs.h" +#include "nsProfileMigrator.h" + +#ifdef XP_WIN +# include <windows.h> +#else +# include <limits.h> +#endif + +NS_IMPL_ISUPPORTS(nsProfileMigrator, nsIProfileMigrator) + +#define MIGRATION_WIZARD_FE_URL \ + "chrome://messenger/content/migration/migration.xhtml"_ns +#define MIGRATION_WIZARD_FE_FEATURES "chrome,dialog,modal,centerscreen"_ns + +NS_IMETHODIMP +nsProfileMigrator::Migrate(nsIProfileStartup* aStartup, const nsACString& aKey, + const nsACString& aProfileName) { + nsAutoCString key; + nsCOMPtr<nsIMailProfileMigrator> mailMigrator; + nsresult rv = GetDefaultMailMigratorKey(key, mailMigrator); + NS_ENSURE_SUCCESS(rv, rv); // abort migration if we failed to get a + // mailMigrator (if we were supposed to) + + nsCOMPtr<nsISupportsCString> cstr( + do_CreateInstance("@mozilla.org/supports-cstring;1")); + NS_ENSURE_TRUE(cstr, NS_ERROR_OUT_OF_MEMORY); + cstr->SetData(key); + + // By opening the Migration FE with a supplied mailMigrator, it will + // automatically migrate from it. + nsCOMPtr<nsIWindowWatcher> ww(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + nsCOMPtr<nsIMutableArray> params(do_CreateInstance(NS_ARRAY_CONTRACTID)); + if (!ww || !params) return NS_ERROR_FAILURE; + + params->AppendElement(cstr); + params->AppendElement(mailMigrator); + params->AppendElement(aStartup); + + nsCOMPtr<mozIDOMWindowProxy> migrateWizard; + return ww->OpenWindow(nullptr, MIGRATION_WIZARD_FE_URL, "_blank"_ns, + MIGRATION_WIZARD_FE_FEATURES, params, + getter_AddRefs(migrateWizard)); +} + +#ifdef XP_WIN +typedef struct { + WORD wLanguage; + WORD wCodePage; +} LANGANDCODEPAGE; + +# define INTERNAL_NAME_THUNDERBIRD "Thunderbird" +# define INTERNAL_NAME_SEAMONKEY "Mozilla" +#endif + +nsresult nsProfileMigrator::GetDefaultMailMigratorKey( + nsACString& aKey, nsCOMPtr<nsIMailProfileMigrator>& mailMigrator) { + // look up the value of profile.force.migration in case we are supposed to + // force migration using a particular migrator.... + nsresult rv = NS_OK; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString forceMigrationType; + prefs->GetCharPref("profile.force.migration", forceMigrationType); + + // if we are being forced to migrate to a particular migration type, then + // create an instance of that migrator and return it. + nsAutoCString migratorID; + if (!forceMigrationType.IsEmpty()) { + bool exists = false; + migratorID.AppendLiteral("@mozilla.org/messenger/server;1?type="); + migratorID.Append(forceMigrationType); + mailMigrator = do_CreateInstance(migratorID.get()); + if (!mailMigrator) return NS_ERROR_NOT_AVAILABLE; + + mailMigrator->GetSourceExists(&exists); + /* trying to force migration on a source which doesn't + * have any profiles. + */ + if (!exists) return NS_ERROR_NOT_AVAILABLE; + aKey = forceMigrationType; + return NS_OK; + } + +#define MAX_SOURCE_LENGTH 10 + const char sources[][MAX_SOURCE_LENGTH] = {"seamonkey", "outlook", ""}; + for (uint32_t i = 0; sources[i][0]; ++i) { + migratorID.AssignLiteral("@mozilla.org/messenger/server;1?type="); + migratorID.Append(sources[i]); + mailMigrator = do_CreateInstance(migratorID.get()); + if (!mailMigrator) continue; + + bool exists = false; + mailMigrator->GetSourceExists(&exists); + if (exists) { + mailMigrator = nullptr; + return NS_OK; + } + } + + return NS_ERROR_NOT_AVAILABLE; +} diff --git a/comm/mail/components/migration/src/nsProfileMigrator.h b/comm/mail/components/migration/src/nsProfileMigrator.h new file mode 100644 index 0000000000..d25a9989e9 --- /dev/null +++ b/comm/mail/components/migration/src/nsProfileMigrator.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; 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/. */ + +#include "nsIFile.h" +#include "nsIProfileMigrator.h" +#include "nsIMailProfileMigrator.h" +#include "nsIServiceManager.h" +#include "nsIToolkitProfile.h" +#include "nsIToolkitProfileService.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceDefs.h" + +#include "nsString.h" + +#define NS_THUNDERBIRD_PROFILEIMPORT_CID \ + { \ + 0xb3c78baf, 0x3a52, 0x41d2, { \ + 0x97, 0x18, 0xc3, 0x19, 0xbe, 0xf9, 0xaf, 0xfc \ + } \ + } + +class nsProfileMigrator final : public nsIProfileMigrator { + public: + NS_DECL_NSIPROFILEMIGRATOR + NS_DECL_ISUPPORTS + + nsProfileMigrator(){}; + + protected: + ~nsProfileMigrator(){}; + + nsresult GetDefaultMailMigratorKey( + nsACString& key, nsCOMPtr<nsIMailProfileMigrator>& mailMigrator); +}; diff --git a/comm/mail/components/migration/src/nsProfileMigratorBase.cpp b/comm/mail/components/migration/src/nsProfileMigratorBase.cpp new file mode 100644 index 0000000000..5ce067308c --- /dev/null +++ b/comm/mail/components/migration/src/nsProfileMigratorBase.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; 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/. */ + +#include "nsMailProfileMigratorUtils.h" +#include "nsISupportsPrimitives.h" +#include "nsProfileMigratorBase.h" +#include "nsIMailProfileMigrator.h" + +#include "nsIImportSettings.h" +#include "nsIImportFilters.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#define kPersonalAddressbookUri "jsaddrbook://abook.sqlite" + +nsProfileMigratorBase::nsProfileMigratorBase() { + mObserverService = do_GetService("@mozilla.org/observer-service;1"); + mProcessingMailFolders = false; +} + +nsProfileMigratorBase::~nsProfileMigratorBase() { + if (mFileIOTimer) mFileIOTimer->Cancel(); +} + +nsresult nsProfileMigratorBase::ImportSettings(nsIImportModule* aImportModule) { + nsresult rv; + + nsAutoString index; + index.AppendInt(nsIMailProfileMigrator::ACCOUNT_SETTINGS); + NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get()); + + nsCOMPtr<nsISupports> supports; + rv = aImportModule->GetImportInterface(NS_IMPORT_SETTINGS_STR, + getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIImportSettings> importSettings = do_QueryInterface(supports); + NS_ENSURE_SUCCESS(rv, rv); + + bool importedSettings = false; + + rv = importSettings->Import(getter_AddRefs(mLocalFolderAccount), + &importedSettings); + + NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get()); + + return rv; +} + +nsresult nsProfileMigratorBase::ImportAddressBook( + nsIImportModule* aImportModule) { + nsresult rv; + + nsAutoString index; + index.AppendInt(nsIMailProfileMigrator::ADDRESSBOOK_DATA); + NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get()); + + nsCOMPtr<nsISupports> supports; + rv = aImportModule->GetImportInterface(NS_IMPORT_ADDRESS_STR, + getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + + mGenericImporter = do_QueryInterface(supports); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsCString> pabString = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // We want to migrate the Outlook addressbook into our personal address book. + pabString->SetData(nsDependentCString(kPersonalAddressbookUri)); + mGenericImporter->SetData("addressDestination", pabString); + + bool importResult; + bool wantsProgress; + mGenericImporter->WantsProgress(&wantsProgress); + rv = mGenericImporter->BeginImport(nullptr, nullptr, &importResult); + + if (wantsProgress) + ContinueImport(); + else + FinishCopyingAddressBookData(); + + return rv; +} + +nsresult nsProfileMigratorBase::FinishCopyingAddressBookData() { + nsAutoString index; + index.AppendInt(nsIMailProfileMigrator::ADDRESSBOOK_DATA); + NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get()); + + // now kick off the mail migration code + ImportMailData(mImportModule); + + return NS_OK; +} + +nsresult nsProfileMigratorBase::ImportMailData(nsIImportModule* aImportModule) { + nsresult rv; + + nsAutoString index; + index.AppendInt(nsIMailProfileMigrator::MAILDATA); + NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get()); + + nsCOMPtr<nsISupports> supports; + rv = aImportModule->GetImportInterface(NS_IMPORT_MAIL_STR, + getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + + mGenericImporter = do_QueryInterface(supports); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsPRBool> migrating = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // by setting the migration flag, we force the import utility to install local + // folders from OE directly into Local Folders and not as a subfolder + migrating->SetData(true); + mGenericImporter->SetData("migration", migrating); + + bool importResult; + bool wantsProgress; + mGenericImporter->WantsProgress(&wantsProgress); + rv = mGenericImporter->BeginImport(nullptr, nullptr, &importResult); + + mProcessingMailFolders = true; + + if (wantsProgress) + ContinueImport(); + else + FinishCopyingMailFolders(); + + return rv; +} + +nsresult nsProfileMigratorBase::FinishCopyingMailFolders() { + nsAutoString index; + index.AppendInt(nsIMailProfileMigrator::MAILDATA); + NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get()); + + // now kick off the filters migration code + return ImportFilters(mImportModule); +} + +nsresult nsProfileMigratorBase::ImportFilters(nsIImportModule* aImportModule) { + nsresult rv = NS_OK; + + nsCOMPtr<nsISupports> supports; + nsresult rv2 = aImportModule->GetImportInterface(NS_IMPORT_FILTERS_STR, + getter_AddRefs(supports)); + nsCOMPtr<nsIImportFilters> importFilters = do_QueryInterface(supports); + + if (NS_SUCCEEDED(rv2) && importFilters) { + nsAutoString index; + index.AppendInt(nsIMailProfileMigrator::FILTERS); + NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get()); + + bool importedFilters = false; + char16_t* error; + + rv = importFilters->Import(&error, &importedFilters); + + NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get()); + } + + // migration is now done...notify the UI. + NOTIFY_OBSERVERS(MIGRATION_ENDED, nullptr); + + return rv; +} diff --git a/comm/mail/components/migration/src/nsProfileMigratorBase.h b/comm/mail/components/migration/src/nsProfileMigratorBase.h new file mode 100644 index 0000000000..6ca0d7fcb4 --- /dev/null +++ b/comm/mail/components/migration/src/nsProfileMigratorBase.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef profilemigratorbase___h___ +#define profilemigratorbase___h___ + +#include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsITimer.h" +#include "nsIImportGeneric.h" +#include "nsIImportModule.h" +#include "nsIMsgAccount.h" + +class nsProfileMigratorBase { + public: + nsProfileMigratorBase(); + virtual ~nsProfileMigratorBase(); + virtual nsresult ContinueImport() = 0; + + protected: + nsresult ImportSettings(nsIImportModule* aImportModule); + nsresult ImportAddressBook(nsIImportModule* aImportModule); + nsresult ImportMailData(nsIImportModule* aImportModule); + nsresult ImportFilters(nsIImportModule* aImportModule); + nsresult FinishCopyingAddressBookData(); + nsresult FinishCopyingMailFolders(); + + nsCOMPtr<nsIObserverService> mObserverService; + nsCOMPtr<nsITimer> mFileIOTimer; + nsCOMPtr<nsIImportGeneric> mGenericImporter; + nsCOMPtr<nsIImportModule> mImportModule; + nsCOMPtr<nsIMsgAccount> + mLocalFolderAccount; // needed for nsIImportSettings::Import + bool mProcessingMailFolders; // we are either asynchronously parsing address + // books or mail folders +}; + +#endif diff --git a/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.cpp b/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.cpp new file mode 100644 index 0000000000..27251462c9 --- /dev/null +++ b/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.cpp @@ -0,0 +1,1175 @@ +/* -*- Mode: C++; 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/. */ + +#include "nsMailProfileMigratorUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIMsgAccountManager.h" +#include "nsISmtpServer.h" +#include "nsISmtpService.h" +#include "nsIPrefLocalizedString.h" +#include "nsIPrefService.h" +#include "nsISupportsPrimitives.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsSeamonkeyProfileMigrator.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsComponentManagerUtils.h" // for do_CreateInstance +#include "mozilla/ArrayUtils.h" +#include "nsIFile.h" + +#include "nsIAbManager.h" +#include "nsIAbDirectory.h" +#include "../../../../mailnews/import/src/MorkImport.h" + +// Mail specific folder paths +#define MAIL_DIR_50_NAME u"Mail"_ns +#define IMAP_MAIL_DIR_50_NAME u"ImapMail"_ns +#define NEWS_DIR_50_NAME u"News"_ns + +/////////////////////////////////////////////////////////////////////////////// +// nsSeamonkeyProfileMigrator +#define FILE_NAME_JUNKTRAINING u"training.dat"_ns +#define FILE_NAME_PERSONALDICTIONARY u"persdict.dat"_ns +#define FILE_NAME_PERSONAL_ADDRESSBOOK u"abook.mab"_ns +#define FILE_NAME_MAILVIEWS u"mailviews.dat"_ns +#define FILE_NAME_CERT9DB u"cert9.db"_ns +#define FILE_NAME_KEY4DB u"key4.db"_ns +#define FILE_NAME_SECMODDB u"secmod.db"_ns +#define FILE_NAME_PREFS u"prefs.js"_ns +#define FILE_NAME_USER_PREFS u"user.js"_ns + +struct PrefBranchStruct { + char* prefName; + int32_t type; + union { + char* stringValue; + int32_t intValue; + bool boolValue; + char16_t* wstringValue; + }; +}; + +NS_IMPL_ISUPPORTS(nsSeamonkeyProfileMigrator, nsIMailProfileMigrator, + nsITimerCallback) + +nsSeamonkeyProfileMigrator::nsSeamonkeyProfileMigrator() {} + +nsSeamonkeyProfileMigrator::~nsSeamonkeyProfileMigrator() {} + +/////////////////////////////////////////////////////////////////////////////// +// nsIMailProfileMigrator + +NS_IMETHODIMP +nsSeamonkeyProfileMigrator::Migrate(uint16_t aItems, + nsIProfileStartup* aStartup, + const char16_t* aProfile) { + nsresult rv = NS_OK; + bool aReplace = aStartup ? true : false; + + if (!mTargetProfile) { + GetProfilePath(aStartup, mTargetProfile); + if (!mTargetProfile) return NS_ERROR_FAILURE; + } + if (!mSourceProfile) { + GetSourceProfile(aProfile); + if (!mSourceProfile) return NS_ERROR_FAILURE; + } + + NOTIFY_OBSERVERS(MIGRATION_STARTED, nullptr); + + if (aReplace) { + CopyPreferences(aReplace); + } else { + ImportPreferences(aItems); + } + + // fake notifications for things we've already imported as part of + // CopyPreferences + COPY_DATA(DummyCopyRoutine, aReplace, + nsIMailProfileMigrator::ACCOUNT_SETTINGS); + COPY_DATA(DummyCopyRoutine, aReplace, nsIMailProfileMigrator::NEWSDATA); + + // copy junk mail training file + COPY_DATA(CopyJunkTraining, aReplace, nsIMailProfileMigrator::JUNKTRAINING); + COPY_DATA(CopyPasswords, aReplace, nsIMailProfileMigrator::PASSWORDS); + + // the last thing to do is to actually copy over any mail folders we have + // marked for copying we want to do this last and it will be asynchronous so + // the UI doesn't freeze up while we perform this potentially very long + // operation. + + nsAutoString index; + index.AppendInt(nsIMailProfileMigrator::MAILDATA); + NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get()); + + // Generate the max progress value now that we know all of the files we need + // to copy + uint32_t count = mFileCopyTransactions.Length(); + for (uint32_t i = 0; i < count; ++i) { + fileTransactionEntry fileTransaction = mFileCopyTransactions.ElementAt(i); + int64_t fileSize; + fileTransaction.srcFile->GetFileSize(&fileSize); + mMaxProgress += fileSize; + } + + CopyNextFolder(); + + return rv; +} + +NS_IMETHODIMP +nsSeamonkeyProfileMigrator::GetMigrateData(const char16_t* aProfile, + bool aReplace, uint16_t* aResult) { + *aResult = 0; + + if (!mSourceProfile) { + GetSourceProfile(aProfile); + if (!mSourceProfile) return NS_ERROR_FILE_NOT_FOUND; + } + + MigrationData data[] = { + {ToNewUnicode(FILE_NAME_PREFS), nsIMailProfileMigrator::SETTINGS, false}, + {ToNewUnicode(FILE_NAME_JUNKTRAINING), + nsIMailProfileMigrator::JUNKTRAINING, true}, + }; + + // Frees file name strings allocated above. + GetMigrateDataFromArray(data, sizeof(data) / sizeof(MigrationData), aReplace, + mSourceProfile, aResult); + + // Now locate passwords + nsCString signonsFileName; + GetSignonFileName(aReplace, signonsFileName); + + if (!signonsFileName.IsEmpty()) { + nsAutoString fileName; + CopyASCIItoUTF16(signonsFileName, fileName); + nsCOMPtr<nsIFile> sourcePasswordsFile; + mSourceProfile->Clone(getter_AddRefs(sourcePasswordsFile)); + sourcePasswordsFile->Append(fileName); + + bool exists; + sourcePasswordsFile->Exists(&exists); + if (exists) *aResult |= nsIMailProfileMigrator::PASSWORDS; + } + + // add some extra migration fields for things we also migrate + *aResult |= nsIMailProfileMigrator::ACCOUNT_SETTINGS | + nsIMailProfileMigrator::MAILDATA | + nsIMailProfileMigrator::NEWSDATA | + nsIMailProfileMigrator::ADDRESSBOOK_DATA; + + return NS_OK; +} + +NS_IMETHODIMP +nsSeamonkeyProfileMigrator::GetSourceProfiles(nsTArray<nsString>& aResult) { + if (mProfileNames.IsEmpty() && mProfileLocations.IsEmpty()) { + // Fills mProfileNames and mProfileLocations + FillProfileDataFromSeamonkeyRegistry(); + } + + aResult = mProfileNames.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsSeamonkeyProfileMigrator::GetSourceProfileLocations( + nsTArray<RefPtr<nsIFile>>& aResult) { + if (mProfileNames.IsEmpty() && mProfileLocations.IsEmpty()) { + // Fills mProfileNames and mProfileLocations + FillProfileDataFromSeamonkeyRegistry(); + } + + aResult = mProfileLocations.Clone(); + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsSeamonkeyProfileMigrator + +nsresult nsSeamonkeyProfileMigrator::GetSourceProfile( + const char16_t* aProfile) { + uint32_t count = mProfileNames.Length(); + for (uint32_t i = 0; i < count; ++i) { + nsString profileName = mProfileNames[i]; + if (profileName.Equals(aProfile)) { + mSourceProfile = mProfileLocations[i]; + break; + } + } + + return NS_OK; +} + +nsresult nsSeamonkeyProfileMigrator::FillProfileDataFromSeamonkeyRegistry() { + // Find the Seamonkey Registry + nsCOMPtr<nsIProperties> fileLocator( + do_GetService("@mozilla.org/file/directory_service;1")); + nsCOMPtr<nsIFile> seamonkeyData; +#undef EXTRA_PREPEND + +#ifdef XP_WIN +# define NEW_FOLDER "SeaMonkey" +# define EXTRA_PREPEND "Mozilla" + + fileLocator->Get(NS_WIN_APPDATA_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(seamonkeyData)); + NS_ENSURE_TRUE(seamonkeyData, NS_ERROR_FAILURE); + +#elif defined(XP_MACOSX) +# define NEW_FOLDER "SeaMonkey" +# define EXTRA_PREPEND "Application Support" + fileLocator->Get(NS_MAC_USER_LIB_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(seamonkeyData)); + NS_ENSURE_TRUE(seamonkeyData, NS_ERROR_FAILURE); + +#elif defined(XP_UNIX) +# define NEW_FOLDER "seamonkey" +# define EXTRA_PREPEND ".mozilla" + fileLocator->Get(NS_UNIX_HOME_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(seamonkeyData)); + NS_ENSURE_TRUE(seamonkeyData, NS_ERROR_FAILURE); + +#else + // On other OS just abort. + return NS_ERROR_FAILURE; +#endif + + nsCOMPtr<nsIFile> newSeamonkeyData; + seamonkeyData->Clone(getter_AddRefs(newSeamonkeyData)); + NS_ENSURE_TRUE(newSeamonkeyData, NS_ERROR_FAILURE); + +#ifdef EXTRA_PREPEND + newSeamonkeyData->Append(NS_LITERAL_STRING_FROM_CSTRING(EXTRA_PREPEND)); +#endif + newSeamonkeyData->Append(NS_LITERAL_STRING_FROM_CSTRING(NEW_FOLDER)); + + nsresult rv = GetProfileDataFromProfilesIni(newSeamonkeyData, mProfileNames, + mProfileLocations); + + return rv; +} + +static nsSeamonkeyProfileMigrator::PrefTransform gTransforms[] = { + + MAKESAMETYPEPREFTRANSFORM("signon.SignonFileName", String), + MAKESAMETYPEPREFTRANSFORM("mailnews.headers.showUserAgent", Bool), + MAKESAMETYPEPREFTRANSFORM("mailnews.headers.showOrganization", Bool), + MAKESAMETYPEPREFTRANSFORM("mail.collect_addressbook", String), + MAKESAMETYPEPREFTRANSFORM("mail.collect_email_address_outgoing", Bool), + MAKESAMETYPEPREFTRANSFORM("mail.wrap_long_lines", Bool), + MAKESAMETYPEPREFTRANSFORM("mailnews.customHeaders", String), + MAKESAMETYPEPREFTRANSFORM("mail.default_html_action", Int), + MAKESAMETYPEPREFTRANSFORM("mail.forward_message_mode", Int), + MAKESAMETYPEPREFTRANSFORM("mail.SpellCheckBeforeSend", Bool), + MAKESAMETYPEPREFTRANSFORM("mail.warn_on_send_accel_key", Bool), + MAKESAMETYPEPREFTRANSFORM("mailnews.headers.showUserAgent", Bool), + MAKESAMETYPEPREFTRANSFORM("mailnews.headers.showOrganization", Bool), + MAKESAMETYPEPREFTRANSFORM("mail.biff.play_sound", Bool), + MAKESAMETYPEPREFTRANSFORM("mail.biff.play_sound.type", Int), + MAKESAMETYPEPREFTRANSFORM("mail.biff.play_sound.url", String), + MAKESAMETYPEPREFTRANSFORM("mail.biff.show_alert", Bool), + MAKESAMETYPEPREFTRANSFORM("network.proxy.type", Int), + MAKESAMETYPEPREFTRANSFORM("network.proxy.http", String), + MAKESAMETYPEPREFTRANSFORM("network.proxy.http_port", Int), + MAKESAMETYPEPREFTRANSFORM("network.proxy.ftp", String), + MAKESAMETYPEPREFTRANSFORM("network.proxy.ftp_port", Int), + MAKESAMETYPEPREFTRANSFORM("network.proxy.ssl", String), + MAKESAMETYPEPREFTRANSFORM("network.proxy.ssl_port", Int), + MAKESAMETYPEPREFTRANSFORM("network.proxy.socks", String), + MAKESAMETYPEPREFTRANSFORM("network.proxy.socks_port", Int), + MAKESAMETYPEPREFTRANSFORM("network.proxy.no_proxies_on", String), + MAKESAMETYPEPREFTRANSFORM("network.proxy.autoconfig_url", String), + + MAKESAMETYPEPREFTRANSFORM("mail.accountmanager.accounts", String), + MAKESAMETYPEPREFTRANSFORM("mail.accountmanager.defaultaccount", String), + MAKESAMETYPEPREFTRANSFORM("mail.accountmanager.localfoldersserver", String), + MAKESAMETYPEPREFTRANSFORM("mail.smtp.defaultserver", String), + MAKESAMETYPEPREFTRANSFORM("mail.smtpservers", String), + + MAKESAMETYPEPREFTRANSFORM("msgcompose.font_face", String), + MAKESAMETYPEPREFTRANSFORM("msgcompose.font_size", String), + MAKESAMETYPEPREFTRANSFORM("msgcompose.text_color", String), + MAKESAMETYPEPREFTRANSFORM("msgcompose.background_color", String), + + MAKEPREFTRANSFORM("mail.pane_config", "mail.pane_config.dynamic", Int, + Int)}; + +/** + * Use the current Seamonkey's prefs.js as base, and transform some branches. + * Thunderbird's prefs.js is thrown away. + */ +nsresult nsSeamonkeyProfileMigrator::TransformPreferences( + const nsAString& aSourcePrefFileName, + const nsAString& aTargetPrefFileName) { + PrefTransform* transform; + PrefTransform* end = + gTransforms + sizeof(gTransforms) / sizeof(PrefTransform); + + // Load the source pref file + nsCOMPtr<nsIPrefService> psvc(do_GetService(NS_PREFSERVICE_CONTRACTID)); + psvc->ResetPrefs(); + + nsCOMPtr<nsIFile> sourcePrefsFile; + mSourceProfile->Clone(getter_AddRefs(sourcePrefsFile)); + sourcePrefsFile->Append(aSourcePrefFileName); + psvc->ReadUserPrefsFromFile(sourcePrefsFile); + + nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(psvc)); + for (transform = gTransforms; transform < end; ++transform) + transform->prefGetterFunc(transform, branch); + + static const char* branchNames[] = { + // Keep the three below first, or change the indexes below + "mail.identity.", "mail.server.", "ldap_2.servers.", + "mail.account.", "mail.smtpserver.", "mailnews.labels.", + "mailnews.tags."}; + + // read in the various pref branch trees for accounts, identities, servers, + // etc. + PBStructArray branches[MOZ_ARRAY_LENGTH(branchNames)]; + uint32_t i; + for (i = 0; i < MOZ_ARRAY_LENGTH(branchNames); ++i) + ReadBranch(branchNames[i], psvc, branches[i]); + + // The signature file prefs may be paths to files in the seamonkey profile + // path so we need to copy them over and fix these paths up before we write + // them out to the new prefs.js. + CopySignatureFiles(branches[0], psvc); + + // Certain mail prefs may actually be absolute paths instead of profile + // relative paths we need to fix these paths up before we write them out to + // the new prefs.js + CopyMailFolders(branches[1], psvc); + + TransformAddressbooksForImport(psvc, branches[2], true); + + // Now that we have all the pref data in memory, load the target pref file, + // and write it back out. + psvc->ResetPrefs(); + + // XXX Re-order this? + + for (transform = gTransforms; transform < end; ++transform) + transform->prefSetterFunc(transform, branch); + + for (i = 0; i < MOZ_ARRAY_LENGTH(branchNames); i++) + WriteBranch(branchNames[i], psvc, branches[i]); + + nsCOMPtr<nsIFile> targetPrefsFile; + mTargetProfile->Clone(getter_AddRefs(targetPrefsFile)); + targetPrefsFile->Append(aTargetPrefFileName); + psvc->SavePrefFile(targetPrefsFile); + + return NS_OK; +} + +nsresult nsSeamonkeyProfileMigrator::CopySignatureFiles( + PBStructArray& aIdentities, nsIPrefService* aPrefService) { + nsresult rv = NS_OK; + + uint32_t count = aIdentities.Length(); + for (uint32_t i = 0; i < count; ++i) { + PrefBranchStruct* pref = aIdentities.ElementAt(i); + nsDependentCString prefName(pref->prefName); + + // a partial fix for bug #255043 + // if the user's signature file from seamonkey lives in the + // seamonkey profile root, we'll copy it over to the new + // thunderbird profile root and then set the pref to the new value + // note, this doesn't work for multiple signatures that live + // below the seamonkey profile root + if (StringEndsWith(prefName, ".sig_file"_ns)) { + // turn the pref into a nsIFile + nsCOMPtr<nsIFile> srcSigFile = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + rv = srcSigFile->SetPersistentDescriptor( + nsDependentCString(pref->stringValue)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> targetSigFile; + rv = mTargetProfile->Clone(getter_AddRefs(targetSigFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // now make the copy + bool exists; + srcSigFile->Exists(&exists); + if (exists) { + nsAutoString leafName; + srcSigFile->GetLeafName(leafName); + srcSigFile->CopyTo( + targetSigFile, + leafName); // will fail if we've already copied a sig file here + targetSigFile->Append(leafName); + + // now write out the new descriptor + nsAutoCString descriptorString; + rv = targetSigFile->GetPersistentDescriptor(descriptorString); + NS_ENSURE_SUCCESS(rv, rv); + free(pref->stringValue); + pref->stringValue = ToNewCString(descriptorString); + } + } + } + return NS_OK; +} + +nsresult nsSeamonkeyProfileMigrator::CopyMailFolders( + PBStructArray& aMailServers, nsIPrefService* aPrefService) { + // Each server has a .directory pref which points to the location of the mail + // data for that server. We need to do two things for that case... + // (1) Fix up the directory path for the new profile + // (2) copy the mail folder data from the source directory pref to the + // destination directory pref + + nsresult rv; + uint32_t count = aMailServers.Length(); + for (uint32_t i = 0; i < count; i++) { + PrefBranchStruct* pref = aMailServers.ElementAt(i); + nsDependentCString prefName(pref->prefName); + + if (StringEndsWith(prefName, ".directory-rel"_ns)) { + // When the directories are modified below, we may change the .directory + // pref. As we don't have a pref branch to modify at this stage and set + // up the relative folders properly, we'll just remove all the + // *.directory-rel prefs. Mailnews will cope with this, creating them + // when it first needs them. + if (pref->type == nsIPrefBranch::PREF_STRING) free(pref->stringValue); + + aMailServers.RemoveElementAt(i); + // Now decrease i and count to match the removed element + --i; + --count; + } else if (StringEndsWith(prefName, ".directory"_ns)) { + // let's try to get a branch for this particular server to simplify things + prefName.Cut(prefName.Length() - strlen("directory"), + strlen("directory")); + prefName.Insert("mail.server.", 0); + + nsCOMPtr<nsIPrefBranch> serverBranch; + aPrefService->GetBranch(prefName.get(), getter_AddRefs(serverBranch)); + + if (!serverBranch) + break; // should we clear out this server pref from aMailServers? + + nsCString serverType; + serverBranch->GetCharPref("type", serverType); + + nsCOMPtr<nsIFile> sourceMailFolder; + serverBranch->GetComplexValue("directory", NS_GET_IID(nsIFile), + getter_AddRefs(sourceMailFolder)); + + // now based on type, we need to build a new destination path for the mail + // folders for this server + nsCOMPtr<nsIFile> targetMailFolder; + if (serverType.Equals("imap")) { + mTargetProfile->Clone(getter_AddRefs(targetMailFolder)); + targetMailFolder->Append(IMAP_MAIL_DIR_50_NAME); + } else if (serverType.Equals("none") || serverType.Equals("pop3") || + serverType.Equals("rss")) { + // local folders and POP3 servers go under <profile>\Mail + mTargetProfile->Clone(getter_AddRefs(targetMailFolder)); + targetMailFolder->Append(MAIL_DIR_50_NAME); + } else if (serverType.Equals("nntp")) { + mTargetProfile->Clone(getter_AddRefs(targetMailFolder)); + targetMailFolder->Append(NEWS_DIR_50_NAME); + } + + if (targetMailFolder) { + // for all of our server types, append the host name to the directory as + // part of the new location + nsCString hostName; + serverBranch->GetCharPref("hostname", hostName); + targetMailFolder->Append(NS_ConvertASCIItoUTF16(hostName)); + + // we should make sure the host name based directory we are going to + // migrate the accounts into is unique. This protects against the case + // where the user has multiple servers with the same host name. + rv = targetMailFolder->CreateUnique(nsIFile::DIRECTORY_TYPE, 0777); + NS_ENSURE_SUCCESS(rv, rv); + + (void)RecursiveCopy(sourceMailFolder, targetMailFolder); + // now we want to make sure the actual directory pref that gets + // transformed into the new profile's pref.js has the right file + // location. + nsAutoCString descriptorString; + rv = targetMailFolder->GetPersistentDescriptor(descriptorString); + NS_ENSURE_SUCCESS(rv, rv); + free(pref->stringValue); + pref->stringValue = ToNewCString(descriptorString); + } + } else if (StringEndsWith(prefName, ".newsrc.file"_ns)) { + // copy the news RC file into \News. this won't work if the user has + // different newsrc files for each account I don't know what to do in that + // situation. + + nsCOMPtr<nsIFile> targetNewsRCFile; + mTargetProfile->Clone(getter_AddRefs(targetNewsRCFile)); + targetNewsRCFile->Append(NEWS_DIR_50_NAME); + + // turn the pref into a nsIFile + nsCOMPtr<nsIFile> srcNewsRCFile = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + rv = srcNewsRCFile->SetPersistentDescriptor( + nsDependentCString(pref->stringValue)); + NS_ENSURE_SUCCESS(rv, rv); + + // now make the copy + bool exists; + srcNewsRCFile->Exists(&exists); + if (exists) { + nsAutoString leafName; + srcNewsRCFile->GetLeafName(leafName); + srcNewsRCFile->CopyTo( + targetNewsRCFile, + leafName); // will fail if we've already copied a newsrc file here + targetNewsRCFile->Append(leafName); + + // now write out the new descriptor + nsAutoCString descriptorString; + rv = targetNewsRCFile->GetPersistentDescriptor(descriptorString); + NS_ENSURE_SUCCESS(rv, rv); + free(pref->stringValue); + pref->stringValue = ToNewCString(descriptorString); + } + } + } + + return NS_OK; +} + +nsresult nsSeamonkeyProfileMigrator::CopyPreferences(bool aReplace) { + nsresult rv = NS_OK; + nsresult tmp; + + tmp = TransformPreferences(FILE_NAME_PREFS, FILE_NAME_PREFS); + + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = CopyFile(FILE_NAME_USER_PREFS, FILE_NAME_USER_PREFS); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + // Security Stuff + tmp = CopyFile(FILE_NAME_CERT9DB, FILE_NAME_CERT9DB); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = CopyFile(FILE_NAME_KEY4DB, FILE_NAME_KEY4DB); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = CopyFile(FILE_NAME_SECMODDB, FILE_NAME_SECMODDB); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + tmp = CopyFile(FILE_NAME_PERSONALDICTIONARY, FILE_NAME_PERSONALDICTIONARY); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = CopyFile(FILE_NAME_MAILVIEWS, FILE_NAME_MAILVIEWS); + if (NS_FAILED(tmp)) { + rv = tmp; + } + return rv; +} + +/** + * Use the current Thunderbird's prefs.js as base, transform branches of + * Seamonkey's prefs.js so that those branches can be imported without conflicts + * or overwriting. + */ +nsresult nsSeamonkeyProfileMigrator::ImportPreferences(uint16_t aItems) { + nsresult rv; + nsCOMPtr<nsIPrefService> psvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Because all operations on nsIPrefService or nsIPrefBranch will update + // prefs.js directly, we need to backup the current pref file to be used as a + // base later. + nsCOMPtr<nsIFile> targetPrefsFile; + mTargetProfile->Clone(getter_AddRefs(targetPrefsFile)); + targetPrefsFile->Append(FILE_NAME_PREFS + u".orig"_ns); + rv = psvc->SavePrefFile(targetPrefsFile); + NS_ENSURE_SUCCESS(rv, rv); + + // Load the source pref file. + rv = psvc->ResetPrefs(); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFile> sourcePrefsFile; + mSourceProfile->Clone(getter_AddRefs(sourcePrefsFile)); + sourcePrefsFile->Append(FILE_NAME_PREFS); + rv = psvc->ReadUserPrefsFromFile(sourcePrefsFile); + NS_ENSURE_SUCCESS(rv, rv); + + // Read in the various pref branch trees for accounts, identities, servers, + // etc. + static const char* branchNames[] = {"mail.identity.", "mail.server.", + "mail.account.", "mail.smtpserver.", + "mailnews.labels.", "mailnews.tags.", + "ldap_2.servers."}; + PBStructArray sourceBranches[MOZ_ARRAY_LENGTH(branchNames)]; + for (uint32_t i = 0; i < MOZ_ARRAY_LENGTH(branchNames); i++) { + if ((!(aItems & nsIMailProfileMigrator::SETTINGS) && i <= 5) || + (!(aItems & nsIMailProfileMigrator::ADDRESSBOOK_DATA) && i == 6)) { + continue; + } + ReadBranch(branchNames[i], psvc, sourceBranches[i]); + } + + // Read back the original prefs. + rv = psvc->ResetPrefs(); + NS_ENSURE_SUCCESS(rv, rv); + rv = psvc->ReadUserPrefsFromFile(targetPrefsFile); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccountManager> accountManager( + do_GetService("@mozilla.org/messenger/account-manager;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + PrefKeyHashTable smtpServerKeyHashTable; + PrefKeyHashTable identityKeyHashTable; + PrefKeyHashTable serverKeyHashTable; + + // Transforming order is important here. + TransformSmtpServersForImport(sourceBranches[3], smtpServerKeyHashTable); + + // mail.identity.idN.smtpServer depends on previous step. + TransformIdentitiesForImport(sourceBranches[0], accountManager, + smtpServerKeyHashTable, identityKeyHashTable); + + TransformMailServersForImport(branchNames[1], psvc, sourceBranches[1], + accountManager, serverKeyHashTable); + + // mail.accountN.{identities,server} depends on previous steps. + TransformMailAccountsForImport(psvc, sourceBranches[2], accountManager, + identityKeyHashTable, serverKeyHashTable); + + // CopyMailFolders requires mail.server.serverN branch exists. + WriteBranch(branchNames[1], psvc, sourceBranches[1], false); + CopyMailFolders(sourceBranches[1], psvc); + + // TransformAddressbooksForImport writes the branch and migrates the files. + TransformAddressbooksForImport(psvc, sourceBranches[6], false); + + for (uint32_t i = 0; i < MOZ_ARRAY_LENGTH(branchNames); i++) + WriteBranch(branchNames[i], psvc, sourceBranches[i]); + + targetPrefsFile->Remove(false); + return rv; +} + +/** + * Transform mail.identity branch. + */ +nsresult nsSeamonkeyProfileMigrator::TransformIdentitiesForImport( + PBStructArray& aIdentities, nsIMsgAccountManager* accountManager, + PrefKeyHashTable& smtpServerKeyHashTable, PrefKeyHashTable& keyHashTable) { + nsresult rv; + nsTArray<nsCString> newKeys; + + for (auto pref : aIdentities) { + nsDependentCString prefName(pref->prefName); + nsTArray<nsCString> keys; + ParseString(prefName, '.', keys); + auto key = keys[0]; + if (key == "default") { + continue; + } else if (StringEndsWith(prefName, ".smtpServer"_ns)) { + nsDependentCString serverKey(pref->stringValue); + nsCString newServerKey; + if (smtpServerKeyHashTable.Get(serverKey, &newServerKey)) { + pref->stringValue = moz_xstrdup(newServerKey.get()); + } + } + + // For every seamonkey identity, create a new one to avoid conflicts. + nsCString newKey; + if (!keyHashTable.Get(key, &newKey)) { + nsCOMPtr<nsIMsgIdentity> identity; + rv = accountManager->CreateIdentity(getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + + identity->GetKey(newKey); + keyHashTable.InsertOrUpdate(key, newKey); + } + + // Replace the prefName with the new key. + prefName.Assign(moz_xstrdup(newKey.get())); + for (uint32_t j = 1; j < keys.Length(); j++) { + prefName.Append('.'); + prefName.Append(keys[j]); + } + pref->prefName = moz_xstrdup(prefName.get()); + } + return NS_OK; +} + +/** + * Transform mail.account branch. Also update mail.accountmanager.accounts at + * the end. + */ +nsresult nsSeamonkeyProfileMigrator::TransformMailAccountsForImport( + nsIPrefService* aPrefService, PBStructArray& aAccounts, + nsIMsgAccountManager* accountManager, + PrefKeyHashTable& identityKeyHashTable, + PrefKeyHashTable& serverKeyHashTable) { + nsTHashMap<nsCStringHashKey, nsCString> keyHashTable; + nsTArray<nsCString> newKeys; + + for (auto pref : aAccounts) { + nsDependentCString prefName(pref->prefName); + nsTArray<nsCString> keys; + ParseString(prefName, '.', keys); + auto key = keys[0]; + if (key == "default") { + continue; + } else if (StringEndsWith(prefName, ".identities"_ns)) { + nsDependentCString identityKey(pref->stringValue); + nsCString newIdentityKey; + if (identityKeyHashTable.Get(identityKey, &newIdentityKey)) { + pref->stringValue = moz_xstrdup(newIdentityKey.get()); + } + } else if (StringEndsWith(prefName, ".server"_ns)) { + nsDependentCString serverKey(pref->stringValue); + nsCString newServerKey; + if (serverKeyHashTable.Get(serverKey, &newServerKey)) { + pref->stringValue = moz_xstrdup(newServerKey.get()); + } + } + + // For every seamonkey account, create a new one to avoid conflicts. + nsCString newKey; + if (!keyHashTable.Get(key, &newKey)) { + accountManager->GetUniqueAccountKey(newKey); + newKeys.AppendElement(newKey); + keyHashTable.InsertOrUpdate(key, newKey); + } + + // Replace the prefName with the new key. + prefName.Assign(moz_xstrdup(newKey.get())); + for (uint32_t j = 1; j < keys.Length(); j++) { + prefName.Append('.'); + prefName.Append(keys[j]); + } + pref->prefName = moz_xstrdup(prefName.get()); + } + + // Append newly create accounts to mail.accountmanager.accounts. + nsCOMPtr<nsIPrefBranch> branch; + nsCString newAccounts; + uint32_t count = newKeys.Length(); + if (count) { + nsresult rv = + aPrefService->GetBranch("mail.accountmanager.", getter_AddRefs(branch)); + NS_ENSURE_SUCCESS(rv, rv); + rv = branch->GetCharPref("accounts", newAccounts); + NS_ENSURE_SUCCESS(rv, rv); + } + for (uint32_t i = 0; i < count; i++) { + newAccounts.Append(','); + newAccounts.Append(newKeys[i]); + } + if (count) { + (void)branch->SetCharPref("accounts", newAccounts); + } + + return NS_OK; +} + +/** + * Transform mail.server branch. + */ +nsresult nsSeamonkeyProfileMigrator::TransformMailServersForImport( + const char* branchName, nsIPrefService* aPrefService, + PBStructArray& aMailServers, nsIMsgAccountManager* accountManager, + PrefKeyHashTable& keyHashTable) { + nsTArray<nsCString> newKeys; + + for (auto pref : aMailServers) { + nsDependentCString prefName(pref->prefName); + nsTArray<nsCString> keys; + ParseString(prefName, '.', keys); + auto key = keys[0]; + if (key == "default") { + continue; + } + nsCString newKey; + bool exists = keyHashTable.Get(key, &newKey); + if (!exists) { + do { + // Since updating prefs.js is batched, GetUniqueServerKey may return the + // previous key. Sleep 500ms and check if the returned key already + // exists to workaround it. + PR_Sleep(PR_MillisecondsToInterval(500)); + accountManager->GetUniqueServerKey(newKey); + } while (newKeys.Contains(newKey)); + newKeys.AppendElement(newKey); + keyHashTable.InsertOrUpdate(key, newKey); + } + + prefName.Assign(moz_xstrdup(newKey.get())); + for (uint32_t j = 1; j < keys.Length(); j++) { + prefName.Append('.'); + prefName.Append(keys[j]); + } + + pref->prefName = moz_xstrdup(prefName.get()); + + // Set `mail.server.serverN.type` so that GetUniqueServerKey next time will + // get a new key. + if (!exists) { + nsCOMPtr<nsIPrefBranch> branch; + nsAutoCString serverTypeKey; + serverTypeKey.Assign(newKey.get()); + serverTypeKey.AppendLiteral(".type"); + nsresult rv = aPrefService->GetBranch(branchName, getter_AddRefs(branch)); + NS_ENSURE_SUCCESS(rv, rv); + (void)branch->SetCharPref(serverTypeKey.get(), "placeholder"_ns); + } + } + return NS_OK; +} + +/** + * Transform mail.smtpserver branch. + * CreateServer will update mail.smtpservers for us. + */ +nsresult nsSeamonkeyProfileMigrator::TransformSmtpServersForImport( + PBStructArray& aServers, PrefKeyHashTable& keyHashTable) { + nsresult rv; + nsCOMPtr<nsISmtpService> smtpService( + do_GetService("@mozilla.org/messengercompose/smtp;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<nsCString> newKeys; + + for (auto pref : aServers) { + nsDependentCString prefName(pref->prefName); + nsTArray<nsCString> keys; + ParseString(prefName, '.', keys); + auto key = keys[0]; + if (key == "default") { + continue; + } + + // For every seamonkey smtp server, create a new one to avoid conflicts. + nsCString newKey; + if (!keyHashTable.Get(key, &newKey)) { + nsCOMPtr<nsISmtpServer> server; + rv = smtpService->CreateServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + char* str; + server->GetKey(&str); + newKey.Assign(str); + newKeys.AppendElement(newKey); + keyHashTable.InsertOrUpdate(key, newKey); + } + + // Replace the prefName with the new key. + prefName.Assign(moz_xstrdup(newKey.get())); + for (uint32_t j = 1; j < keys.Length(); j++) { + prefName.Append('.'); + prefName.Append(keys[j]); + } + pref->prefName = moz_xstrdup(prefName.get()); + } + + return NS_OK; +} + +/** + * Transform ldap_2.servers branch. + */ +nsresult nsSeamonkeyProfileMigrator::TransformAddressbooksForImport( + nsIPrefService* aPrefService, PBStructArray& aAddressbooks, bool aReplace) { + nsTHashMap<nsCStringHashKey, nsCString> keyHashTable; + nsTHashMap<nsCStringHashKey, nsCString> pendingMigrations; + nsTArray<nsCString> newKeys; + nsresult rv; + + nsCOMPtr<nsIPrefBranch> branch; + rv = aPrefService->GetBranch("ldap_2.servers.", getter_AddRefs(branch)); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto pref : aAddressbooks) { + nsDependentCString prefName(pref->prefName); + nsTArray<nsCString> keys; + ParseString(prefName, '.', keys); + auto key = keys[0]; + if (key == "default") { + continue; + } + + nsCString newKey; + if (aReplace) { + newKey.Assign(key); + } else { + // For every addressbook, create a new one to avoid conflicts. + if (!keyHashTable.Get(key, &newKey)) { + uint32_t uniqueCount = 0; + + while (true) { + nsAutoCString filenameKey; + nsAutoCString filename; + filenameKey.Assign(key); + filenameKey.AppendInt(++uniqueCount); + filenameKey.AppendLiteral(".filename"); + nsresult rv = branch->GetCharPref(filenameKey.get(), filename); + if (NS_FAILED(rv)) { + newKey.Assign(key); + newKey.AppendInt(uniqueCount); + (void)branch->SetCharPref(filenameKey.get(), "placeholder"_ns); + break; + } + } + keyHashTable.InsertOrUpdate(key, newKey); + } + } + + // Replace the prefName with the new key. + prefName.Assign(moz_xstrdup(newKey.get())); + for (uint32_t j = 1; j < keys.Length(); j++) { + prefName.Append('.'); + prefName.Append(keys[j]); + + if (j == 1) { + if (keys[j].Equals("dirType")) { + // Make sure we have the right type of directory. + pref->intValue = 101; + } else if (!aReplace && keys[j].Equals("description") && + !strcmp(pref->stringValue, + "chrome://messenger/locale/addressbook/" + "addressBook.properties")) { + // We're importing the default directories, which have localized + // names. The names are tied to the pref's name, which we are + // changing, so the localization will fail. Instead, do the + // localization here and assign it to the directory being copied. + nsCOMPtr<nsIPrefLocalizedString> localizedString; + rv = branch->GetComplexValue(pref->prefName, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(localizedString)); + if (NS_SUCCEEDED(rv)) { + nsString localizedValue; + localizedString->GetData(localizedValue); + pref->stringValue = + moz_xstrdup(NS_ConvertUTF16toUTF8(localizedValue).get()); + } + } else if (keys[j].Equals("filename")) { + // Update the prefs for the new filename of the directory. + nsCString oldFileName(pref->stringValue); + nsCString newFileName(pref->stringValue); + + if (StringEndsWith(newFileName, nsCString("mab"))) { + newFileName.Cut(newFileName.Length() - strlen("mab"), + strlen("mab")); + newFileName.Append("sqlite"); + pref->stringValue = moz_xstrdup(newFileName.get()); + } + + if (!aReplace) { + // Find an unused filename in the destination directory. + nsCOMPtr<nsIFile> targetAddrbook; + mTargetProfile->Clone(getter_AddRefs(targetAddrbook)); + targetAddrbook->Append(NS_ConvertUTF8toUTF16(newFileName)); + nsresult rv = + targetAddrbook->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + nsString leafName; + targetAddrbook->GetLeafName(leafName); + + pref->stringValue = + moz_xstrdup(NS_ConvertUTF16toUTF8(leafName).get()); + } + + if (StringEndsWith(oldFileName, nsCString("sqlite"))) { + nsCOMPtr<nsIFile> oldFile; + mSourceProfile->Clone(getter_AddRefs(oldFile)); + oldFile->Append(NS_ConvertUTF8toUTF16(oldFileName)); + bool exists = false; + oldFile->Exists(&exists); + if (exists) { + // The source directory already has SQLite directories. + // Just copy them. + CopyFile(NS_ConvertUTF8toUTF16(oldFileName), + NS_ConvertUTF8toUTF16(newFileName)); + continue; + } + + oldFileName.Cut(oldFileName.Length() - strlen("sqlite"), + strlen("sqlite")); + oldFileName.Append("mab"); + } + + // Store the directories to be migrated for later. + pendingMigrations.InsertOrUpdate(newKey, oldFileName); + } + } + } + pref->prefName = moz_xstrdup(prefName.get()); + } + + // Write out the preferences and ask the address book manager to reload. + // This initializes the directories using the new prefs we've just set up. + WriteBranch("ldap_2.servers.", aPrefService, aAddressbooks, false); + NOTIFY_OBSERVERS("addrbook-reload", nullptr); + + // Do the migration. + for (auto iter = pendingMigrations.Iter(); !iter.Done(); iter.Next()) { + nsCString dirPrefId = "ldap_2.servers."_ns; + dirPrefId.Append(iter.Key()); + MigrateMABFile(dirPrefId, iter.UserData()); + } + + return NS_OK; +} + +nsresult nsSeamonkeyProfileMigrator::MigrateMABFile( + const nsCString& aDirPrefId, const nsCString& aSourceFileName) { + nsCOMPtr<nsIFile> sourceFile; + mSourceProfile->Clone(getter_AddRefs(sourceFile)); + + sourceFile->Append(NS_ConvertUTF8toUTF16(aSourceFileName)); + bool exists = false; + sourceFile->Exists(&exists); + if (!exists) return NS_OK; + + nsresult rv; + + nsCOMPtr<nsIAbManager> abManager( + do_GetService("@mozilla.org/abmanager;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectoryFromId(aDirPrefId, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, NS_OK); + + rv = ReadMABToDirectory(sourceFile, directory); + + return NS_OK; +} + +void nsSeamonkeyProfileMigrator::ReadBranch(const char* branchName, + nsIPrefService* aPrefService, + PBStructArray& aPrefs) { + // Enumerate the branch + nsCOMPtr<nsIPrefBranch> branch; + aPrefService->GetBranch(branchName, getter_AddRefs(branch)); + + nsTArray<nsCString> prefs; + nsresult rv = branch->GetChildList("", prefs); + if (NS_FAILED(rv)) return; + + for (auto& pref : prefs) { + // Save each pref's value into an array + char* currPref = moz_xstrdup(pref.get()); + int32_t type; + branch->GetPrefType(currPref, &type); + PrefBranchStruct* prefBranch = new PrefBranchStruct; + prefBranch->prefName = currPref; + prefBranch->type = type; + switch (type) { + case nsIPrefBranch::PREF_STRING: { + nsCString str; + rv = branch->GetCharPref(currPref, str); + prefBranch->stringValue = moz_xstrdup(str.get()); + break; + } + case nsIPrefBranch::PREF_BOOL: + rv = branch->GetBoolPref(currPref, &prefBranch->boolValue); + break; + case nsIPrefBranch::PREF_INT: + rv = branch->GetIntPref(currPref, &prefBranch->intValue); + break; + default: + NS_WARNING( + "Invalid Pref Type in " + "nsNetscapeProfileMigratorBase::ReadBranch"); + break; + } + if (NS_SUCCEEDED(rv)) + aPrefs.AppendElement(prefBranch); + else + delete prefBranch; + } +} + +void nsSeamonkeyProfileMigrator::WriteBranch(const char* branchName, + nsIPrefService* aPrefService, + PBStructArray& aPrefs, + bool deallocate) { + // Enumerate the branch + nsCOMPtr<nsIPrefBranch> branch; + aPrefService->GetBranch(branchName, getter_AddRefs(branch)); + + uint32_t count = aPrefs.Length(); + for (uint32_t i = 0; i < count; i++) { + PrefBranchStruct* pref = aPrefs.ElementAt(i); + switch (pref->type) { + case nsIPrefBranch::PREF_STRING: + (void)branch->SetCharPref(pref->prefName, + nsDependentCString(pref->stringValue)); + if (deallocate) { + free(pref->stringValue); + pref->stringValue = nullptr; + } + break; + case nsIPrefBranch::PREF_BOOL: + (void)branch->SetBoolPref(pref->prefName, pref->boolValue); + break; + case nsIPrefBranch::PREF_INT: + (void)branch->SetIntPref(pref->prefName, pref->intValue); + break; + default: + NS_WARNING( + "Invalid Pref Type in " + "nsNetscapeProfileMigratorBase::WriteBranch"); + break; + } + if (deallocate) { + free(pref->prefName); + pref->prefName = nullptr; + delete pref; + } + pref = nullptr; + } + if (deallocate) { + aPrefs.Clear(); + } +} + +nsresult nsSeamonkeyProfileMigrator::DummyCopyRoutine(bool aReplace) { + // place holder function only to fake the UI out into showing some migration + // process. + return NS_OK; +} + +nsresult nsSeamonkeyProfileMigrator::CopyJunkTraining(bool aReplace) { + return aReplace ? CopyFile(FILE_NAME_JUNKTRAINING, FILE_NAME_JUNKTRAINING) + : NS_OK; +} + +nsresult nsSeamonkeyProfileMigrator::CopyPasswords(bool aReplace) { + nsresult rv = NS_OK; + + nsCString signonsFileName; + GetSignonFileName(aReplace, signonsFileName); + + if (signonsFileName.IsEmpty()) return NS_ERROR_FILE_NOT_FOUND; + + nsAutoString fileName; + CopyASCIItoUTF16(signonsFileName, fileName); + if (aReplace) + rv = CopyFile(fileName, fileName); + else { + // don't do anything right now + } + return rv; +} diff --git a/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h b/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h new file mode 100644 index 0000000000..e085e1b2cd --- /dev/null +++ b/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; 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/. */ + +#ifndef seamonkeyprofilemigrator___h___ +#define seamonkeyprofilemigrator___h___ + +#include "nsTHashMap.h" +#include "nsIMailProfileMigrator.h" +#include "nsIMsgAccountManager.h" +#include "nsNetscapeProfileMigratorBase.h" + +class nsIPrefBranch; +class nsIPrefService; + +class nsSeamonkeyProfileMigrator : public nsNetscapeProfileMigratorBase { + public: + NS_DECL_ISUPPORTS_INHERITED + + nsSeamonkeyProfileMigrator(); + + // nsIMailProfileMigrator methods + NS_IMETHOD Migrate(uint16_t aItems, nsIProfileStartup* aStartup, + const char16_t* aProfile) override; + NS_IMETHOD GetMigrateData(const char16_t* aProfile, bool aReplace, + uint16_t* aResult) override; + NS_IMETHOD GetSourceProfiles(nsTArray<nsString>& aResult) override; + NS_IMETHOD GetSourceProfileLocations( + nsTArray<RefPtr<nsIFile>>& aResult) override; + + protected: + virtual ~nsSeamonkeyProfileMigrator(); + nsresult FillProfileDataFromSeamonkeyRegistry(); + nsresult GetSourceProfile(const char16_t* aProfile); + + nsresult MigrateMABFile(const nsCString& aDirPrefId, + const nsCString& aSourceFileName); + + nsresult CopyPreferences(bool aReplace); + nsresult ImportPreferences(uint16_t aItems); + nsresult TransformPreferences(const nsAString& aSourcePrefFileName, + const nsAString& aTargetPrefFileName); + + nsresult DummyCopyRoutine(bool aReplace); + nsresult CopyJunkTraining(bool aReplace); + nsresult CopyPasswords(bool aReplace); + nsresult CopyMailFolders(PBStructArray& aMailServers, + nsIPrefService* aPrefBranch); + nsresult CopySignatureFiles(PBStructArray& aIdentities, + nsIPrefService* aPrefBranch); + + typedef nsTHashMap<nsCStringHashKey, nsCString> PrefKeyHashTable; + + nsresult TransformIdentitiesForImport( + PBStructArray& aIdentities, nsIMsgAccountManager* accountManager, + PrefKeyHashTable& smtpServerKeyHashTable, PrefKeyHashTable& keyHashTable); + nsresult TransformMailAccountsForImport( + nsIPrefService* aPrefService, PBStructArray& aAccounts, + nsIMsgAccountManager* accountManager, + PrefKeyHashTable& identityKeyHashTable, + PrefKeyHashTable& serverKeyHashTable); + nsresult TransformMailServersForImport(const char* branchName, + nsIPrefService* aPrefService, + PBStructArray& aMailServers, + nsIMsgAccountManager* accountManager, + PrefKeyHashTable& keyHashTable); + nsresult TransformSmtpServersForImport(PBStructArray& aServers, + PrefKeyHashTable& keyHashTable); + nsresult TransformAddressbooksForImport(nsIPrefService* aPrefService, + PBStructArray& aAddressbooks, + bool aReplace); + + void ReadBranch(const char* branchName, nsIPrefService* aPrefService, + PBStructArray& aPrefs); + void WriteBranch(const char* branchName, nsIPrefService* aPrefService, + PBStructArray& aPrefs, bool deallocate = true); + + private: + nsTArray<nsString> mProfileNames; + nsTArray<RefPtr<nsIFile>> mProfileLocations; +}; + +#endif |