Backed out changeset 67c263d7544b (bug 1491228) for test failures in test_converterImap.js. a=backout DONTBUILD
authorJorg K <jorgk@jorgk.com>
Fri, 11 Jan 2019 10:41:15 +0100
changeset 33310 6c36a1eb8323
parent 33309 389458d07470
child 33311 277f7d1fd552
push id2368
push userclokep@gmail.com
push dateMon, 28 Jan 2019 21:12:50 +0000
treeherdercomm-beta@56d23c07d815 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1491228
backs out67c263d7544b
Backed out changeset 67c263d7544b (bug 1491228) for test failures in test_converterImap.js. a=backout DONTBUILD
mailnews/base/test/unit/test_mailstoreConverter.js
mailnews/base/util/converterWorker.js
mailnews/base/util/mailstoreConverter.jsm
mailnews/test/data/mbox_mboxrd
mailnews/test/data/mbox_modern
mailnews/test/data/mbox_unquoted
mailnews/test/data/readme.txt
--- a/mailnews/base/test/unit/test_mailstoreConverter.js
+++ b/mailnews/base/test/unit/test_mailstoreConverter.js
@@ -7,105 +7,127 @@ ChromeUtils.import("resource://gre/modul
 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();
 
-  add_task(async function() {
-    await doMboxTest("test1", "../../../data/mbox_modern", 2);
-    await doMboxTest("test2", "../../../data/mbox_mboxrd", 2);
-    await doMboxTest("test3", "../../../data/mbox_unquoted", 2);
-    // Ideas for more tests:
-    // - check a really big mbox
-    // - check with really huge message (larger than one chunk)
-    // - check mbox with "From " line on chunk boundary
-    // - add tests for maildir->mbox conversion
-    // - check that round-trip conversion preserves messages
-    // - check that conversions preserve message body (ie that the
-    //   "From " line escaping scheme is reversable)
-  });
+  // {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();
 }
 
-/**
- * Helper to create a server, account and inbox, and install an
- * mbox file.
- * @return {nsIMsgIncomingServer} a server.
- */
-function setupServer(srvName, mboxFilename) {
-  // {nsIMsgIncomingServer} pop server for the test.
-  let server = MailServices.accounts.createIncomingServer(srvName,"localhost",
-                                                       "pop3");
-  let account= MailServices.accounts.createAccount();
-  account.incomingServer = server;
-  server.QueryInterface(Ci.nsIPop3IncomingServer);
-  server.valid = true;
-
-  let inbox = account.incomingServer.rootFolder
-    .getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
+add_task(async function setupMessages() {
+  let msgFile = do_get_file("../../../data/bugmail10");
 
-  // install the mbox file
-  let mboxFile = do_get_file(mboxFilename);
-  mboxFile.copyTo( inbox.filePath.parent, inbox.filePath.leafName)
-
-  // TODO: is there some way to make folder rescan the mbox?
-  // We don't need it for this, but would be nice to do things properly.
-  return server;
-}
-
-
-/**
- * Perform an mbox->maildir conversion test.
- *
- * @param {string} srvName - A unique server name to use for the test.
- * @param {string} mboxFilename - mbox file to install and convert.
- * @param {number} expectCnt - Number of messages expected.
- * @return {nsIMsgIncomingServer} a server.
- */
-async function doMboxTest(srvName, mboxFilename, expectCnt) {
-  // set up an account+server+inbox and copy in the test mbox file
-  let server = setupServer(srvName, mboxFilename);
+  // Add 1000 messages to the "Inbox" folder.
+  for (let i = 0; i < 1000; i++) {
+    await copyFileMessage(msgFile, gInbox, false);
+  }
+});
 
+add_task(function testMaildirConversion() {
   let mailstoreContractId = Services.prefs.getCharPref(
-    "mail.server." + server.key + ".storeContractID");
-
-  let aVal = await convertMailStoreTo(
-    mailstoreContractId, server, new EventTarget());
-  // NOTE: convertMailStoreTo() will suppress exceptions in it's
-  // worker, which makes unittest failures trickier to read...
-
-  let originalRootFolder = server.rootFolder.filePath;
-
-  // Converted. Now find resulting Inbox/cur directory so
-  // we can count the messages there.
-
-  let inbox = server.rootFolder
-    .getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
-  // NOTE: the conversion updates the path of the root folder,
-  // but _not_ the path of the inbox...
-  // Ideally, we'd just use inbox.filePath here, but
-  // instead we'll have compose the path manually.
-
-  let curDir = server.rootFolder.filePath;
-  curDir.append(inbox.filePath.leafName);
-  curDir.append("cur");
-
-  // Sanity check.
-  Assert.ok(curDir.isDirectory(), "'cur' directory created" );
-
-  // Check number of messages in Inbox/cur is what we expect.
-  let cnt = 0;
-  let it = curDir.directoryEntries;
-  while (it.hasMoreElements()) {
-    let curContent = it.getNext();
-    cnt++;
-  }
-
-  Assert.equal(cnt, expectCnt, "expected number of messages (" + mboxFilename + ")");
-}
-
+    "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!
+  });
+});
--- a/mailnews/base/util/converterWorker.js
+++ b/mailnews/base/util/converterWorker.js
@@ -1,483 +1,314 @@
 /* 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/. */
 
