Bug 856087 - Implement a pluggable store converter (mbox <-> maildir). r=mkmelin,aceman
This commit is contained in:
Родитель
bd91833fac
Коммит
f27dcea3e0
|
@ -0,0 +1,11 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<!ENTITY converterDialog.title "Message Store Type Converter">
|
||||
<!ENTITY converterDialog.continueButton "Continue">
|
||||
<!ENTITY converterDialog.cancelButton "Cancel">
|
||||
<!ENTITY converterDialog.finishButton "Finish">
|
||||
<!ENTITY converterDialog.complete "The conversion is complete. &brandShortName; will now restart.">
|
||||
<!ENTITY converterDialog.error "Conversion failed.">
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# 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/.
|
||||
|
||||
# LOCALIZATION NOTE (converterDialog.warning):
|
||||
# %1$S will be replaced by the name of the account which is going to be converted.
|
||||
# %2$S will be replaced by the format into which the account will be converted.
|
||||
# %3$S will be replaced by $BrandShortName.
|
||||
converterDialog.warning=The messages in the account %1$S will now be converted to the %2$S format. %3$S will restart after the conversion is complete.
|
||||
|
||||
# LOCALIZATION NOTE (converterDialog.message):
|
||||
# %1$S will be replaced by the name of the account which is being converted.
|
||||
# %2$S will be replaced by the format into which the account will be converted.
|
||||
converterDialog.message=Converting the account %1$S to %2$S…
|
||||
|
||||
# LOCALIZATION NOTE (converterDialog.warningForDeferredAccount):
|
||||
# %1$S will be replaced by the name of the deferred account for which migration is initiated by the user.
|
||||
# %2$S will be replaced by the name of the account to which the deferred account is deferred ie the name of the deferred-to account.
|
||||
# %3$S will be replaced by the name of the deferred-to account.
|
||||
# %4$S will be replaced by a comma separated list of names of accounts which are deferred to the deferred-to account.
|
||||
# %5$S will be replaced by a comma separated list of names of accounts which are going to get converted.
|
||||
# %6$S will be replaced by the format into which the accounts will be converted.
|
||||
# %7$S will be replaced by $BrandShortName.
|
||||
converterDialog.warningForDeferredAccount=%1$S is deferred to %2$S. Accounts deferred to %3$S: %4$S. The messages in the accounts %5$S will now be converted to the %6$S format. %7$S will restart after the conversion is complete.
|
||||
|
||||
# LOCALIZATION NOTE (converterDialog.warningForDeferredToAccount):
|
||||
# %1$S will be replaced by the name of the deferred-to account for which migration is initiated by the user and to which other accounts are deferred.
|
||||
# %2$S will be replaced by a comma separated list of names of accounts which are deferred to the deferred-to account.
|
||||
# %3$S will be replaced by a comma separated list of names of accounts which are going to get converted.
|
||||
# %4$S will be replaced by the format into which the accounts will be converted.
|
||||
# %5$S will be replaced by $BrandShortName.
|
||||
converterDialog.warningForDeferredToAccount=Accounts deferred to %1$S: %2$S. The messages in the accounts %3$S will now be converted to the %4$S format. %5$S will restart after the conversion is complete.
|
||||
|
||||
# LOCALIZATION NOTE (converterDialog.messageForDeferredAccount):
|
||||
# %1$S will be replaced by a comma separated list of names of accounts which are being converted.
|
||||
# %2$S will be replaced by the format into which the accounts will be converted.
|
||||
converterDialog.messageForDeferredAccount=Converting the accounts %1$S to %2$S…
|
||||
|
||||
# LOCALIZATION NOTE (converterDialog.percentDone):
|
||||
# %1$S will be replaced by the percentage of conversion that is complete.
|
||||
converterDialog.percentDone=%1$S%% done
|
|
@ -49,6 +49,8 @@
|
|||
locale/@AB_CD@/messenger/am-addressing.dtd (%chrome/messenger/am-addressing.dtd)
|
||||
locale/@AB_CD@/messenger/am-main.dtd (%chrome/messenger/am-main.dtd)
|
||||
locale/@AB_CD@/messenger/am-server-top.dtd (%chrome/messenger/am-server-top.dtd)
|
||||
locale/@AB_CD@/messenger/converterDialog.dtd (%chrome/messenger/converterDialog.dtd)
|
||||
locale/@AB_CD@/messenger/converterDialog.properties (%chrome/messenger/converterDialog.properties)
|
||||
locale/@AB_CD@/messenger/am-identities-list.dtd (%chrome/messenger/am-identities-list.dtd)
|
||||
locale/@AB_CD@/messenger/am-identity-edit.dtd (%chrome/messenger/am-identity-edit.dtd)
|
||||
locale/@AB_CD@/messenger/am-im.dtd (%chrome/messenger/am-im.dtd)
|
||||
|
|
|
@ -8,6 +8,58 @@ ChromeUtils.import("resource:///modules/MailUtils.js");
|
|||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var gServer;
|
||||
var gOriginalStoreType;
|
||||
|
||||
/**
|
||||
* Called when the store type menu is clicked.
|
||||
* @param {Object} aStoreTypeElement - store type menu list element.
|
||||
*/
|
||||
function clickStoreTypeMenu(aStoreTypeElement) {
|
||||
if (aStoreTypeElement.value == gOriginalStoreType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Response from migration dialog modal. If the conversion is complete
|
||||
// 'response.newRootFolder' will hold the path to the new account root folder,
|
||||
// otherwise 'response.newRootFolder' will be null.
|
||||
let response = { newRootFolder: null };
|
||||
// Send 'response' as an argument to converterDialog.xhtml.
|
||||
window.openDialog("converterDialog.xhtml","mailnews:mailstoreconverter",
|
||||
"modal,centerscreen,width=800,height=180", gServer,
|
||||
aStoreTypeElement.value, response);
|
||||
changeStoreType(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert store type to the original store type if converter modal closes
|
||||
* before migration is complete, otherwise change original store type to
|
||||
* currently selected store type.
|
||||
* @param {Object} aResponse - response from migration dialog modal.
|
||||
*/
|
||||
function changeStoreType(aResponse) {
|
||||
if (aResponse.newRootFolder) {
|
||||
// The conversion is complete.
|
||||
// Set local path to the new account root folder which is present
|
||||
// in 'aResponse.newRootFolder'.
|
||||
if (gServer.type == "nntp") {
|
||||
let newRootFolder = aResponse.newRootFolder;
|
||||
let lastSlash = newRootFolder.lastIndexOf("/");
|
||||
let newsrc = newRootFolder.slice(0, lastSlash) + "/newsrc-" +
|
||||
newRootFolder.slice(lastSlash + 1);
|
||||
document.getElementById("nntp.newsrcFilePath").value = newsrc;
|
||||
}
|
||||
|
||||
document.getElementById("server.localPath").value = aResponse.newRootFolder;
|
||||
gOriginalStoreType = document.getElementById("server.storeTypeMenulist")
|
||||
.value;
|
||||
BrowserUtils.restartApplication();
|
||||
} else {
|
||||
// The conversion failed or was cancelled.
|
||||
// Restore selected item to what was selected before conversion.
|
||||
document.getElementById("server.storeTypeMenulist").value =
|
||||
gOriginalStoreType;
|
||||
}
|
||||
}
|
||||
|
||||
function onSave()
|
||||
{
|
||||
|
@ -46,9 +98,12 @@ function onInit(aPageId, aServerId)
|
|||
.getAttribute("value");
|
||||
let targetItem = storeTypeElement.getElementsByAttribute("value", currentStoreID);
|
||||
storeTypeElement.selectedItem = targetItem[0];
|
||||
// disable store type change if store has already been used
|
||||
// Disable store type change if store has not been used yet.
|
||||
storeTypeElement.setAttribute("disabled",
|
||||
gServer.getBoolValue("canChangeStoreType") ? "false" : "true");
|
||||
gServer.getBoolValue("canChangeStoreType") ?
|
||||
"false" : !Services.prefs.getBoolPref("mail.store_conversion_enabled"));
|
||||
// Initialise 'gOriginalStoreType' to the item that was originally selected.
|
||||
gOriginalStoreType = storeTypeElement.value;
|
||||
}
|
||||
|
||||
function onPreInit(account, accountValues)
|
||||
|
|
|
@ -400,7 +400,8 @@
|
|||
<label value="&storeType.label;"
|
||||
accesskey="&storeType.accesskey;"
|
||||
control="server.storeTypeMenulist"/>
|
||||
<menulist id="server.storeTypeMenulist">
|
||||
<menulist id="server.storeTypeMenulist"
|
||||
oncommand="clickStoreTypeMenu(this);">
|
||||
<menupopup id="server.storeTypeMenupopup">
|
||||
<menuitem id="server.mboxStore"
|
||||
value="@mozilla.org/msgstore/berkeleystore;1"
|
||||
|
|
|
@ -3,7 +3,53 @@
|
|||
* 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/. */
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
|
||||
|
||||
var gServer;
|
||||
var gOriginalStoreType;
|
||||
|
||||
/**
|
||||
* Called when the store type menu is clicked.
|
||||
* @param {Object} aStoreTypeElement - store type menu list element.
|
||||
*/
|
||||
function clickStoreTypeMenu(aStoreTypeElement) {
|
||||
if (aStoreTypeElement.value == gOriginalStoreType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Response from migration dialog modal. If the conversion is complete
|
||||
// 'response.newRootFolder' will hold the path to the new account root folder,
|
||||
// otherwise 'response.newRootFolder' will be null.
|
||||
let response = { newRootFolder: null };
|
||||
// Send 'response' as an argument to converterDialog.xhtml.
|
||||
window.openDialog("converterDialog.xhtml","mailnews:mailstoreconverter",
|
||||
"modal,centerscreen,width=800,height=180", gServer,
|
||||
aStoreTypeElement.value, response);
|
||||
changeStoreType(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert store type to the original store type if converter modal closes
|
||||
* before migration is complete, otherwise change original store type to
|
||||
* currently selected store type.
|
||||
* @param {Object} aResponse - response from migration dialog modal.
|
||||
*/
|
||||
function changeStoreType(aResponse) {
|
||||
if (aResponse.newRootFolder) {
|
||||
// The conversion is complete.
|
||||
// Set local path to the new account root folder which is present
|
||||
// in 'aResponse.newRootFolder'.
|
||||
document.getElementById("server.localPath").value = aResponse.newRootFolder;
|
||||
gOriginalStoreType = document.getElementById("server.storeTypeMenulist")
|
||||
.value;
|
||||
BrowserUtils.restartApplication();
|
||||
} else {
|
||||
// The conversion failed or was cancelled.
|
||||
// Restore selected item to what was selected before conversion.
|
||||
document.getElementById("server.storeTypeMenulist").value =
|
||||
gOriginalStoreType;
|
||||
}
|
||||
}
|
||||
|
||||
function onInit(aPageId, aServerId) {
|
||||
|
||||
|
@ -14,9 +60,8 @@ function onInit(aPageId, aServerId) {
|
|||
.getAttribute("value");
|
||||
let targetItem = storeTypeElement.getElementsByAttribute("value", currentStoreID);
|
||||
storeTypeElement.selectedItem = targetItem[0];
|
||||
// disable store type change if store has already been used
|
||||
storeTypeElement.setAttribute("disabled",
|
||||
gServer.getBoolValue("canChangeStoreType") ? "false" : "true");
|
||||
// Initialise 'gOriginalStoreType' to the item that was originally selected.
|
||||
gOriginalStoreType = storeTypeElement.value;
|
||||
}
|
||||
|
||||
function onPreInit(account, accountValues) {
|
||||
|
|
|
@ -51,7 +51,8 @@
|
|||
<label value="&storeType.label;"
|
||||
accesskey="&storeType.accesskey;"
|
||||
control="server.storeTypeMenulist"/>
|
||||
<menulist id="server.storeTypeMenulist">
|
||||
<menulist id="server.storeTypeMenulist"
|
||||
oncommand="clickStoreTypeMenu(this);">
|
||||
<menupopup id="server.storeTypeMenupopup">
|
||||
<menuitem id="server.mboxStore"
|
||||
value="@mozilla.org/msgstore/berkeleystore;1"
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* This file contains functionality for the front-end part of the mail store
|
||||
* type conversion.
|
||||
*/
|
||||
|
||||
ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
|
||||
ChromeUtils.import("resource:///modules/MailUtils.js");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Log.jsm");
|
||||
ChromeUtils.import("resource:///modules/mailstoreConverter.jsm");
|
||||
ChromeUtils.import("resource:///modules/folderUtils.jsm");
|
||||
|
||||
var log = Log.repository.getLogger("MailStoreConverter");
|
||||
// {nsIMsgIncomingServer} server for the account to be migrated.
|
||||
var gServer;
|
||||
// {nsIMsgFolder} account root folder.
|
||||
var gFolder;
|
||||
// 'gResponse.newRootFolder' is path to the new account root folder if migration
|
||||
// is complete, else null.
|
||||
// 'gResponse' is set to the modified response parameter received from
|
||||
// am-server.js.
|
||||
var gResponse;
|
||||
// Array to hold deferred accounts.
|
||||
var gDeferredAccounts = [];
|
||||
// Value of Services.io.offline before migration.
|
||||
var gOriginalOffline;
|
||||
/**
|
||||
* Place account name in migration dialog modal.
|
||||
* @param {nsIMsgIncomingServer} aServer - account server.
|
||||
*/
|
||||
function placeAccountName(aServer) {
|
||||
gOriginalOffline = Services.io.offline;
|
||||
|
||||
let bundle = Cc["@mozilla.org/intl/stringbundle;1"]
|
||||
.getService(Ci.nsIStringBundleService)
|
||||
.createBundle("chrome://messenger/locale/converterDialog.properties");
|
||||
|
||||
let brandShortName = Cc["@mozilla.org/intl/stringbundle;1"]
|
||||
.getService(Ci.nsIStringBundleService)
|
||||
.createBundle("chrome://branding/locale/brand.properties")
|
||||
.GetStringFromName("brandShortName");
|
||||
|
||||
// 'deferredToRootFolder' holds path of rootMsgFolder of account to which
|
||||
// other accounts have been deferred.
|
||||
let deferredToRootFolder = aServer.rootMsgFolder.filePath.path;
|
||||
// String to hold names of deferred accounts separated by commas.
|
||||
let deferredAccountsString = "";
|
||||
// Account to which other accounts have been deferred.
|
||||
let deferredToAccount;
|
||||
// Array of all accounts.
|
||||
let accounts = allAccountsSorted(true);
|
||||
|
||||
for (let account of accounts) {
|
||||
if (account.incomingServer.rootFolder.filePath.path ==
|
||||
deferredToRootFolder) {
|
||||
// Other accounts may be deferred to this account.
|
||||
deferredToAccount = account;
|
||||
} else if (account.incomingServer.rootMsgFolder.filePath.path ==
|
||||
deferredToRootFolder) {
|
||||
// This is a deferred account.
|
||||
gDeferredAccounts.push(account);
|
||||
}
|
||||
}
|
||||
|
||||
// String to hold the names of accounts to be converted separated by commas.
|
||||
let accountsToConvert = "";
|
||||
|
||||
if (gDeferredAccounts.length >= 1) {
|
||||
// Add account names to 'accountsToConvert' and 'deferredAccountsString'.
|
||||
for (let i = 0; i < gDeferredAccounts.length; i++) {
|
||||
if (i < gDeferredAccounts.length - 1) {
|
||||
accountsToConvert += gDeferredAccounts[i].incomingServer.username + ", ";
|
||||
deferredAccountsString += gDeferredAccounts[i].incomingServer.username + ", ";
|
||||
} else {
|
||||
accountsToConvert += gDeferredAccounts[i].incomingServer.username;
|
||||
deferredAccountsString += gDeferredAccounts[i].incomingServer.username;
|
||||
}
|
||||
}
|
||||
|
||||
// Username of Local Folders is "nobody". So it's better to use
|
||||
// its hostname which is "Local Folders".
|
||||
// TODO: maybe test against .key == MailServices.accounts.localFoldersServer.key ?
|
||||
if (deferredToAccount.incomingServer.hostName == "Local Folders") {
|
||||
accountsToConvert += ", " + deferredToAccount.incomingServer.prettyName;
|
||||
} else {
|
||||
accountsToConvert += ", " + deferredToAccount.incomingServer.prettyName;
|
||||
}
|
||||
log.info(accountsToConvert + " will be converted");
|
||||
let storeContractId = Services.prefs.getCharPref("mail.server." +
|
||||
deferredToAccount.incomingServer.key + ".storeContractID");
|
||||
|
||||
if (storeContractId == "@mozilla.org/msgstore/berkeleystore;1") {
|
||||
storeContractId = "maildir";
|
||||
} else {
|
||||
storeContractId = "mbox";
|
||||
}
|
||||
|
||||
// Username of Local Folders is "nobody". So it's better to use
|
||||
// its hostname which is "Local Folders".
|
||||
// TODO: maybe test against .key != MailServices.accounts.localFoldersServer.key ?
|
||||
let deferredToAccountName = deferredToAccount.incomingServer.hostName;
|
||||
if (deferredToAccountName != "Local Folders") {
|
||||
deferredToAccountName = deferredToAccount.incomingServer.username;
|
||||
}
|
||||
|
||||
if (aServer.rootFolder.filePath.path != deferredToRootFolder) {
|
||||
document.getElementById("warningSpan").innerHTML =
|
||||
bundle.formatStringFromName(
|
||||
"converterDialog.warningForDeferredAccount",
|
||||
[aServer.username, deferredToAccountName, deferredToAccountName,
|
||||
deferredAccountsString,
|
||||
accountsToConvert, storeContractId, brandShortName], 7);
|
||||
} else {
|
||||
document.getElementById("warningSpan").innerHTML =
|
||||
bundle.formatStringFromName(
|
||||
"converterDialog.warningForDeferredToAccount",
|
||||
[deferredToAccountName,
|
||||
deferredAccountsString,
|
||||
accountsToConvert, storeContractId, brandShortName], 5);
|
||||
}
|
||||
|
||||
document.getElementById("messageSpan").innerHTML =
|
||||
bundle.formatStringFromName("converterDialog.messageForDeferredAccount",
|
||||
[accountsToConvert, storeContractId], 2);
|
||||
gServer = deferredToAccount.incomingServer;
|
||||
|
||||
} else {
|
||||
// No account is deferred.
|
||||
let storeContractId = Services.prefs.getCharPref(
|
||||
"mail.server." + aServer.key + ".storeContractID");
|
||||
if (storeContractId == "@mozilla.org/msgstore/berkeleystore;1") {
|
||||
storeContractId = "maildir";
|
||||
} else {
|
||||
storeContractId = "mbox";
|
||||
}
|
||||
|
||||
let tempName = aServer.username;
|
||||
if (tempName == "nobody") {
|
||||
tempName = "Local Folders";
|
||||
} else if (!tempName) {
|
||||
tempName = aServer.hostName;
|
||||
}
|
||||
|
||||
document.getElementById("warningSpan").innerHTML =
|
||||
bundle.formatStringFromName("converterDialog.warning",
|
||||
[tempName, storeContractId, brandShortName],
|
||||
3);
|
||||
document.getElementById("messageSpan").innerHTML =
|
||||
bundle.formatStringFromName("converterDialog.message",
|
||||
[tempName, storeContractId], 2);
|
||||
gServer = aServer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the conversion process.
|
||||
* @param {String} aSelectedStoreType - mailstore type selected by user.
|
||||
* @param {Object} aResponse - response from the migration dialog modal.
|
||||
*/
|
||||
function startContinue(aSelectedStoreType, aResponse) {
|
||||
gResponse = aResponse;
|
||||
gFolder = gServer.rootFolder.filePath;
|
||||
|
||||
let bundle = Cc["@mozilla.org/intl/stringbundle;1"]
|
||||
.getService(Ci.nsIStringBundleService)
|
||||
.createBundle("chrome://messenger/locale/converterDialog.properties");
|
||||
|
||||
document.getElementById("progress").addEventListener("progress", function(e) {
|
||||
document.getElementById("progress").value = e.detail;
|
||||
document.getElementById("progressPrcnt").innerHTML =
|
||||
bundle.formatStringFromName("converterDialog.percentDone", [e.detail], 1);
|
||||
});
|
||||
|
||||
document.getElementById("warning").style.display = "none";
|
||||
document.getElementById("progressDiv").style.display = "block";
|
||||
|
||||
// Storing original prefs and root folder path
|
||||
// to revert changes in case of error.
|
||||
let p1 = "mail.server." + gServer.key + ".directory";
|
||||
let p2 = "mail.server." + gServer.key + ".directory-rel";
|
||||
let p3 = "mail.server." + gServer.key + ".newsrc.file";
|
||||
let p4 = "mail.server." + gServer.key + ".newsrc.file-rel";
|
||||
let p5 = "mail.server." + gServer.key + ".storeContractID";
|
||||
|
||||
let originalDirectoryPref = Services.prefs.getCharPref(p1);
|
||||
let originalDirectoryRelPref = Services.prefs.getCharPref(p2);
|
||||
let originalNewsrcFilePref;
|
||||
let originalNewsrcFileRelPref;
|
||||
if (gServer.type == "nntp") {
|
||||
originalNewsrcFilePref = Services.prefs.getCharPref(p3);
|
||||
originalNewsrcFileRelPref = Services.prefs.getCharPref(p4);
|
||||
}
|
||||
let originalStoreContractID = Services.prefs.getCharPref(p5);
|
||||
let originalRootFolderPath = gServer.rootFolder.filePath.path;
|
||||
|
||||
/**
|
||||
* Called when promise returned by convertMailStoreTo() is rejected.
|
||||
* @param {String} aReason - error because of which the promise was rejected.
|
||||
*/
|
||||
function promiseRejected(aReason){
|
||||
log.error("Conversion to '" + mailstoreContractId + "' failed: " +
|
||||
aReason);
|
||||
document.getElementById("messageSpan").style.display = "none";
|
||||
|
||||
document.getElementById("errorSpan").style.display = "block";
|
||||
gResponse.newRootFolder = null;
|
||||
|
||||
// Revert prefs.
|
||||
Services.prefs.setCharPref(p1, originalDirectoryPref);
|
||||
Services.prefs.setCharPref(p2, originalDirectoryRelPref);
|
||||
if (gServer.type == "nntp") {
|
||||
Services.prefs.setCharPref(p3, originalNewsrcFilePref);
|
||||
Services.prefs.setCharPref(p4, originalNewsrcFileRelPref);
|
||||
}
|
||||
Services.prefs.setCharPref(p5, originalStoreContractID);
|
||||
Services.prefs.savePrefFile(null);
|
||||
if (gServer.rootFolder.filePath.path != originalRootFolderPath)
|
||||
gServer.rootFolder.filePath = new FileUtils.File(originalRootFolderPath);
|
||||
Services.io.offline = gOriginalOffline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when promise returned by convertMailStoreTo() is resolved.
|
||||
* @param {String} aVal - path of the new account root folder with which the
|
||||
* promise returned by convertMailStoreTo() is resolved.
|
||||
*/
|
||||
function promiseResolved(aVal) {
|
||||
let newMailstoreContractId = aSelectedStoreType;
|
||||
log.info("Converted to '" + newMailstoreContractId + "' - " + aVal);
|
||||
|
||||
gResponse.newRootFolder = aVal;
|
||||
for (let deferredAccount of gDeferredAccounts) {
|
||||
let defServer = deferredAccount.incomingServer;
|
||||
defServer.rootMsgFolder.filePath = new FileUtils.File(aVal);
|
||||
Services.prefs.setCharPref("mail.server." + defServer.key +
|
||||
".storeContractID", newMailstoreContractId);
|
||||
}
|
||||
|
||||
Services.io.offline = gOriginalOffline;
|
||||
document.getElementById("cancel").style.display = "none";
|
||||
document.getElementById("finish").style.display = "inline-block";
|
||||
document.getElementById("messageSpan").style.display = "none";
|
||||
document.getElementById("completeSpan").style.display = "block";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an mbox folder can be compacted or not.
|
||||
* @param {nsIMsgFolder} aFolder - mbox folder that is to be checked.
|
||||
*/
|
||||
function canCompact(aFolder) {
|
||||
if (aFolder.expungedBytes != 0)
|
||||
return true;
|
||||
if (aFolder.hasSubFolders) {
|
||||
let subFolders = aFolder.subFolders;
|
||||
while (subFolders.hasMoreElements()) {
|
||||
if (canCompact(subFolders.getNext().QueryInterface(Ci.nsIMsgFolder)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compaction (compactAll()) works only for mbox folders which satisfy one of
|
||||
// the following 2 conditions -
|
||||
// 1. Messages are moved out of the folder.
|
||||
// 2. Messages are moved out of some descendant folder of the folder.
|
||||
// If the account root folder can be compacted, start the conversion after
|
||||
// compacting it.
|
||||
if (originalStoreContractID == "@mozilla.org/msgstore/berkeleystore;1"
|
||||
&&
|
||||
canCompact(gServer.rootFolder)) {
|
||||
let urlListener = {
|
||||
OnStartRunningUrl: function (aUrl) {
|
||||
},
|
||||
OnStopRunningUrl: function (aUrl, aExitCode) {
|
||||
let pConvert = convertMailStoreTo(originalStoreContractID,
|
||||
gServer, document.getElementById("progress"));
|
||||
pConvert.then(function(val) {
|
||||
promiseResolved(val);
|
||||
}).catch(function(reason) {
|
||||
promiseRejected(reason);
|
||||
});
|
||||
}
|
||||
};
|
||||
gServer.rootFolder.compactAll(urlListener, null, gServer.type == "imap" ||
|
||||
gServer.type == "nntp");
|
||||
}
|
||||
else {
|
||||
let pConvert = convertMailStoreTo(originalStoreContractID,
|
||||
gServer, document.getElementById("progress"));
|
||||
pConvert.then(function(val) {
|
||||
promiseResolved(val);
|
||||
}).catch(function(reason) {
|
||||
promiseRejected(reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the conversion.
|
||||
* @param {Object} aResponse - response param from the migration dialog modal.
|
||||
*/
|
||||
function cancelConversion(aResponse) {
|
||||
gResponse = aResponse;
|
||||
gResponse.newRootFolder = null;
|
||||
terminateWorkers();
|
||||
Services.io.offline = gOriginalOffline;
|
||||
window.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when "finish" button is clicked.
|
||||
*/
|
||||
function finishConversion() {
|
||||
window.close();
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<!-- 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/.
|
||||
-->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % converterDTD SYSTEM "chrome://messenger/locale/converterDialog.dtd">
|
||||
%converterDTD;
|
||||
]>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>&converterDialog.title;</title>
|
||||
<style>
|
||||
.controls {
|
||||
width: 97%;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
#progressDiv {
|
||||
margin: 1%;
|
||||
}
|
||||
#progress {
|
||||
width: 80%;
|
||||
}
|
||||
</style>
|
||||
<script src="converterDialog.js" />
|
||||
</head>
|
||||
|
||||
<body onload="placeAccountName(window.arguments[0]);">
|
||||
<div id="warning">
|
||||
<p id="warningSpan">[The messages in the account XXX will now be converted to the maildir format.]</p>
|
||||
<div class="controls">
|
||||
<button onclick="cancelConversion(window.arguments[2]);">
|
||||
&converterDialog.cancelButton;
|
||||
</button>
|
||||
<button onclick="startContinue(window.arguments[1], window.arguments[2]);">
|
||||
&converterDialog.continueButton;
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="progressDiv" hidden="hidden">
|
||||
<p id="messageSpan">[Messages go here]</p>
|
||||
<p id="completeSpan" hidden="hidden">&converterDialog.complete;</p>
|
||||
<p id="errorSpan" hidden="hidden">&converterDialog.error;</p>
|
||||
<p>
|
||||
<progress id="progress" value="0" max="100"></progress>
|
||||
<span id="progressPrcnt">0% done</span>
|
||||
</p>
|
||||
<div class="controls">
|
||||
<button id="cancel" onclick="cancelConversion(window.arguments[1]);">
|
||||
&converterDialog.cancelButton;
|
||||
</button>
|
||||
<button id="finish" onclick="finishConversion();" hidden="hidden">
|
||||
&converterDialog.finishButton;
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,179 @@
|
|||
/* 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/. */
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
ChromeUtils.import("resource://testing-common/mailnews/PromiseTestUtils.jsm");
|
||||
ChromeUtils.import("resource:///modules/mailstoreConverter.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Log.jsm");
|
||||
ChromeUtils.import("resource:///modules/folderUtils.jsm");
|
||||
|
||||
// XXX: merge into test_converter.js
|
||||
|
||||
var log = Log.repository.getLogger("MailStoreConverter");
|
||||
|
||||
Services.prefs.setCharPref("mail.serverDefaultStoreContractID",
|
||||
"@mozilla.org/msgstore/berkeleystore;1");
|
||||
|
||||
// No. of messages/files and folders copied.
|
||||
var gProgressValue = 0;
|
||||
var gMsgHdrs = [];
|
||||
// {nsIMsgLocalMailFolder} folder carrying messages for the pop server.
|
||||
var gInbox;
|
||||
// {nsIMsgIncomingServer} server for first deferred pop account.
|
||||
var gServer1;
|
||||
// {nsIMsgIncomingServer} server for second deferred pop account.
|
||||
var gServer2;
|
||||
// {nsIMsgIncomingServer} server to convert.
|
||||
var gServer;
|
||||
|
||||
var copyListenerWrap = {
|
||||
SetMessageKey: function(aKey) {
|
||||
let hdr = gInbox.GetMessageHeader(aKey);
|
||||
gMsgHdrs.push({hdr: hdr, ID: hdr.messageId});
|
||||
},
|
||||
OnStopCopy: function(aStatus) {
|
||||
// Check: message successfully copied.
|
||||
Assert.equal(aStatus, 0);
|
||||
}
|
||||
};
|
||||
|
||||
var EventTarget = function () {
|
||||
this.dispatchEvent = function (event) {
|
||||
if (event.type == "progress") {
|
||||
log.trace("Progress: " + event.detail);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function copyFileMessage(file, destFolder, isDraftOrTemplate)
|
||||
{
|
||||
let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
|
||||
MailServices.copy.CopyFileMessage(file, destFolder, null, isDraftOrTemplate,
|
||||
0, "", listener, null);
|
||||
return listener.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that conversion worked for the given source.
|
||||
* @param source - mbox source directory
|
||||
* @param target - maildir target directory
|
||||
*/
|
||||
function checkConversion(source, target) {
|
||||
let sourceContents = source.directoryEntries;
|
||||
|
||||
while (sourceContents.hasMoreElements()) {
|
||||
let sourceContent = sourceContents.getNext().QueryInterface(Ci.nsIFile);
|
||||
let sourceContentName = sourceContent.leafName;
|
||||
let ext = sourceContentName.substr(-4);
|
||||
let targetFile = FileUtils.File(OS.Path.join(target.path,sourceContentName));
|
||||
log.debug("Checking path: " + targetFile.path);
|
||||
if (ext == ".dat") {
|
||||
Assert.ok(targetFile.exists());
|
||||
} else if (sourceContent.isDirectory()) {
|
||||
Assert.ok(targetFile.exists());
|
||||
checkConversion(sourceContent, targetFile);
|
||||
} else if (ext != ".msf") {
|
||||
Assert.ok(targetFile.exists());
|
||||
let cur = FileUtils.File(OS.Path.join(targetFile.path,"cur"));
|
||||
Assert.ok(cur.exists());
|
||||
let tmp = FileUtils.File(OS.Path.join(targetFile.path,"tmp"));
|
||||
Assert.ok(tmp.exists());
|
||||
if (targetFile.leafName == "Inbox") {
|
||||
let curContents = cur.directoryEntries;
|
||||
let curContentsCount = 0;
|
||||
while (curContents.hasMoreElements()) {
|
||||
let curContent = curContents.getNext();
|
||||
curContentsCount++;
|
||||
}
|
||||
Assert.equal(curContentsCount, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
localAccountUtils.loadLocalMailAccount();
|
||||
|
||||
// Set up two deferred pop accounts.
|
||||
gServer1 = MailServices.accounts.createIncomingServer("test1", "localhost1",
|
||||
"pop3");
|
||||
gServer2 = MailServices.accounts.createIncomingServer("test2", "localhost2",
|
||||
"pop3");
|
||||
var accountPop1 = MailServices.accounts.createAccount();
|
||||
var accountPop2 = MailServices.accounts.createAccount();
|
||||
|
||||
// Set incoming servers.
|
||||
accountPop1.incomingServer = gServer1;
|
||||
gServer1.QueryInterface(Ci.nsIPop3IncomingServer);
|
||||
gServer1.valid = true;
|
||||
accountPop2.incomingServer = gServer2;
|
||||
gServer2.QueryInterface(Ci.nsIPop3IncomingServer);
|
||||
gServer2.valid = true;
|
||||
|
||||
// Defer accounts to Local Folders.
|
||||
gServer1.deferredToAccount = localAccountUtils.msgAccount.key;
|
||||
gServer2.deferredToAccount = localAccountUtils.msgAccount.key;
|
||||
|
||||
// 'gServer1' should be deferred. Get the path of the root folder to which
|
||||
// other accounts are deferred.
|
||||
ok(gServer1.rootFolder.filePath.path !=
|
||||
gServer1.rootMsgFolder.filePath.path);
|
||||
let deferredToRootFolder = gServer1.rootMsgFolder.filePath.path;
|
||||
|
||||
// Account to which other accounts have been deferred.
|
||||
let deferredToAccount;
|
||||
// String to hold names of accounts to convert.
|
||||
let accountsToConvert = "";
|
||||
|
||||
let accounts = allAccountsSorted(true);
|
||||
for (let account of accounts) {
|
||||
if (account.incomingServer.rootFolder.filePath.path ==
|
||||
deferredToRootFolder) {
|
||||
// Other accounts may be deferred to this account.
|
||||
deferredToAccount = account;
|
||||
} else if (account.incomingServer.rootMsgFolder.filePath.path ==
|
||||
deferredToRootFolder) {
|
||||
// This is a deferred account.
|
||||
accountsToConvert += account.incomingServer.username + ", ";
|
||||
}
|
||||
}
|
||||
|
||||
accountsToConvert = accountsToConvert +
|
||||
deferredToAccount.incomingServer.username;
|
||||
log.info(accountsToConvert + " will be converted");
|
||||
|
||||
gInbox = localAccountUtils.inboxFolder;
|
||||
gServer = deferredToAccount.incomingServer;
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* setupMessages() {
|
||||
let msgFile = do_get_file("../../../data/bugmail10");
|
||||
// Add 1000 messages to the "Inbox" folder.
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
yield copyFileMessage(msgFile, gInbox, false);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function testMaildirConversion() {
|
||||
let mailstoreContractId = Services.prefs.getCharPref(
|
||||
"mail.server." + gServer.key + ".storeContractID");
|
||||
|
||||
do_test_pending();
|
||||
let pConverted = convertMailStoreTo(mailstoreContractId, gServer,
|
||||
new EventTarget());
|
||||
let originalRootFolder = gServer.rootFolder.filePath;
|
||||
|
||||
pConverted.then(function(val) {
|
||||
log.debug("Conversion done: " + originalRootFolder.path + " => " + val);
|
||||
let newRootFolder = gServer.rootFolder.filePath;
|
||||
checkConversion(originalRootFolder, newRootFolder);
|
||||
do_test_finished();
|
||||
}).catch (function(reason) {
|
||||
log.error("Conversion failed: " + reason.error);
|
||||
ok(false); //Fail the test!
|
||||
});
|
||||
});
|
|
@ -0,0 +1,133 @@
|
|||
/* 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/. */
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
ChromeUtils.import("resource://testing-common/mailnews/PromiseTestUtils.jsm");
|
||||
ChromeUtils.import("resource:///modules/mailstoreConverter.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Log.jsm");
|
||||
|
||||
var log = Log.repository.getLogger("MailStoreConverter");
|
||||
Services.prefs.setCharPref("mail.serverDefaultStoreContractID",
|
||||
"@mozilla.org/msgstore/berkeleystore;1");
|
||||
|
||||
var gMsgHdrs = [];
|
||||
// {nsIMsgLocalMailFolder} folder carrying messages for the pop server.
|
||||
var gInbox;
|
||||
|
||||
// {nsIMsgAccount} Account to convert.
|
||||
var gAccount;
|
||||
// Server for the account to convert.
|
||||
var gServer;
|
||||
|
||||
var copyListenerWrap = {
|
||||
SetMessageKey: function(aKey) {
|
||||
let hdr = gInbox.GetMessageHeader(aKey);
|
||||
gMsgHdrs.push({hdr: hdr, ID: hdr.messageId});
|
||||
},
|
||||
OnStopCopy: function(aStatus) {
|
||||
// Check: message successfully copied.
|
||||
Assert.equal(aStatus, 0);
|
||||
}
|
||||
};
|
||||
|
||||
var EventTarget = function () {
|
||||
this.dispatchEvent = function(aEvent) {
|
||||
if (aEvent.type == "progress") {
|
||||
log.trace("Progress: " + aEvent.detail);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function copyFileMessage(aFile, aDestFolder, aIsDraftOrTemplate)
|
||||
{
|
||||
let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
|
||||
MailServices.copy.CopyFileMessage(aFile, aDestFolder, null, aIsDraftOrTemplate,
|
||||
0, "", listener, null);
|
||||
return listener.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that conversion worked for the given source.
|
||||
* @param aSource - mbox source directory
|
||||
* @param aTarget - maildir target directory
|
||||
*/
|
||||
function checkConversion(aSource, aTarget) {
|
||||
let sourceContents = aSource.directoryEntries;
|
||||
|
||||
while (sourceContents.hasMoreElements()) {
|
||||
let sourceContent = sourceContents.getNext().QueryInterface(Ci.nsIFile);
|
||||
let sourceContentName = sourceContent.leafName;
|
||||
let ext = sourceContentName.substr(-4);
|
||||
let targetFile = FileUtils.File(OS.Path.join(aTarget.path,sourceContentName));
|
||||
log.debug("Checking path: " + targetFile.path);
|
||||
if (ext == ".dat") {
|
||||
Assert.ok(targetFile.exists());
|
||||
} else if (sourceContent.isDirectory()) {
|
||||
Assert.ok(targetFile.exists());
|
||||
checkConversion(sourceContent, targetFile);
|
||||
} else if (ext != ".msf") {
|
||||
Assert.ok(targetFile.exists());
|
||||
let cur = FileUtils.File(OS.Path.join(targetFile.path,"cur"));
|
||||
Assert.ok(cur.exists());
|
||||
let tmp = FileUtils.File(OS.Path.join(targetFile.path,"tmp"));
|
||||
Assert.ok(tmp.exists());
|
||||
if (targetFile.leafName == "Inbox") {
|
||||
let curContents = cur.directoryEntries;
|
||||
let curContentsCount = 0;
|
||||
while (curContents.hasMoreElements()) {
|
||||
let curContent = curContents.getNext();
|
||||
curContentsCount++;
|
||||
}
|
||||
// We had 1000 msgs in the old folder. We should have that after
|
||||
// conversion too.
|
||||
Assert.equal(curContentsCount, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
localAccountUtils.loadLocalMailAccount();
|
||||
|
||||
// {nsIMsgIncomingServer} pop server for the test.
|
||||
gServer = MailServices.accounts.createIncomingServer("test","localhost",
|
||||
"pop3");
|
||||
gAccount = MailServices.accounts.createAccount();
|
||||
gAccount.incomingServer = gServer;
|
||||
gServer.QueryInterface(Ci.nsIPop3IncomingServer);
|
||||
gServer.valid = true;
|
||||
|
||||
gInbox = gAccount.incomingServer.rootFolder
|
||||
.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* setupMessages() {
|
||||
let msgFile = do_get_file("../../../data/bugmail10");
|
||||
|
||||
// Add 1000 messages to the "Inbox" folder.
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
yield copyFileMessage(msgFile, gInbox, false);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function testMaildirConversion() {
|
||||
let mailstoreContractId = Services.prefs.getCharPref(
|
||||
"mail.server." + gServer.key + ".storeContractID");
|
||||
do_test_pending();
|
||||
let pConverted = convertMailStoreTo(mailstoreContractId, gServer,
|
||||
new EventTarget());
|
||||
let originalRootFolder = gServer.rootFolder.filePath;
|
||||
pConverted.then(function(aVal) {
|
||||
log.debug("Conversion done: " + originalRootFolder.path + " => " + aVal);
|
||||
let newRootFolder = gServer.rootFolder.filePath;
|
||||
checkConversion(originalRootFolder, newRootFolder);
|
||||
do_test_finished();
|
||||
}).catch(function(aReason) {
|
||||
log.error("Conversion Failed: " + aReason.error);
|
||||
ok(false); // Fail the test!
|
||||
});
|
||||
});
|
|
@ -20,6 +20,8 @@ support-files = nodelist_test.xml data/*
|
|||
[test_bug514945.js]
|
||||
[test_compactFailure.js]
|
||||
[test_compactColumnSave.js]
|
||||
[test_mailstoreConverter.js]
|
||||
[test_converterDeferredAccount.js]
|
||||
[test_copyChaining.js]
|
||||
[test_copyThenMoveManual.js]
|
||||
[test_copyToInvalidDB.js]
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
/* 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/. */
|
||||
|
||||
self.importScripts("resource://gre/modules/osfile.jsm");
|
||||
self.addEventListener("message", function(e) {
|
||||
try {
|
||||
// {String} sourceFile - path to file or directory encountered.
|
||||
var sourceFile = e.data[1];
|
||||
// {String} dest - path to directory in which the new files or directories
|
||||
// need to be created.
|
||||
var dest = e.data[0];
|
||||
// {String} destFile - name of the file or directory encountered.
|
||||
var destFile = e.data[2];
|
||||
var mailstoreContractId = e.data[3];
|
||||
var tmpDir = e.data[4];
|
||||
var serverType = e.data[5];
|
||||
var stat = OS.File.stat(sourceFile);
|
||||
const constNoOfBytes = 10000000;
|
||||
|
||||
if (mailstoreContractId == "@mozilla.org/msgstore/maildirstore;1" &&
|
||||
stat.isDir && sourceFile.substr(-4) != ".sbd") {
|
||||
// Mailstore type is maildir and a maildir msg folder is encountered.
|
||||
|
||||
// Create a directory with path 'dest'.
|
||||
OS.File.makeDir(dest, {from: tmpDir});
|
||||
|
||||
// If the file with path 'dest/destFile' does not exist, create it,
|
||||
// open it for writing. This is the mbox msg file with the same name as
|
||||
// 'sourceFile'.
|
||||
let mboxFile;
|
||||
if (!OS.File.exists(OS.Path.join(dest,destFile))) {
|
||||
mboxFile = OS.File.open(OS.Path.join(dest,destFile), {write: true,
|
||||
create: true}, {});
|
||||
}
|
||||
|
||||
// If length of 'e.data' is greater than 6, we know that e.data carries
|
||||
// maildir msg file names.
|
||||
if (e.data.length > 6) {
|
||||
for(let msgCount = 0; msgCount < e.data.length - 6; msgCount++) {
|
||||
let n = e.data[msgCount + 6];
|
||||
// Open the file 'sourceFile/cur/msgFile' for reading.
|
||||
let msgFileOpen = OS.File.open(OS.Path.join(sourceFile, "cur", n));
|
||||
mboxFile.write(msgFileOpen.read());
|
||||
msgFileOpen.close();
|
||||
|
||||
// Send a message to "mailstoreConverter.jsm" indicating that a
|
||||
// msg was copied. This would indicate "progress" for both imap and
|
||||
// pop accounts if mailstore type is maildir and the no. of
|
||||
// msgs in the account is greater than zero.
|
||||
self.postMessage(["copied", OS.Path.join(sourceFile, "cur", n)]);
|
||||
}
|
||||
}
|
||||
|
||||
mboxFile.close();
|
||||
|
||||
// Send a message to "mailstoreConverter.jsm" indicating that an mbox msg
|
||||
// file was created. This would indicate "progress" for both imap and pop
|
||||
// accounts if mailstore type is maildir and the no. of messages in
|
||||
// the account is 0.
|
||||
self.postMessage(["file", sourceFile, e.data.length]);
|
||||
|
||||
} else if (stat.isDir) {
|
||||
// A directory is encountered. This is a ".sbd" directory.
|
||||
|
||||
// Create a directory with path 'dest'.
|
||||
OS.File.makeDir(dest, {from: tmpDir});
|
||||
|
||||
// Create a directory with same name as 'sourceFile' in the
|
||||
// directory whose path is in 'dest'.
|
||||
OS.File.makeDir(OS.Path.join(dest, destFile), {from: tmpDir});
|
||||
|
||||
// Send message to "mailstoreConverter.jsm" indicating that a directory
|
||||
// was created.
|
||||
// This would indicate "progress" for an imap account but not for a pop
|
||||
// account if the number of messages in the pop account is more than 0 and
|
||||
// mailstore type is mbox.
|
||||
// This would indicate "progress" for a pop account if the number of
|
||||
// messages in the pop account is 0 and the mailstore type is
|
||||
// mbox.
|
||||
// This would indicate "progress" for pop or imap account if the noumber
|
||||
// of messages in the account is 0.
|
||||
self.postMessage(["dir", sourceFile]);
|
||||
|
||||
} else {
|
||||
// If a file is encountered, then if it is a .dat file, copy the
|
||||
// file to the directory whose path is in 'dest'.
|
||||
// For Local Folders, pop3, and movemail accounts, when the .msf files
|
||||
// are copied, something goes wrong with the .msf files and the messages
|
||||
// don't show up. Thunderbird automatically creates .msf files. So to
|
||||
// resolve this, .msf files are not copied for Local Folders, pop3 and
|
||||
// movemail accounts.
|
||||
let ext = sourceFile.substr(-4);
|
||||
if ((ext == ".msf") || (ext == ".dat")) {
|
||||
if (ext == ".dat" || (serverType == "imap" || serverType == "nntp")) {
|
||||
// If the directory with path 'dest' does not exist, create it.
|
||||
if (!OS.File.exists(dest)) {
|
||||
OS.File.makeDir(dest, {from: tmpDir});
|
||||
}
|
||||
OS.File.copy(sourceFile, OS.Path.join(dest,destFile));
|
||||
}
|
||||
|
||||
// Send a message to "mailstoreConverter.jsm" indicating that a .msf or
|
||||
// .dat file was copied.
|
||||
// This is used to indicate progress on IMAP accounts if mailstore
|
||||
// type is mbox.
|
||||
// This is used to indicate progress on pop accounts if the no. of msgs
|
||||
// in the account is 0 and mailstore type is mbox.
|
||||
// This is used to indicate progress on pop and imap accounts if the
|
||||
// no. of msgs in the account is 0 and mailstore type is maildir.
|
||||
self.postMessage(["msfdat", sourceFile]);
|
||||
|
||||
} else if (mailstoreContractId != "@mozilla.org/msgstore/maildirstore;1") {
|
||||
// An mbox message file is encountered.
|
||||
|
||||
// Create a directory with path 'dest'.
|
||||
OS.File.makeDir(dest, {from: tmpDir});
|
||||
|
||||
// Create a directory with same name as the file encountered in the
|
||||
// directory with path 'dest'.
|
||||
// In this directory create a directory with name "cur" and a directory
|
||||
// with name "tmp".
|
||||
OS.File.makeDir(OS.Path.join(dest, destFile));
|
||||
OS.File.makeDir(OS.Path.join(dest, destFile, "cur"));
|
||||
OS.File.makeDir(OS.Path.join(dest, destFile, "tmp"));
|
||||
|
||||
let decoder = new TextDecoder();
|
||||
let encoder = new TextEncoder();
|
||||
|
||||
// File to which the message is to be copied.
|
||||
let targetFile = null;
|
||||
// Get a timestamp for file name.
|
||||
let name = Date.now();
|
||||
// No. of bytes to be read from the source file.
|
||||
// Needs to be a large size to read in chunks.
|
||||
let noOfBytes = constNoOfBytes;
|
||||
// 'text' holds the string that was read.
|
||||
let text = null;
|
||||
// Index of last match in 'text'.
|
||||
let lastMatchIndex;
|
||||
// Current position in the source file before reading bytes from it.
|
||||
let position;
|
||||
// New position in the source file after reading bytes from it.
|
||||
let nextPos;
|
||||
// New length of the text read from source file.
|
||||
let nextLen;
|
||||
// Position in the file after reading the bytes in the previous
|
||||
// iteration.
|
||||
let prevPos = 0;
|
||||
// Length of the text read from source file in the previous
|
||||
// iteration.
|
||||
let prevLen = 0;
|
||||
// Bytes read from the source file are decoded into a string and
|
||||
// assigned to 'textNew'.
|
||||
let textNew;
|
||||
|
||||
// Read the file. Since the files can be large, we read it in chunks.
|
||||
let sourceFileOpen = OS.File.open(sourceFile);
|
||||
while (true) {
|
||||
position = sourceFileOpen.getPosition();
|
||||
let array = sourceFileOpen.read(noOfBytes);
|
||||
textNew = decoder.decode(array);
|
||||
nextPos = sourceFileOpen.getPosition();
|
||||
nextLen = textNew.length;
|
||||
|
||||
if (nextPos == prevPos && nextLen == prevLen) {
|
||||
// Reached the last message in the source file.
|
||||
if (text !== null) {
|
||||
// Array to hold indices of "From -" matches found within 'text'.
|
||||
let lastPos = [];
|
||||
// Regular expression to find "From - " at beginning of lines.
|
||||
let regexpLast = /^(From - )/gm;
|
||||
let resultLast = regexpLast.exec(text);
|
||||
while (resultLast !== null) {
|
||||
lastPos[lastPos.length] = resultLast.index;
|
||||
resultLast = regexpLast.exec(text);
|
||||
}
|
||||
|
||||
// Create a maildir message file in 'dest/destFile/cur/'
|
||||
// and open it for writing.
|
||||
targetFile = OS.File.open(OS.Path.join(dest, destFile, "cur",
|
||||
name.toString()), {write: true, create: true}, {});
|
||||
|
||||
// Extract the text in 'text' between 'lastPos[0]' ie the
|
||||
// index of the first "From - " match and the end of 'text'.
|
||||
targetFile.write(encoder.encode(text.substring(lastPos[0],
|
||||
text.length)));
|
||||
targetFile.close();
|
||||
|
||||
// Send a message indicating that a message was copied.
|
||||
// This indicates progress for a pop account if the no. of msgs
|
||||
// in the account is more than 0 and mailstore type is mbox.
|
||||
self.postMessage(["copied", name, position]);
|
||||
}
|
||||
|
||||
break;
|
||||
} else {
|
||||
// We might have more messages in the source file.
|
||||
prevPos = nextPos;
|
||||
prevLen = nextLen;
|
||||
text = textNew;
|
||||
}
|
||||
|
||||
// Array to hold indices of "From -" matches found within 'text'.
|
||||
let msgPos = [];
|
||||
// Regular expression to find "From - " at beginning of lines.
|
||||
let regexp = /^(From - )/gm;
|
||||
let result = regexp.exec(text);
|
||||
while (result !== null) {
|
||||
msgPos[msgPos.length] = result.index;
|
||||
result = regexp.exec(text);
|
||||
}
|
||||
|
||||
if (msgPos.length > 1) {
|
||||
// More than one "From - " match is found.
|
||||
noOfBytes = constNoOfBytes;
|
||||
for (let i = 0; i < msgPos.length - 1; i++) {
|
||||
// Create and open a new file in 'dest/destFile/cur'
|
||||
// to hold the next mail.
|
||||
targetFile = OS.File.open(OS.Path.join(dest, destFile, "cur",
|
||||
name.toString()), {write: true, create: true}, {});
|
||||
// Extract the text lying between consecutive indices, encode
|
||||
// it and write it.
|
||||
targetFile.write(encoder.encode(text.substring(msgPos[i],
|
||||
msgPos[i + 1])));
|
||||
targetFile.close();
|
||||
|
||||
// Send a message indicating that a mail was copied.
|
||||
// This indicates progress for a pop account if the no. of msgs
|
||||
// in the account is more than 0 and mailstore type is mbox.
|
||||
self.postMessage(["copied", name, position + msgPos[i],
|
||||
position + msgPos[i + 1]]);
|
||||
|
||||
// Increment 'name' to get a new file name.
|
||||
// Cannot use Date.now() because it is possible to get the
|
||||
// same timestamp as before.
|
||||
name++;
|
||||
|
||||
// Set index of the (i+1)th "From - " match found in 'text'.
|
||||
lastMatchIndex = msgPos[i + 1];
|
||||
}
|
||||
|
||||
// Now 'lastMatchIndex' holds the index of the last match found in
|
||||
// 'text'. So we move the position in the file to 'position +
|
||||
// lastMatchIndex' from the beginning of the file.
|
||||
// This ensures that the next 'text' starts from "From - "
|
||||
// and that there is at least 1 match everytime.
|
||||
sourceFileOpen.setPosition(position + lastMatchIndex,
|
||||
OS.File.POS_START);
|
||||
} else {
|
||||
// If 1 match is found increase the no. of bytes to be extracted by
|
||||
// 1000000 and move the position in the file to 'position', i.e. the
|
||||
// position in the file before reading the bytes.
|
||||
sourceFileOpen.setPosition(position, OS.File.POS_START);
|
||||
noOfBytes = noOfBytes + 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message indicating that a message file was encountered.
|
||||
// This indicates progress for an imap account if mailstore type is
|
||||
// mbox.
|
||||
// This indicates progress for a pop account if mailstore type is
|
||||
// mbox and the no. of msgs in the account is 0.
|
||||
self.postMessage(["file", sourceFile, textNew.length]);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// We try-catch the error because otherwise the error from File.OS is
|
||||
// not properly propagated back to the worker error handling.
|
||||
throw new Error(e);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,512 @@
|
|||
/* 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 EXPORTED_SYMBOLS = ["convertMailStoreTo", "terminateWorkers"];
|
||||
|
||||
ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
|
||||
ChromeUtils.import("resource:///modules/MailUtils.js");
|
||||
ChromeUtils.import("resource:///modules/mailServices.js");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Log.jsm");
|
||||
|
||||
var log = Log.repository.getLogger("MailStoreConverter");
|
||||
log.level = Log.Level.Debug;
|
||||
log.addAppender(new Log.DumpAppender(new Log.BasicFormatter()));
|
||||
|
||||
// Array to hold workers.
|
||||
var gConverterWorkerArray = [];
|
||||
|
||||
/**
|
||||
* Creates "Converter" folder in tmp dir, moves the folder hierarchy of the
|
||||
* account root folder creating the same folder hierarchy in Converter
|
||||
* folder in tmp dir, copies the .msf and .dat files to proper places in
|
||||
* Converter folder, parses the mbox files and creates corresponding folders
|
||||
* and maildir files in proper places in "Converter" folder and returns a
|
||||
* promise.
|
||||
* @param aMailstoreContractId - account mailstore contract id
|
||||
* @param {nsIMsgIncominserver} aServer - server for the account
|
||||
* @param aEventTarget - target on which the "progress"
|
||||
* event will be dispatched
|
||||
* @return {Promise} - new root folder path of the converted
|
||||
* server.
|
||||
*/
|
||||
function convertMailStoreTo(aMailstoreContractId, aServer, aEventTarget) {
|
||||
// {nsIMsgfolder} account root folder.
|
||||
var accountRootFolder = aServer.rootFolder.filePath;
|
||||
// {nsIFile} tmp dir.
|
||||
var tmpDir = FileUtils.getDir("TmpD", [], false);
|
||||
// Array to hold path to the Converter folder in tmp dir.
|
||||
var pathArray;
|
||||
// No. of messages that have been copied in case of a pop account, movemail
|
||||
// account, Local Folders account or any account with maildir mailstore type
|
||||
// having at least 1 message.
|
||||
// No. of files and folders that have been copied in case of a pop account,
|
||||
// movemail account, Local Folders account or any account with maildir
|
||||
// mailstore type having 0 messages.
|
||||
// No. of files and folders that have been copied in case of an imap account
|
||||
// or an nntp account.
|
||||
var progressValue = 0;
|
||||
// No. of files and folders in original account root folder for imap account
|
||||
// if mailstore type is mbox, or an nntp account.
|
||||
// No. of files and folders in original account root folder for a pop
|
||||
// account, Local Folders account or movemail account if no. of msgs is 0
|
||||
// and mailstore type is mbox.
|
||||
// No. of files and folders in any non nntp account if no. of msgs is
|
||||
// 0 and mailstore type is maildir.
|
||||
// No. of messages in a pop account, Local Folders account or movemail
|
||||
// account if no. of msgs is more than 0 and mailstore type is mbox.
|
||||
// No. of messages in any non nntp account if no. of msgs is more than 0 and
|
||||
// mailstore type is maildir.
|
||||
var count = 0;
|
||||
// If there are zero msgs in the account "zeroMessages" is true else it is
|
||||
// false.
|
||||
var zeroMessages = false;
|
||||
|
||||
// No. of files and folders in original account root folder for imap account.
|
||||
// We use a progress bar to show the status of the conversion process.
|
||||
// So, we need a value as the maximum value of the progress bar to measure the
|
||||
// progress.
|
||||
// During the conversion there are three kinds of files or folders that can be
|
||||
// encountered.
|
||||
// 1. A directory - This simply requires a directory to be created in the right
|
||||
// place. So this a single step.
|
||||
// 2. A .msf or a .dat file - This simply requires the file to be copied to the
|
||||
// right place. This too is a single step.
|
||||
// 3. A message file - A message file contains several messages and each one
|
||||
// needs to be copied to a separate file in the right
|
||||
// place. So parsing a parsing a message file consists of
|
||||
// several steps.
|
||||
//
|
||||
// So, it's the parsing of message files that is actually time consuming and
|
||||
// dealing with a directory, .msf, .dat file takes very little time.
|
||||
//
|
||||
// So it makes more sense to measure progress as the no. of messages copied.
|
||||
// But for an imap account, getTotalMessages(true) does not give the no. of
|
||||
// messages actually present in the account root folder, but gives the no. of
|
||||
// messages shown on Thunderbird which is less than the no. of messages
|
||||
// actually present in the account root folder. So can't use that.
|
||||
//
|
||||
// But we still need a way to measure progress for an imap account.
|
||||
// So we measure progress by the total no. of files and folders in the account
|
||||
// root folder and we increment the value of the progress bar every time a
|
||||
// .msf, .dat, or a message file or a directory is encountered during
|
||||
// conversion.
|
||||
|
||||
/**
|
||||
* Count no. of files and folders in the account root folder for imap
|
||||
* accounts.
|
||||
* @param {nsIMsgFolder} aFolder - account root folder.
|
||||
*/
|
||||
var countImapFileFolders = function(aFolder) {
|
||||
var count = 0;
|
||||
var contents = aFolder.directoryEntries;
|
||||
while (contents.hasMoreElements()) {
|
||||
var content = contents.getNext()
|
||||
.QueryInterface(Ci.nsIFile);
|
||||
if (content.isDirectory()) {
|
||||
count = count + 1 + countImapFileFolders(content);
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the no. of msgs in account root folder if the mailstore type is
|
||||
* maildir.
|
||||
* @param {nsIMsgFolder} aFolder - account root folder.
|
||||
*/
|
||||
var countMaildirMsgs = function(aFolder) {
|
||||
var count = 0;
|
||||
var contents = aFolder.directoryEntries;
|
||||
while (contents.hasMoreElements()) {
|
||||
var content = contents.getNext().QueryInterface(Ci.nsIFile);
|
||||
if (content.isDirectory() && content.leafName.substr(-4) != ".sbd") {
|
||||
var cur = FileUtils.File(OS.Path.join(content.path,"cur"));
|
||||
var curContents = cur.directoryEntries;
|
||||
while (curContents.hasMoreElements()) {
|
||||
curContents.getNext();
|
||||
count++;
|
||||
}
|
||||
} else if (content.isDirectory()) {
|
||||
count = count + countMaildirMsgs(content);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the no. of files and folders in account root folder if the mailstore
|
||||
* type is maildir and the no. of msgs in the account is 0.
|
||||
* @param {nsIMsgFolder} aFolder - account root folder.
|
||||
*/
|
||||
var countMaildirZeroMsgs = function(aFolder) {
|
||||
var count = 0;
|
||||
var contents = aFolder.directoryEntries;
|
||||
while (contents.hasMoreElements()) {
|
||||
var content = contents.getNext().QueryInterface(Ci.nsIFile);
|
||||
// Count maildir msg folders as 1 and don't count "cur" and "tmp"
|
||||
// folders.
|
||||
if ((content.isDirectory() && content.leafName.substr(-4) != ".sbd") ||
|
||||
!content.isDirectory()) {
|
||||
count++;
|
||||
} else if (content.isDirectory()) {
|
||||
count = count + 1 + countMaildirMsgs(content);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
var isMaildir = (aMailstoreContractId == "@mozilla.org/msgstore/maildirstore;1");
|
||||
|
||||
var conversionOk; // Resolve callback function.
|
||||
var conversionFailed; // Reject callback function.
|
||||
|
||||
/**
|
||||
* Moves the folder hierarchy of the account root folder creating the same
|
||||
* folder hierarchy in Converter folder in tmp dir, copies the .msf
|
||||
* and .dat files to proper places in Converter folder, parses the mbox
|
||||
* files and creates corresponding folders and maildir files in proper
|
||||
* places in "Converter" folder and resolves the promise returned by
|
||||
* convertmailStoreTo().
|
||||
*
|
||||
* @param {nsIFile} aFolder - account root folder. Folder from where the
|
||||
* files and directories are to be migrated.
|
||||
* @param {nsIFile} aDestPath - "Converter" folder. Folder into which the
|
||||
* files directories are to be migrated.
|
||||
*/
|
||||
var subDir = function(aFolder, aDestPath) {
|
||||
let contents = aFolder.directoryEntries;
|
||||
while (contents.hasMoreElements()) {
|
||||
let content = contents.getNext()
|
||||
.QueryInterface(Ci.nsIFile);
|
||||
let converterWorker = new ChromeWorker(
|
||||
"resource:///modules/converterWorker.js");
|
||||
gConverterWorkerArray.push(converterWorker);
|
||||
log.debug("Processing " + content.path + " => : " + aDestPath.path +
|
||||
" - "+ count + "items");
|
||||
|
||||
// Data to be passed to the worker. Initially "dataArray" contains
|
||||
// path of the directory in which the files and directories are to be
|
||||
// migrated, path of the file or directory encountered, name of the file
|
||||
// or directory encountered and the mailstore type, path of tmp dir,
|
||||
// server type.
|
||||
let dataArray = [
|
||||
aDestPath.path,
|
||||
content.path,
|
||||
content.leafName,
|
||||
aMailstoreContractId,
|
||||
tmpDir.path,
|
||||
aServer.type
|
||||
];
|
||||
|
||||
if (content.isDirectory()) {
|
||||
// If it is not a ".sbd" directory we know that it is a maildir msg
|
||||
// folder.
|
||||
if (content.leafName.substr(-4) != ".sbd") {
|
||||
// Array to hold unsorted list of maildir msg filenames.
|
||||
let dataArrayUnsorted = [];
|
||||
// "cur" directory inside the maildir msg folder.
|
||||
let cur = FileUtils.File(OS.Path.join(content.path,"cur"));
|
||||
// Msg files inside "cur" directory.
|
||||
let msgs = cur.directoryEntries;
|
||||
|
||||
while (msgs.hasMoreElements()) {
|
||||
// Add filenames as integers into 'dataArrayUnsorted'.
|
||||
let msg = msgs.getNext()
|
||||
.QueryInterface(Ci.nsIFile);
|
||||
dataArrayUnsorted.push(parseInt(msg.leafName));
|
||||
}
|
||||
dataArrayUnsorted.sort()
|
||||
// Add the maildir msg filenames into 'dataArray' in a sorted order.
|
||||
for (let elem of dataArrayUnsorted) {
|
||||
dataArray.push(elem.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send 'dataArray' to the worker.
|
||||
converterWorker.postMessage(dataArray);
|
||||
converterWorker.addEventListener("message", function(e) {
|
||||
var responseWorker = e.data[0];
|
||||
log.debug("Type of file or folder encountered: " + e.data);
|
||||
|
||||
// Dispatch the "progress" event on the event target and increment
|
||||
// "progressValue" every time.
|
||||
//
|
||||
// mbox:
|
||||
// - IMAP: a file or folder is copied.
|
||||
// This is because we cannot get the no. of messages actually present
|
||||
// in an imap account so we need some other way to measure the
|
||||
// progress.
|
||||
// - POP: a msg is copied if the no. of msgs in the account is more than
|
||||
// 0. A file or folder is copied if the no. of msgs in the account is 0.
|
||||
// - NNTP: a file or folder is copied.
|
||||
// - MOVEMAIL: Same as POP.
|
||||
// - NONE (LOCAL FOLDERS): Same as POP.
|
||||
//
|
||||
// maildir:
|
||||
// - A msg is copied if the no. of msgs in the account is more than 0.
|
||||
// - A file or folder is copied if the no. of msgs in the account is 0.
|
||||
let popOrLocalOrMoveMailOrMaildir =
|
||||
aServer.type == "pop3" || aServer.type == "none" ||
|
||||
aServer.type == "movemail" || isMaildir;
|
||||
if (((responseWorker == "copied" || (responseWorker != "copied" && zeroMessages))
|
||||
&& popOrLocalOrMoveMailOrMaildir)
|
||||
||
|
||||
(responseWorker != "copied" && !popOrLocalOrMoveMailOrMaildir)
|
||||
||
|
||||
(responseWorker != "copied" && aServer.type == "nntp")
|
||||
) {
|
||||
progressValue++;
|
||||
log.debug("Progress: " + progressValue);
|
||||
|
||||
let event = new Event("progress");
|
||||
event.detail = parseInt((progressValue/count) * 100);
|
||||
aEventTarget.dispatchEvent(event);
|
||||
if (progressValue == count) {
|
||||
log.info("Migration completed. Migrated " + count + " items");
|
||||
|
||||
// Migration is complete, get path of parent of account root
|
||||
// folder into "parentPath" check if Converter folder already
|
||||
// exists in "parentPath". If yes, remove it.
|
||||
let lastSlash = accountRootFolder.path.lastIndexOf("/");
|
||||
let parentPath = accountRootFolder.parent.path;
|
||||
log.info("Path to parent folder of account root" +
|
||||
" folder: " + parentPath);
|
||||
|
||||
let parent = new FileUtils.File(parentPath);
|
||||
log.info("Path to parent folder of account root folder: " +
|
||||
parent.path);
|
||||
|
||||
var converterFolder = new FileUtils.File(OS.Path.join(parent.path,
|
||||
dir.leafName));
|
||||
if (converterFolder.exists()) {
|
||||
log.info("Converter folder exists in " + parentPath +
|
||||
". Removing already existing folder");
|
||||
converterFolder.remove(true);
|
||||
}
|
||||
|
||||
// Move Converter folder into the parent of account root folder.
|
||||
try {
|
||||
dir.moveTo(parent, dir.leafName);
|
||||
// {nsIFile} new account root folder.
|
||||
var newRootFolder = new FileUtils.File(OS.Path.join(parent.path,
|
||||
dir.leafName));
|
||||
log.info("Path to new account root folder: " +
|
||||
newRootFolder.path);
|
||||
} catch (e) {
|
||||
// Cleanup.
|
||||
log.error(e);
|
||||
var newRootFolder = new FileUtils.File(OS.Path.join(parent.path,
|
||||
dir.leafName));
|
||||
log.error("Trying to remove converter folder: " +
|
||||
newRootFolder.path);
|
||||
newRootFolder.remove(true);
|
||||
conversionFailed(e);
|
||||
}
|
||||
|
||||
// If the account is imap then copy the msf file for the original
|
||||
// root folder and rename the copy with the name of the new root
|
||||
// folder.
|
||||
if (aServer.type != "pop3" && aServer.type != "none" &&
|
||||
aServer.type != "movemail") {
|
||||
let converterFolderMsf = new FileUtils.File(OS.Path.join(
|
||||
parent.path,dir.leafName + ".msf"));
|
||||
if (converterFolderMsf.exists()) {
|
||||
converterFolderMsf.remove(true);
|
||||
}
|
||||
|
||||
let oldRootFolderMsf = new FileUtils.File(OS.Path.join(
|
||||
parent.path,accountRootFolder.leafName + ".msf"));
|
||||
if (oldRootFolderMsf.exists()) {
|
||||
oldRootFolderMsf.copyTo(parent, converterFolderMsf.leafName);
|
||||
}
|
||||
}
|
||||
|
||||
if (aServer.type == "nntp") {
|
||||
let converterFolderNewsrc = new FileUtils.File(OS.Path.join(
|
||||
parent.path,"newsrc-" + dir.leafName));
|
||||
if (converterFolderNewsrc.exists()) {
|
||||
converterFolderNewsrc.remove(true);
|
||||
}
|
||||
let oldNewsrc = new FileUtils.File(OS.Path.join(parent.path,
|
||||
"newsrc-" + accountRootFolder.leafName));
|
||||
if (oldNewsrc.exists()) {
|
||||
oldNewsrc.copyTo(parent, converterFolderNewsrc.leafName);
|
||||
}
|
||||
}
|
||||
|
||||
aServer.rootFolder.filePath = newRootFolder;
|
||||
aServer.localPath = newRootFolder;
|
||||
log.info("Path to account root folder: " +
|
||||
aServer.rootFolder.filePath.path);
|
||||
|
||||
// Set various preferences.
|
||||
let p1 = "mail.server." + aServer.key + ".directory";
|
||||
let p2 = "mail.server." + aServer.key + ".directory-rel";
|
||||
let p3 = "mail.server." + aServer.key + ".newsrc.file";
|
||||
let p4 = "mail.server." + aServer.key + ".newsrc.file-rel";
|
||||
let p5 = "mail.server." + aServer.key + ".storeContractID";
|
||||
|
||||
Services.prefs.setCharPref(p1, newRootFolder.path);
|
||||
log.info(p1 + ": " + newRootFolder.path)
|
||||
|
||||
// The directory-rel pref is of the form "[ProfD]Mail/pop.gmail.com
|
||||
// " (pop accounts) or "[ProfD]ImapMail/imap.gmail.com" (imap
|
||||
// accounts) ie the last slash "/" is followed by the root folder
|
||||
// name. So, replace the old root folder name that follows the last
|
||||
// slash with the new root folder name to set the correct value of
|
||||
// directory-rel pref.
|
||||
let directoryRel = Services.prefs.getCharPref(p2);
|
||||
lastSlash = directoryRel.lastIndexOf("/");
|
||||
directoryRel = directoryRel.slice(0, lastSlash) + "/" +
|
||||
newRootFolder.leafName;
|
||||
Services.prefs.setCharPref(p2, directoryRel);
|
||||
log.info(p2 + ": " + directoryRel);
|
||||
|
||||
if (aServer.type == "nntp") {
|
||||
let newNewsrc = FileUtils.File(OS.Path.join(parent.path,
|
||||
"newsrc-" + newRootFolder.leafName));
|
||||
Services.prefs.setCharPref(p3, newNewsrc.path);
|
||||
|
||||
// The newsrc.file-rel pref is of the form "[ProfD]News/newsrc-
|
||||
// news.mozilla.org" ie the last slash "/" is followed by the
|
||||
// newsrc file name. So, replace the old newsrc file name that
|
||||
// follows the last slash with the new newsrc file name to set
|
||||
// the correct value of newsrc.file-rel pref.
|
||||
let newsrcRel = Services.prefs.getCharPref(p4);
|
||||
lastSlash = newsrcRel.lastIndexOf("/");
|
||||
newsrcRel = newsrcRel.slice(0, lastSlash) + "/" +
|
||||
newNewsrc.leafName;
|
||||
Services.prefs.setCharPref(p4, newsrcRel);
|
||||
log.info(p4 + ": " + newsrcRel);
|
||||
}
|
||||
|
||||
Services.prefs.setCharPref(p5, isMaildir ?
|
||||
"@mozilla.org/msgstore/berkeleystore;1" :
|
||||
"@mozilla.org/msgstore/maildirstore;1");
|
||||
|
||||
Services.prefs.savePrefFile(null);
|
||||
log.info("Conversion done!");
|
||||
|
||||
// Resolve the promise with the path of the new account root
|
||||
// folder.
|
||||
conversionOk(newRootFolder.path);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
converterWorker.addEventListener("error", function(e) {
|
||||
let reasonString =
|
||||
"Error at " + e.filename + ":" + e.lineno + " - " + e.message;
|
||||
log.error(reasonString);
|
||||
terminateWorkers();
|
||||
// Cleanup.
|
||||
log.error("Trying to remove converter folder: " +
|
||||
aDestPath.path);
|
||||
aDestPath.remove(true);
|
||||
conversionFailed(e.message);
|
||||
});
|
||||
|
||||
if (content.isDirectory()) {
|
||||
if (content.leafName.substr(-4) == ".sbd") {
|
||||
let dirNew = new FileUtils.File(OS.Path.join(aDestPath.path,
|
||||
content.leafName));
|
||||
subDir(content, dirNew);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Converter folder exists in tmp dir, removes it and creates a new
|
||||
* "Converter" folder.
|
||||
* @param {nsIFile} aFolder - account root folder.
|
||||
*/
|
||||
var createTmpConverterFolder = function(aFolder) {
|
||||
let tmpFolder;
|
||||
switch (aMailstoreContractId) {
|
||||
case "@mozilla.org/msgstore/maildirstore;1": {
|
||||
if (aFolder.leafName.substr(-8) == "-maildir") {
|
||||
tmpFolder = new FileUtils.File(OS.Path.join(tmpDir.path,
|
||||
aFolder.leafName.substr(0, aFolder.leafName.length - 8) + "-mbox"));
|
||||
} else {
|
||||
tmpFolder = new FileUtils.File(OS.Path.join(tmpDir.path,
|
||||
aFolder.leafName + "-mbox"));
|
||||
}
|
||||
|
||||
if (tmpFolder.exists()) {
|
||||
log.info("Temporary Converter folder " + tmpFolder.path +
|
||||
"exists in tmp dir. Removing it");
|
||||
tmpFolder.remove(true);
|
||||
}
|
||||
return FileUtils.getDir("TmpD", [tmpFolder.leafName], true);
|
||||
}
|
||||
|
||||
case "@mozilla.org/msgstore/berkeleystore;1": {
|
||||
if (aFolder.leafName.substr(-5) == "-mbox") {
|
||||
tmpFolder = new FileUtils.File(OS.Path.join(tmpDir.path,
|
||||
aFolder.leafName.substr(0, aFolder.leafName.length - 5) +
|
||||
"-maildir"));
|
||||
} else {
|
||||
tmpFolder = new FileUtils.File(OS.Path.join(tmpDir.path,
|
||||
aFolder.leafName + "-maildir"));
|
||||
}
|
||||
|
||||
if (tmpFolder.exists()) {
|
||||
log.info("Temporary Converter folder " + tmpFolder.path +
|
||||
"exists in tmp dir. Removing it");
|
||||
tmpFolder.remove(true);
|
||||
}
|
||||
return FileUtils.getDir("TmpD", [tmpFolder.leafName], true);
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error("Unexpected mailstoreContractId: " +
|
||||
aMailstoreContractId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMaildir && aServer.type != "nntp") {
|
||||
count = countMaildirMsgs(accountRootFolder);
|
||||
if (count == 0) {
|
||||
count = countMaildirZeroMsgs(accountRootFolder);
|
||||
zeroMessages = true;
|
||||
}
|
||||
} else if (aServer.type == "pop3" ||
|
||||
aServer.type == "none" || // none: Local Folders.
|
||||
aServer.type == "movemail") {
|
||||
count = aServer.rootFolder.getTotalMessages(true);
|
||||
if (count == 0) {
|
||||
count = countImapFileFolders(accountRootFolder);
|
||||
zeroMessages = true;
|
||||
}
|
||||
} else if (aServer.type == "imap" || aServer.type == "nntp") {
|
||||
count = countImapFileFolders(accountRootFolder);
|
||||
}
|
||||
|
||||
// Go offline before conversion, so there aren't messages coming in during
|
||||
// the process.
|
||||
Services.io.offline = true;
|
||||
let dir = createTmpConverterFolder(accountRootFolder);
|
||||
return new Promise(function(resolve, reject) {
|
||||
conversionOk = resolve;
|
||||
conversionFailed = reject;
|
||||
subDir(accountRootFolder, dir);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate all workers.
|
||||
*/
|
||||
function terminateWorkers() {
|
||||
for (let worker of gConverterWorkerArray) {
|
||||
worker.terminate();
|
||||
}
|
||||
}
|
|
@ -49,6 +49,7 @@ SOURCES += [
|
|||
|
||||
EXTRA_JS_MODULES += [
|
||||
'ABQueryUtils.jsm',
|
||||
'converterWorker.js',
|
||||
'errUtils.js',
|
||||
'folderUtils.jsm',
|
||||
'hostnameUtils.jsm',
|
||||
|
@ -58,6 +59,7 @@ EXTRA_JS_MODULES += [
|
|||
'JXON.js',
|
||||
'mailnewsMigrator.js',
|
||||
'mailServices.js',
|
||||
'mailstoreConverter.jsm',
|
||||
'msgDBCacheManager.js',
|
||||
'OAuth2.jsm',
|
||||
'OAuth2Providers.jsm',
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
/* 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/. */
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
ChromeUtils.import("resource:///modules/mailstoreConverter.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Log.jsm");
|
||||
|
||||
Services.prefs.setCharPref("mail.serverDefaultStoreContractID",
|
||||
"@mozilla.org/msgstore/berkeleystore;1");
|
||||
|
||||
load("../../../resources/logHelper.js");
|
||||
load("../../../resources/asyncTestUtils.js");
|
||||
load("../../../resources/messageGenerator.js");
|
||||
load("../../../resources/alertTestUtils.js");
|
||||
|
||||
var log = Log.repository.getLogger("MailStoreConverter");
|
||||
|
||||
var FileUtils = ChromeUtils.import("resource://gre/modules/FileUtils.jsm")
|
||||
.FileUtils;
|
||||
|
||||
// Globals
|
||||
var gRootFolder;
|
||||
|
||||
var gMsgFile1 = do_get_file("../../../data/bugmail10");
|
||||
|
||||
// Copied straight from the example files
|
||||
var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
|
||||
var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
|
||||
var gMsgId3 = "4849BF7B.2030800@example.com";
|
||||
var gMsgId4 = "bugmail7.m47LtAEf007542@mrapp51.mozilla.org";
|
||||
var gMsgId5 = "bugmail6.m47LtAEf007542@mrapp51.mozilla.org";
|
||||
|
||||
function checkConversion(aSource, aTarget) {
|
||||
let sourceContents = aSource.directoryEntries;
|
||||
|
||||
while (sourceContents.hasMoreElements()) {
|
||||
let sourceContent = sourceContents.getNext().QueryInterface(Ci.nsIFile);
|
||||
let sourceContentName = sourceContent.leafName;
|
||||
let ext = sourceContentName.slice(-4);
|
||||
let targetFile = FileUtils.File(OS.Path.join(aTarget.path,sourceContentName));
|
||||
log.debug("Checking path: " + targetFile.path);
|
||||
|
||||
if (ext == ".msf" || ext == ".dat") {
|
||||
Assert.ok(targetFile.exists());
|
||||
} else if (sourceContent.isDirectory()) {
|
||||
Assert.ok(targetFile.exists());
|
||||
checkConversion(sourceContent, targetFile);
|
||||
} else {
|
||||
Assert.ok(targetFile.exists());
|
||||
let cur = FileUtils.File(OS.Path.join(targetFile.path,"cur"));
|
||||
Assert.ok(cur.exists());
|
||||
let tmp = FileUtils.File(OS.Path.join(targetFile.path,"tmp"));
|
||||
Assert.ok(tmp.exists());
|
||||
if (targetFile.leafName == "INBOX") {
|
||||
let curContents = cur.directoryEntries;
|
||||
let curContentsCount = 0;
|
||||
while (curContents.hasMoreElements()) {
|
||||
let curContent = curContents.getNext();
|
||||
curContentsCount += 1;
|
||||
}
|
||||
Assert.equal(curContentsCount, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var EventTarget = function () {
|
||||
this.dispatchEvent = function (aEvent) {
|
||||
if (aEvent.type == "progress") {
|
||||
log.trace("Progress: " + aEvent.detail);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Adds some messages directly to a mailbox (eg new mail).
|
||||
function addMessagesToServer(aMessages, aMailbox)
|
||||
{
|
||||
// For every message we have, we need to convert it to a file:/// URI
|
||||
aMessages.forEach(function (message)
|
||||
{
|
||||
let URI =
|
||||
Services.io.newFileURI(message.file).QueryInterface(Ci.nsIFileURL);
|
||||
message.spec = URI.spec;
|
||||
});
|
||||
|
||||
// Create the imapMessages and store them on the mailbox.
|
||||
aMessages.forEach(function (message)
|
||||
{
|
||||
aMailbox.addMessage(new imapMessage(message.spec, aMailbox.uidnext++, []));
|
||||
});
|
||||
}
|
||||
|
||||
function setup() {
|
||||
setupIMAPPump();
|
||||
|
||||
gRootFolder = IMAPPump.incomingServer.rootFolder;
|
||||
// These hacks are required because we've created the inbox before
|
||||
// running initial folder discovery, and adding the folder bails
|
||||
// out before we set it as verified online, so we bail out, and
|
||||
// then remove the INBOX folder since it's not verified.
|
||||
IMAPPump.inbox.hierarchyDelimiter = '/';
|
||||
IMAPPump.inbox.verifiedAsOnlineFolder = true;
|
||||
|
||||
// Add a couple of messages to the INBOX.
|
||||
// This is synchronous.
|
||||
addMessagesToServer([{file: gMsgFile1, messageId: gMsgId1},
|
||||
{file: gMsgFile1, messageId: gMsgId4},
|
||||
{file: gMsgFile1, messageId: gMsgId2},
|
||||
{file: gMsgFile1, messageId: gMsgId5}],
|
||||
IMAPPump.daemon.getMailbox("INBOX"), IMAPPump.inbox);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
setup();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* downloadForOffline() {
|
||||
// Download for offline use.
|
||||
dump("Downloading for offline use\n");
|
||||
let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
|
||||
IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
|
||||
yield promiseUrlListener.promise;
|
||||
});
|
||||
|
||||
add_task(function convert() {
|
||||
let mailstoreContractId = Services.prefs.getCharPref(
|
||||
"mail.server." + IMAPPump.incomingServer.key + ".storeContractID");
|
||||
let eventTarget = new EventTarget();
|
||||
let pConverted = convertMailStoreTo(mailstoreContractId,
|
||||
IMAPPump.incomingServer, eventTarget);
|
||||
do_test_pending();
|
||||
let originalRootFolder = IMAPPump.incomingServer.rootFolder.filePath;
|
||||
pConverted.then(function(val) {
|
||||
log.debug("Conversion done: " + originalRootFolder.path + " => " + val);
|
||||
let newRootFolder = IMAPPump.incomingServer.rootFolder.filePath;
|
||||
checkConversion(originalRootFolder, newRootFolder);
|
||||
let newRootFolderMsf = FileUtils.File(newRootFolder.path + ".msf");
|
||||
Assert.ok(newRootFolderMsf.exists());
|
||||
do_test_finished();
|
||||
}).catch (function(reason) {
|
||||
log.error("Conversion Failed: " + reason.error);
|
||||
do_test_finished();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function teardown() {
|
||||
gRootFolder = null;
|
||||
teardownIMAPPump();
|
||||
});
|
|
@ -7,6 +7,7 @@ tail =
|
|||
[test_bug460636.js]
|
||||
[test_chunkLastLF.js]
|
||||
[test_compactOfflineStore.js]
|
||||
[test_converterImap.js]
|
||||
[test_copyThenMove.js]
|
||||
[test_customCommandReturnsFetchResponse.js]
|
||||
[test_dod.js]
|
||||
|
|
|
@ -65,6 +65,8 @@ messenger.jar:
|
|||
content/messenger/SmtpServerEdit.js (base/prefs/content/SmtpServerEdit.js)
|
||||
content/messenger/smtpEditOverlay.xul (base/prefs/content/smtpEditOverlay.xul)
|
||||
content/messenger/smtpEditOverlay.js (base/prefs/content/smtpEditOverlay.js)
|
||||
content/messenger/converterDialog.xhtml (base/prefs/content/converterDialog.xhtml)
|
||||
content/messenger/converterDialog.js (base/prefs/content/converterDialog.js)
|
||||
content/messenger/removeAccount.xul (base/prefs/content/removeAccount.xul)
|
||||
content/messenger/removeAccount.js (base/prefs/content/removeAccount.js)
|
||||
content/messenger/msgSelectOfflineFolders.xul (base/content/msgSelectOfflineFolders.xul)
|
||||
|
|
|
@ -536,9 +536,16 @@ pref("mail.server.default.offline_download",true);
|
|||
// -1 means no limit, no purging of offline stores.
|
||||
pref("mail.server.default.autosync_max_age_days", -1);
|
||||
|
||||
// can we change the store type?
|
||||
// Can we change the store type without conversion? (=has the store been used)
|
||||
pref("mail.server.default.canChangeStoreType", false);
|
||||
|
||||
// Store conversion (mbox <-> maildir)
|
||||
#ifndef RELEASE_OR_BETA
|
||||
pref("mail.store_conversion_enabled", true);
|
||||
#else
|
||||
pref("mail.store_conversion_enabled", false);
|
||||
#endif
|
||||
|
||||
// This is the default store contractID for newly created servers.
|
||||
// We don't use mail.server.default because we want to ensure that the
|
||||
// store contract id is always written out to prefs.js
|
||||
|
|
Загрузка…
Ссылка в новой задаче