From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/base/src/mailstoreConverter.jsm | 339 ++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 comm/mailnews/base/src/mailstoreConverter.jsm (limited to 'comm/mailnews/base/src/mailstoreConverter.jsm') diff --git a/comm/mailnews/base/src/mailstoreConverter.jsm b/comm/mailnews/base/src/mailstoreConverter.jsm new file mode 100644 index 0000000000..6e5be5ebe1 --- /dev/null +++ b/comm/mailnews/base/src/mailstoreConverter.jsm @@ -0,0 +1,339 @@ +/* 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"]; + +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +let log = console.createInstance({ + prefix: "mail.mailstoreconverter", + maxLogLevel: "Warn", + maxLogLevelPref: "mail.mailstoreconverter.loglevel", +}); + +let gConverterWorker = null; + +/** + * 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 {?Element} 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 an error message. + */ +function convertMailStoreTo(aMailstoreContractId, aServer, aEventTarget) { + let accountRootFolder = aServer.rootFolder.filePath; + + let srcType = null; + let destType = null; + if (aMailstoreContractId == "@mozilla.org/msgstore/maildirstore;1") { + srcType = "maildir"; + destType = "mbox"; + } else { + srcType = "mbox"; + destType = "maildir"; + } + + // 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. + return new Promise(function (resolve, reject) { + let worker = new ChromeWorker("resource:///modules/converterWorker.js"); + gConverterWorker = worker; + + // Helper to log error, clean up and reject with error message. + let bailout = function (errmsg) { + log.error("bailing out (" + errmsg + ")"); + // Cleanup. + log.info("Trying to remove converter folder: " + destDir.path); + destDir.remove(true); + reject(errmsg); + }; + + // Handle exceptions thrown by the worker thread. + worker.addEventListener("error", function (e) { + // (e is type ErrorEvent) + + // if we're lucky, the error will contain location info + if (e.filename && e.lineno) { + bailout(e.filename + ":" + e.lineno + ": " + e.message); + } else { + bailout(e.message); + } + }); + + // 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("installNewRoot() failed"); + } + } + }); + + // Kick off the worker. + worker.postMessage({ + srcType, + destType, + srcRoot: accountRootFolder.path, + destRoot: destDir.path, + }); + }); +} + +/** + * 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. + */ +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( + PathUtils.join( + tmpDir.path, + aFolder.leafName.substr(0, aFolder.leafName.length - 8) + "-mbox" + ) + ); + } else { + tmpFolder = new FileUtils.File( + PathUtils.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( + PathUtils.join( + tmpDir.path, + aFolder.leafName.substr(0, aFolder.leafName.length - 5) + "-maildir" + ) + ); + } else { + tmpFolder = new FileUtils.File( + PathUtils.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 + ); + } + } +} + +/** + * 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( + PathUtils.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") { + let converterFolderMsf = new FileUtils.File( + PathUtils.join(parent.path, dir.leafName + ".msf") + ); + if (converterFolderMsf.exists()) { + converterFolderMsf.remove(true); + } + + let oldRootFolderMsf = new FileUtils.File( + PathUtils.join(parent.path, accountRootFolder.leafName + ".msf") + ); + if (oldRootFolderMsf.exists()) { + oldRootFolderMsf.copyTo(parent, converterFolderMsf.leafName); + } + } + + if (server.type == "nntp") { + let converterFolderNewsrc = new FileUtils.File( + PathUtils.join(parent.path, "newsrc-" + dir.leafName) + ); + if (converterFolderNewsrc.exists()) { + converterFolderNewsrc.remove(true); + } + let oldNewsrc = new FileUtils.File( + PathUtils.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( + PathUtils.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; + } +} -- cgit v1.2.3