-/* eslint-env mozilla/chrome-worker, node */
-
 /**
- * This worker will perform mbox<->maildir conversions on a tree of
- * directories. It operates purely at the filesystem level.
- *
- * The initial message data should pass in these params to control
- * the conversion:
- *
- * srcType  - source mailstore type ('mbox' or 'maildir')
- * destType - destination mailstore type ('maildir' or 'mbox')
- * srcRoot  - root path of source (eg ".../ImapMail/imap.example.com")
- * destRoot - root path of destination (eg "/tmp/imap.example.com-maildir")
- *
- * The conversion is non-destructive - srcRoot will be left untouched.
+ * This worker performs one of a set of operations requested by
+ * mailstoreConverter.jsm. The possible operations are:
+ * - copy a file (.dat or .msf)
+ * - split a mbox file out into a maildir
+ * - join the contents of a maildir into a mbox file
+ * - create a subfolder (.sbd dir)
+ * - handle a .mozmsgs directory
  *
- * The worker will post progess messages back to the main thread of
- * the form:
- *
- *   {"msg": "progress", "val": val, "total": total}
- *
- * Where `val` is the current progress, out of `total`.
- * The units used for val and total are undefined.
+ * The caller relies on each worker to send the right number (and type)
+ * of update notifications for the operation that worker is responsible
+ * for.
+ * The caller counts notifications to detect when the overall mailstore
+ * conversion is complete.
  *
- * When the conversion is complete, before exiting, the worker sends a
- * message of the form:
- *
- *   {"msg": "success"}
- *
- * Errors are posted back to the main thread via the standard
- * "error" event.
- *
+ * Currently, the worker decides which operation it is responsible for
+ * performing by looking at:
+ *  - the name of the source
+ *  - the type of the source (file or directory),
+ *  - xpcom interface of the source mailstore (maildir or mbox).
+ * Since mailstoreConverter.jsm is already scanning the store
+ * and making these decisions, it would probably make sense to have it
+ * specify the operation type explicitly rather than repeating the
+ * logic here so the worker can decide.
  */
 
-importScripts("resource://gre/modules/osfile.jsm");
-
-/**
- * Merge all the messages in a maildir into a single mbox file.
- *
- * @param {String} maildir              - Path to the source maildir.
- * @param {String} mboxFilename         - Path of the mbox file to create.
- * @param {Function(Number)} progressFn - Function to be invoked regularly with
- *                                        progress updates. Param is number of
- *                                        "units" processed since last update.
- */
-function maildirToMBox(maildir, mboxFilename, progressFn) {
-  // Helper to format dates
-  // eg "Thu Jan 18 12:34:56 2018"
-  let fmtUTC = function(d) {
-    const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
-    const monthNames = ["Jan", "Feb", "Mar", "Apr",
-      "May", "Jun", "Jul", "Aug",
-      "Sep", "Oct", "Nov", "Dec"];
-    return dayNames[d.getUTCDay()] +
-      " " + monthNames[d.getUTCMonth()] +
-      " " + d.getUTCDate().toString().padStart(2) +
-      " " + d.getUTCHours().toString().padStart(2, "0") +
-      ":" + d.getUTCMinutes().toString().padStart(2, "0") +
-      ":" + d.getUTCSeconds().toString().padStart(2, "0") +
-      " " + d.getUTCFullYear();
-  };
-
-  let encoder = new TextEncoder();
-  let mboxFile = OS.File.open(mboxFilename, {write: true, create: true}, {});
-
-  // Iterate over all the message files in "cur".
-  let curPath = OS.Path.join(maildir, "cur");
-  let iterator = new OS.File.DirectoryIterator(curPath);
+self.importScripts("resource://gre/modules/osfile.jsm");
+self.addEventListener("message", function(e) {
   try {
-    let files = [];
-    if ("winCreationDate" in OS.File.DirectoryIterator.Entry.prototype) {
-      // Under Windows, additional information allow us to sort files immediately
-      // without having to perform additional I/O.
-      iterator.forEach(function(ent) {
-        files.push({path: ent.path, creationDate: ent.winCreationDate});
-      });
-    } else {
-      // Under other OSes, we need to call OS.File.stat
-      iterator.forEach(function(ent) {
-        files.push({path: ent.path, creationDate: OS.File.stat(ent.path).creationDate});
-      });
-    }
-    // Sort by creation time.
-    files.sort(function(a, b) {
-        return a.creationDate - b.creationDate;
-    });
-
-    for (let ent of files) {
-      let inFile = OS.File.open(ent.path);
-      try {
-        let raw = inFile.read();
-        // Old converter had a bug where maildir messages included the
-        // leading "From " marker, so we need to cope with any
-        // cases of this left in the wild.
-        if (String.fromCharCode.apply(null, raw.slice(0, 5)) != "From ") {
-          // Write the separator line.
-          // Technically, timestamp should be the reception time of the
-          // message, but we don't really want to have to parse the
-          // message here and nothing is likely to rely on iterator.
-          let sepLine = "From - " + fmtUTC(new Date()) + "\n";
-          mboxFile.write(encoder.encode(sepLine));
-        }
-
-        mboxFile.write(raw);
-      } finally {
-        inFile.close();
-      }
-      // Maildir progress is one per message.
-      progressFn(1);
-    }
-  } finally {
-    iterator.close();
-    mboxFile.close();
-  }
-}
+    // {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);
 
-/**
- * Split an mbox file up into a maildir.
- *
- * @param {String} mboxPath             - Path of the mbox file to split.
- * @param {String} maildirPath          - Path of the maildir to create.
- * @param {Function(Number)} progressFn - Function to be invoked regularly with
- *                                        progress updates. One parameter is
- *                                        passed - the number of "cost units"
- *                                        since the previous update.
- */
-function mboxToMaildir(mboxPath, maildirPath, progressFn) {
-  // Create the maildir structure.
-  OS.File.makeDir(maildirPath);
-  let curDirPath = OS.Path.join(maildirPath, "cur");
-  let tmpDirPath = OS.Path.join(maildirPath, "tmp");
-  OS.File.makeDir(curDirPath);
-  OS.File.makeDir(tmpDirPath);
+    if (stat.isDir && sourceFile.substr(-8) == ".mozmsgs") {
+      // it's an OS search integration dir.
+      // A no-op for now. Maildir/OS search integration is still
+      // a little undecided (see bug 1144478).
+      return;
+    }
 
-  let decoder = new TextDecoder();
-  let encoder = new TextEncoder();
-
-  const CHUNK_SIZE = 10000000;
-  // SAFE_MARGIN is how much to keep back between chunks in order to
-  // cope with separator lines which might span chunks.
-  const SAFE_MARGIN = 100;
-
-  // A regexp to match mbox separator lines.
-  // We support lines like:
-  // "From "
-  // "From MAILER-DAEMON Fri Jul  8 12:08:34 2011"
-  // "From - Mon Jul 11 12:08:34 2011"
-  // "From bob@example.com Fri Jul  8 12:08:34 2011"
-  // we also require a message header on the next line, in order
-  // to better cope with unescaped "From " lines in the message body.
-  // note: the first subexpression matches the separator line, so
-  // it can be removed from the input.
-  let sepRE = /^((?:From \r?\n)|(?:From [\S]+ \S{3} \S{3} [ \d]\d \d\d:\d\d:\d\d \d{4}\r?\n))[\x21-\x7E]+:/gm;
+    if (stat.isDir && sourceFile.substr(-4) == ".sbd") {
+      // it's a subfolder
+      OS.File.makeDir(dest, {from: tmpDir});
+      OS.File.makeDir(OS.Path.join(dest, destFile), {from: tmpDir});
 
-  // Use timestamp as starting name for output messages, incrementing
-  // by one for each.
-  let ident = Date.now();
-  let outFile = null;
-
-  let writeToMsg = function(text) {
-    if (outFile === null) {
-      let outPath = OS.Path.join(curDirPath, ident.toString());
-      ident += 1;
-      outFile = OS.File.open(outPath, {write: true, create: true}, {});
-    }
-    let raw = encoder.encode(text);
-    outFile.write(raw);
-    // for mbox->maildir conversion, progress measured in bytes
-    progressFn(raw.byteLength);
-  };
-
-  let closeExistingMsg = function() {
-    if (outFile) {
-      outFile.close();
-      outFile = null;
-    }
-  };
-
-  let mboxFile = OS.File.open(mboxPath);
-  let buf = "";
-  let eof = false;
-  while (!eof) {
-    let raw = mboxFile.read(CHUNK_SIZE);
-    buf = buf + decoder.decode(raw);
-    eof = (raw.byteLength < CHUNK_SIZE);
-
-    let pos = 0;
-    sepRE.lastIndex = 0;  // start at beginning of buf
-    let m = null;
-    while ((m = sepRE.exec(buf)) !== null) {
-      // Output everything up to the line separator.
-      if (m.index > pos) {
-        writeToMsg(buf.substring(pos, m.index));
-      }
-      pos = m.index;
-      pos += m[1].length;  // skip the "From " line
-      closeExistingMsg();
+      // 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]);
+      return;
     }
 
-    // Deal with whatever is left in the buffer.
-    let endPos = buf.length;
-    if (!eof) {
-      // Keep back enough to cope with separator lines crossing
-      // chunk boundaries.
-      endPos -= SAFE_MARGIN;
-      if (endPos < pos) {
-        endPos = pos;
+    if (mailstoreContractId == "@mozilla.org/msgstore/maildirstore;1" &&
+        stat.isDir && sourceFile.substr(-4) != ".sbd") {
+      // copy messages from maildir -> mbox
+
+      // 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]);
+      return;
+    }
+
+
+    // 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 (!stat.isDir && ((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]);
+      return;
     }
 
-    if (endPos > pos) {
-      writeToMsg(buf.substring(pos, endPos));
-    }
-    buf = buf.substring(endPos);
+    // All other files are assumed to be mbox.
+    if (!stat.isDir && mailstoreContractId != "@mozilla.org/msgstore/maildirstore;1") {
+      // An mbox message file is encountered. Split it up into a maildir.
 
-  }
-  closeExistingMsg();
-}
+      const constNoOfBytes = 10000000;
+      // (TODO: check this doesn't bound the size of messages we can convert!)
+
+      // Create a directory with path 'dest'.
+      OS.File.makeDir(dest, {from: tmpDir});
 
-/**
- * Check if directory is a subfolder directory.
- *
- * @param {String} name     - Name of directory to check.
- * @returns {Boolean}       - true if subfolder.
- */
-function isSBD(name) {
-  return name.substr(-4) == ".sbd";
-}
+      // 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();
 
-/**
- * Check if file is a type which should be copied verbatim as part of a
- * conversion.
- *
- * @param {String} name     - Name of file to check.
- * @returns {Boolean}       - true if file should be copied verbatim.
- */
-function isFileToCopy(name) {
-  let ext4 = name.substr(-4);
-  if (ext4 == ".msf" || ext4 == ".dat") {
-    return true;
-  }
-  return false;
-}
+      // 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;
 
-/**
- * Check if file is an mbox.
- * (actually we can't really tell if it's an mbox or not just from the name.
- * we just assume it is, if it's not .msf or .dat).
- *
- * @param {String} name     - Name of file to check.
- * @returns {Boolean}       - true if file is an mbox
- */
-function isMBoxName(name) {
-  let ext4 = name.substr(-4);
-  if (ext4 == ".msf" || ext4 == ".dat") {
-    return false;
-  }
-  // Assume all other files are mbox.
-  return true;
-}
+      // 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);
+            }
 
-/**
- * Check if directory is a maildir (by looking for a "cur" subdir).
- *
- * @param {String} dir    - Path of directory to check.
- * @returns {Boolean}     - true if directory is a maildir.
- */
-function isMaildir(dir) {
-  try {
-    let cur = OS.Path.join(dir, "cur");
-    let fi = OS.File.stat(cur);
-    return fi.isDir;
-  } catch (ex) {
-    if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
-      // "cur" does not exist - not a maildir.
-      return false;
-    }
-    throw ex; // Other error.
-  }
-}
+            // 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);
+        }
 
-/**
- * Count the number of messages in the "cur" dir of maildir.
- *
- * @param {String} maildir  - Path of maildir.
- * @returns {Number}        - number of messages found.
- */
-function countMaildirMsgs(maildir) {
-  let cur = OS.Path.join(maildir, "cur");
-  let iterator = new OS.File.DirectoryIterator(cur);
-  let count = 0;
-  try {
-    iterator.forEach(function(ent) { count++; });
-  } finally {
-    iterator.close();
-  }
-  return count;
-}
+        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]]);
 
-/**
- * Recursively calculate the 'cost' of a hierarchy of maildir folders.
- * This is the figure used for progress updates.
- * For maildir, cost is 1 per message.
- *
- * @param {String} srcPath  - Path of root dir containing maildirs.
- * @returns {Number}        - calculated conversion cost.
- */
-function calcMaildirCost(srcPath) {
-  let cost = 0;
-  let iterator = new OS.File.DirectoryIterator(srcPath);
-  try {
-    iterator.forEach(function(ent) {
-      if (ent.isDir) {
-        if (isSBD(ent.name)) {
-          // Recurse into subfolder.
-          cost += calcMaildirCost(ent.path);
-        } else if (isMaildir(ent.path)) {
-          // Looks like a maildir. Cost is number of messages.
-          cost += countMaildirMsgs(ent.path);
+            // 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 every time.
+          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;
         }
       }
-    });
-  } finally {
-    iterator.close();
-  }
-  return cost;
-}
 
-/**
- * Recursively calculate the 'cost' of a hierarchy of mbox folders.
- * This is the figure used for progress updates.
- * For mbox, cost is the total byte size of data. This avoids the need to
- * parse the mbox files to count the number of messages.
- * Note that this byte count cost is not 100% accurate because it includes
- * the "From " lines which are not written into the maildir files. But it's
- * definitely close enough to give good user feedback.
- *
- * @param {String} srcPath  - Path of root dir containing maildirs.
- * @returns {Number}        - calculated conversion cost.
- */
-function calcMBoxCost(srcPath) {
-  let cost = 0;
-  let iterator = new OS.File.DirectoryIterator(srcPath);
-  try {
-    iterator.forEach(function(ent) {
-      if (ent.isDir) {
-        if (isSBD(ent.name)) {
-          // Recurse into .sbd subfolder.
-          cost += calcMBoxCost(ent.path);
-        }
-      } else if (isMBoxName(ent.name)) {
-        let fi = OS.File.stat(ent.path);
-        cost += fi.size;
-      }
-    });
-  } finally {
-    iterator.close();
-  }
-  return cost;
-}
-
-/**
- * Recursively convert a tree of mbox-based folders to maildirs.
- *
- * @param {String} srcPath              - Root path containing mboxes.
- * @param {String} destPath             - Where to create destination root.
- * @param {Function(Number)} progressFn - Function to be invoked regularly with
- *                                        progress updates (called with number of
- *                                        cost "units" since last update)
- */
-function convertTreeMBoxToMaildir(srcPath, destPath, progressFn) {
-  OS.File.makeDir(destPath);
-
-  let iterator = new OS.File.DirectoryIterator(srcPath);
-  try {
-    iterator.forEach(function(ent) {
-      let dest = OS.Path.join(destPath, ent.name);
-      if (ent.isDir) {
-        if (isSBD(ent.name)) {
-          // Recurse into .sbd subfolder.
-          convertTreeMBoxToMaildir(ent.path, dest, progressFn);
-        }
-      } else if (isFileToCopy(ent.name)) {
-        OS.File.copy(ent.path, dest);
-      } else if (isMBoxName(ent.name)) {
-        // It's an mbox. Convert iterator.
-        mboxToMaildir(ent.path, dest, progressFn);
-      }
-    });
-  } finally {
-    iterator.close();
-  }
-}
-
-/**
- * Recursively convert a tree of maildir-based folders to mbox.
- *
- * @param {String} srcPath              - Root path containing maildirs.
- * @param {String} destPath             - Where to create destination root.
- * @param {Function(Number)} progressFn - Function to be invoked regularly with
- *                                        progress updates (called with number of
- *                                        cost "units" since last update)
- */
-function convertTreeMaildirToMBox(srcPath, destPath, progressFn) {
-  OS.File.makeDir(destPath);
-
-  let iterator = new OS.File.DirectoryIterator(srcPath);
-  try {
-    iterator.forEach(function(ent) {
-      let dest = OS.Path.join(destPath, ent.name);
-      if (ent.isDir) {
-        if (isSBD(ent.name)) {
-          // Recurse into .sbd subfolder.
-          convertTreeMaildirToMBox(ent.path, dest, progressFn);
-        } else if (isMaildir(ent.path)) {
-          // It's a maildir - convert iterator.
-          maildirToMBox(ent.path, dest, progressFn);
-        }
-      } else if (isFileToCopy(ent.name)) {
-        OS.File.copy(ent.path, dest);
-      }
-    });
-  } finally {
-    iterator.close();
-  }
-}
-
-self.addEventListener("message", function(e) {
-  try {
-    // Unpack the request params from the main thread.
-    let srcType = e.data.srcType;
-    let destType = e.data.destType;
-    let srcRoot = e.data.srcRoot;
-    let destRoot = e.data.destRoot;
-    // destRoot will be a temporary dir, so if it all goes pear-shaped
-    // we can just bail out without cleaning up.
-
-    // Configure the conversion.
-    let costFn = null;
-    let convertFn = null;
-    if (srcType == "maildir" && destType == "mbox") {
-      costFn = calcMaildirCost;
-      convertFn = convertTreeMaildirToMBox;
-    } else if (srcType == "mbox" && destType == "maildir") {
-      costFn = calcMBoxCost;
-      convertFn = convertTreeMBoxToMaildir;
-    } else {
-      throw new Error(`Unsupported conversion: ${srcType} => ${destType}`);
+      // 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]);
+      return;
     }
 
-    // Go!
-    let totalCost = costFn(srcRoot);
-    let v = 0;
-    let progressFn = function(n) {
-      v += n;
-      self.postMessage({"msg": "progress", "val": v, "total": totalCost});
-    };
-    convertFn(srcRoot, destRoot, progressFn);
+    // Should never get here, but the above rules are a little
+    // complex. So just in case.
+    throw new Error("Unhandled source: " + sourceFile);
 
-    // We fake a final progress update, with exactly 100% completed.
-    // Our byte-counting on mbox->maildir conversion will fall slightly short:
-    // The total is estimated from the mbox filesize, but progress is tracked
-    // by counting bytes as they are written out - and the mbox "From " lines
-    // are _not_ written out to the maildir files.
-    // This is still accurate enough to provide progress to the user, but we
-    // don't want the GUI left showing "progress 97% - conversion complete!"
-    // or anything silly like that.
-    self.postMessage({"msg": "progress", "val": totalCost, "total": totalCost});
-
-    // Let the main thread know we succeeded.
-    self.postMessage({"msg": "success"});
-
-  } catch (err) {
+  } 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(err);
+    throw new Error(e);
   }
 });
-
--- a/mailnews/base/util/mailstoreConverter.jsm
+++ b/mailnews/base/util/mailstoreConverter.jsm
@@ -1,311 +1,531 @@
 /* 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.jsm");
+ChromeUtils.import("resource:///modules/MailServices.jsm");
 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");
 
-let log = Log.repository.getLogger("MailStoreConverter");
+var log = Log.repository.getLogger("MailStoreConverter");
 log.level = Log.Level.Debug;
 log.addAppender(new Log.DumpAppender(new Log.BasicFormatter()));
 
-let gConverterWorker = null;
-
+// Array to hold workers.
+var gConverterWorkerArray = [];
 
 /**
- * Sets a server to use a different type of mailstore, converting
- * all the existing data.
- *
- * @param {String} aMailstoreContractId - XPCOM id of new mailstore type.
- * @param {nsIMsgServer} aServer        - server to migrate.
- * @param aEventTarget                  - if set, element to send progress events.
- *
- * @returns {Promise} - Resolves with a string containing the new root
- *                     directory for the migrated server.
- *                     Rejects with a string error message.
+ * 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 totalCount = 0;
+  // If there are zero msgs in the account "zeroMessages" is true else it is
+  // false.
+  var zeroMessages = false;
 
-  let accountRootFolder = aServer.rootFolder.filePath;
+  // 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()) {
+        // Don't count Windows Search integration dir.
+        if (content.leafName.substr(-8) != ".mozmsgs") {
+          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()) {
+        continue;
+      }
+      if (content.leafName.substr(-8) == ".mozmsgs") {
+        // Windows Search integration dir. Ignore.
+        continue;
+      }
+      if (content.leafName.substr(-4) == ".sbd") {
+        // A subfolder. Recurse into it.
+        count = count + countMaildirMsgs(content);
+      } else {
+        // We assume everything else is an actual maildir, and count the messages.
+        var cur = FileUtils.File(OS.Path.join(content.path,"cur"));
+        var curContents = cur.directoryEntries;
+        while (curContents.hasMoreElements()) {
+          curContents.getNext();
+          count++;
+        }
+      }
+    }
+    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);
+      if (!content.isDirectory()) {
+        count++;
+      } else if (content.leafName.substr(-4) == ".sbd") {
+        // A subfolder. Recurse into it.
+        count = count + 1 + countMaildirMsgs(content);
+      } else if (content.leafName.substr(-8) != ".mozmsgs") {
+        // Assume any other dir is an actual maildir.
+        count++;
+      }
+    }
+    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;
+    // For each file in the source folder...
+    while (contents.hasMoreElements()) {
+      let content = contents.getNext()
+                            .QueryInterface(Ci.nsIFile);
+
+      // 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 (content.leafName.substr(-4) != ".sbd" && content.leafName.substr(-8) != ".mozmsgs") {
+          // Assume it's a maildir, and grab the list of messages.
+          // 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'.
+            // TODO: this'll break if maildir scheme changes! (eg .eml extension)
+            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());
+          }
+        }
+      }
+
+      // Set up the worker.
+      let converterWorker = new ChromeWorker(
+        "resource:///modules/converterWorker.js");
+      gConverterWorkerArray.push(converterWorker);
+      log.debug("Processing " + content.path + " => : " + aDestPath.path);
+
+      converterWorker.addEventListener("message", function(e) {
+        var responseWorker = e.data[0];
+        log.debug("Type of file or folder encountered: " + e.data);
 
-  let srcType = null;
-  let destType = null;
-  if (aMailstoreContractId == "@mozilla.org/msgstore/maildirstore;1") {
-    srcType = "maildir";
-    destType = "mbox";
-  } else {
-    srcType = "mbox";
-    destType = "maildir";
+        // 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/totalCount) * 100);
+          aEventTarget.dispatchEvent(event);
+          if (progressValue == totalCount) {
+            log.info("Migration completed. Migrated " + totalCount + " 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);
+      });
+
+      // Kick off the worker.
+      converterWorker.postMessage(dataArray);
+
+      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") {
+
+    // TODO: why can't maildir count use aServer.rootFolder.getTotalMessages(true)?
+    totalCount = countMaildirMsgs(accountRootFolder);
+    if (totalCount == 0) {
+      totalCount = countMaildirZeroMsgs(accountRootFolder);
+      zeroMessages = true;
+    }
+  } else if (aServer.type == "pop3" ||
+             aServer.type == "none" || // none: Local Folders.
+             aServer.type == "movemail") {
+    totalCount = aServer.rootFolder.getTotalMessages(true);
+    if (totalCount == 0) {
+      totalCount = countImapFileFolders(accountRootFolder);
+      zeroMessages = true;
+    }
+  } else if (aServer.type == "imap" || aServer.type == "nntp") {
+    totalCount = countImapFileFolders(accountRootFolder);
+  }
+  log.debug("totalCount = " + totalCount + " (zeroMessages = " + zeroMessages + ")");
+
   // Go offline before conversion, so there aren't messages coming in during
   // the process.
   Services.io.offline = true;
-  let destDir = createTmpConverterFolder(accountRootFolder, aMailstoreContractId);
-
-  // Return a promise that will complete once the worker is done.
+  let dir = createTmpConverterFolder(accountRootFolder);
   return new Promise(function(resolve, reject) {
-
-    let worker = new ChromeWorker("resource:///modules/converterWorker.js");
-    gConverterWorker = worker;
-
-    // Helper to log error, clean up and return failure.
-    let bailout = function(e) {
-      let reasonString =
-        "ERROR " + e.filename + ":" + e.lineno + " - " + e.message;
-      log.error(reasonString);
-      // Cleanup.
-      log.error("Trying to remove converter folder: " + destDir.path);
-      destDir.remove(true);
-      reject(e.message);
-    };
-
-    // Handle exceptions thrown by the worker thread.
-    worker.addEventListener("error", bailout);
-
-    // Handle updates from the worker thread.
-    worker.addEventListener("message", function(e) {
-      let response = e.data;
-      // log.debug("WORKER SAYS: " + JSON.stringify(response) + "\n");
-      if (response.msg == "progress") {
-        let val = response.val;
-        let total = response.total;
-
-        // Send the percentage completion to the GUI.
-        // XXX TODO: should probably check elapsed time, and throttle
-        // the events to avoid spending all our time drawing!
-        let ev = new Event("progress");
-        ev.detail = parseInt((val / total) * 100);
-        if (aEventTarget) {
-          aEventTarget.dispatchEvent(ev);
-        }
-      }
-      if (response.msg == "success") {
-        // If we receive this, the worker has completed, without errors.
-        let storeTypeIDs = {
-          "mbox": "@mozilla.org/msgstore/berkeleystore;1",
-          "maildir": "@mozilla.org/msgstore/maildirstore;1",
-        };
-        let newStoreTypeID = storeTypeIDs[destType];
-
-        try {
-          let finalRoot = installNewRoot(aServer, destDir, newStoreTypeID);
-          log.info("Conversion complete. Converted dir installed as: " + finalRoot);
-          resolve(finalRoot);
-        } catch (e) {
-          bailout(e);
-        }
-      }
-    });
-
-    // Kick off the worker.
-    worker.postMessage({
-      "srcType": srcType,
-      "destType": destType,
-      "srcRoot": accountRootFolder.path,
-      "destRoot": destDir.path,
-    });
+    conversionOk = resolve;
+    conversionFailed = reject;
+    subDir(accountRootFolder, dir);
   });
 }
 
 /**
- * Checks if Converter folder exists in tmp dir, removes it and creates a new
- * "Converter" folder.
- * @param {nsIFile} aFolder             - account root folder.
- * @param {String} aMailstoreContractId - XPCOM id of dest mailstore type
- *
- * @returns {nsIFile} - the new tmp directory to use as converter dest.
+ * Terminate all workers.
  */
-function createTmpConverterFolder(aFolder, aMailstoreContractId) {
-  let tmpDir = FileUtils.getDir("TmpD", [], false);
-  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);
-    }
+function terminateWorkers() {
+  for (let worker of gConverterWorkerArray) {
+    worker.terminate();
   }
 }
-
-
-/**
- * Switch server over to use the newly-converted directory tree.
- * Moves the converted directory into an appropriate place for the server.
- *
- * @param {nsIMsgServer} server   - server to migrate.
- * @param {String} dir            - dir of converted mailstore to install
- *                                  (will be moved by this function).
- * @param {String} newStoreTypeID - XPCOM id of new mailstore type.
- * @returns {String} new location of dir.
- */
-function installNewRoot(server, dir, newStoreTypeID) {
-  let accountRootFolder = server.rootFolder.filePath;
-
-  // 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);
-
-  let 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.
-    log.info("Path to new account root folder: " +
-              converterFolder.path);
-  } catch (e) {
-    // Cleanup.
-    log.error(e);
-    log.error("Trying to remove converter folder: " +
-      converterFolder.path);
-    converterFolder.remove(true);
-    throw 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 (server.type != "pop3" && server.type != "none" &&
-    server.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 (server.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);
-    }
-  }
-
-  server.rootFolder.filePath = converterFolder;
-  server.localPath = converterFolder;
-  log.info("Path to account root folder: " +
-            server.rootFolder.filePath.path);
-
-  // Set various preferences.
-  let p1 = "mail.server." + server.key + ".directory";
-  let p2 = "mail.server." + server.key + ".directory-rel";
-  let p3 = "mail.server." + server.key + ".newsrc.file";
-  let p4 = "mail.server." + server.key + ".newsrc.file-rel";
-  let p5 = "mail.server." + server.key + ".storeContractID";
-
-  Services.prefs.setCharPref(p1, converterFolder.path);
-  log.info(p1 + ": " + converterFolder.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) + "/" +
-                                    converterFolder.leafName;
-  Services.prefs.setCharPref(p2, directoryRel);
-  log.info(p2 + ": " + directoryRel);
-
-  if (server.type == "nntp") {
-    let newNewsrc = FileUtils.File(OS.Path.join(parent.path,
-      "newsrc-" + converterFolder.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, newStoreTypeID);
-
-  Services.prefs.savePrefFile(null);
-
-  return converterFolder.path;
-}
-
-/**
- * Terminate any workers involved in the conversion process.
- */
-function terminateWorkers() {
-  // We're only using a single worker right now.
-  if (gConverterWorker !== null) {
-    gConverterWorker.terminate();
-    gConverterWorker = null;
-  }
-}
deleted file mode 100644
--- a/mailnews/test/data/mbox_mboxrd
+++ /dev/null
@@ -1,15 +0,0 @@
-From MAILER-DAEMON Fri Jul  8 12:08:34 2011
-From: Author <author@example.com>
-To: Recipient <recipient@example.com>
-Subject: Sample message 1
-
-This is the body.
->From (should be escaped).
-There are 3 lines.
-
-From MAILER-DAEMON Fri Jul  8 12:08:34 2011
-From: Author <author@example.com>
-To: Recipient <recipient@example.com>
-Subject: Sample message 2
-
-This is the second body.
deleted file mode 100644
--- a/mailnews/test/data/mbox_modern
+++ /dev/null
@@ -1,20 +0,0 @@
-From - Fri Aug 24 11:55:47 2018
-From: Author <author@example.com>
-To: Recipient <recipient@example.com>
-Subject: Sample message 1
-Date: Fri, 24 Aug 2018 11:55:47 +0000
-
-Later versions of Thunderbird quote things by prefixing
-a space,like this:
- From 
- From - Fri Aug 24 11:55:47 2018
-This could cause problems, if a reader decides to split the message
-here.
-
-From - Thu Aug 23 09:10:23 2018
-From: Author <author@example.com>
-To: Recipient <recipient@example.com>
-Subject: Sample message 2
-Date: Thu, 23 Aug 2018 09:10:23 +0000
-
-This is the second body.
deleted file mode 100644
--- a/mailnews/test/data/mbox_unquoted
+++ /dev/null
@@ -1,18 +0,0 @@
-From 
-From: Author <author@example.com>
-To: Recipient <recipient@example.com>
-Subject: Sample message 1
-Date: Wed, 22 Aug 2005 17:20:08 +0000
-
-Earlier versions of Thunderbird don't seem to quote things like this:
-From 
-This could cause problems, if a reader decides to split the message
-here.
-
-From 
-From: Author <author@example.com>
-To: Recipient <recipient@example.com>
-Subject: Sample message 2
-Date: Thu, 23 Aug 2005 11:17:43 +0000
-
-This is the second body.
--- a/mailnews/test/data/readme.txt
+++ b/mailnews/test/data/readme.txt
@@ -54,50 +54,8 @@ This is a file which is known as the MIM
 nested multipart/* and message/* segments; as its explanation describes:
 
   This is a demonstration of multi-part mail with encapsulated messages.  This
   is a very complex message whose purpose it is to exercise software using the
   new multi-part message standard.
 
 The original source of this file is unknown, but a copy can be found at
 <http://sourceforge.net/projects/kmmail/files/MIME%20Torture%20Tests/>.
-
-
-
-
-mbox_modern
------------
-
-A simple mbox in the form that Thunderbird currently seems to save (as
-of Nov 2018).
-Separator lines are of the form:
-
-From - Fri Aug 24 11:55:47 2018
-
-Lines beginning with "From " within the message body are escaped
-by prefixing a space.
-
-
-mbox_unquoted
--------------
-
-Old-style mbox. This seems to be what Thunderbird was storing back around
-2005, and so there are likely to be a bunch of these out in the wild.
-
-Separator lines are lines containing only: "From "
-Lines within the message body beginning with "From " are not escaped in
-any way.
-
-
-mbox_mboxrd
------------
-
-Example of the mboxrd variant, taken from the wikipedia mbox page.
-Separator lines are of the form:
-
-From MAILER-DAEMON Fri Jul  8 12:08:34 2011
-
-(note that MAILER-DAEMON is a special case - this is where an
-email address usually goes, but MAILER-DAEMON can be used instead).
-
-"From " lines within the message body are escaped by prefixing with
-a '>' char.
-