Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
const messengerBundle = Services.strings.createBundle(
);
const folderTypes = [
{ flag: Ci.nsMsgFolderFlags.Inbox, name: "Inbox", type: "inbox" },
{ flag: Ci.nsMsgFolderFlags.Drafts, name: "Drafts", type: "drafts" },
{ flag: Ci.nsMsgFolderFlags.Templates, name: "Templates", type: "templates" },
{ flag: Ci.nsMsgFolderFlags.SentMail, name: "Sent", type: "sent" },
{ flag: Ci.nsMsgFolderFlags.Archive, name: "Archives", type: "archives" },
{ flag: Ci.nsMsgFolderFlags.Junk, name: "Junk", type: "junk" },
{ flag: Ci.nsMsgFolderFlags.Trash, name: "Trash", type: "trash" },
// { flag: Ci.nsMsgFolderFlags.Queue, name: "Outbox", type: "outbox" },
];
const allFlags = folderTypes.reduce((all, cur) => all | cur.flag, 0);
class SmartMailbox {
#tagsFolder = null;
#rootFolder = null;
#server = null;
#account = null;
#TagFolderURIs = new Map();
constructor() {
this.verify();
}
/**
* Returns the server of the smart mailbox account.
*
* @returns {nsIMsgIncomingServer}
*/
get server() {
return this.#server;
}
/**
* Returns the smart mailbox account.
*
* @returns {nsIMsgAccount}
*/
get account() {
return this.#account;
}
/**
* Returns the root folder of the smart mailbox account.
*
* @returns {nsIMsgFolder}
*/
get rootFolder() {
return this.#rootFolder;
}
/**
* Returns the tags folder of the smart mailbox account.
*
* @returns {nsIMsgFolder}
*/
get tagsFolder() {
return this.#tagsFolder;
}
/**
* Creates or updates the smart mailbox server.
*/
verify() {
let smartServer = lazy.MailServices.accounts.findServer(
"nobody",
"smart mailboxes",
"none"
);
if (!smartServer) {
smartServer = lazy.MailServices.accounts.createIncomingServer(
"nobody",
"smart mailboxes",
"none"
);
// We don't want the "smart" server/account leaking out into the ui in
// other places, so set it as hidden.
smartServer.hidden = true;
const account = lazy.MailServices.accounts.createAccount();
account.incomingServer = smartServer;
}
smartServer.prettyName =
messengerBundle.GetStringFromName("unifiedAccountName");
this.#rootFolder = smartServer.rootFolder.QueryInterface(
Ci.nsIMsgLocalMailFolder
);
// Create smart folders, if missing.
for (const folderType of folderTypes) {
this.getSmartFolder(folderType.name);
}
// Create root tag folder, if missing.
this.#tagsFolder =
this.#rootFolder.getChildWithURI(
`${this.#rootFolder.URI}/tags`,
false,
false
) ?? this.#rootFolder.createLocalSubfolder("tags");
this.#tagsFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
// Remove obsolete tag folders.
const tags = lazy.MailServices.tags.getAllTags();
const obsoleteFolders = this.#tagsFolder.subFolders.filter(
folder => !tags.some(t => t.tag == folder.name)
);
for (const folder of obsoleteFolders) {
folder.deleteSelf(null);
}
// Create tag folders, if missing.
for (const tag of tags) {
this.getTagFolder(tag);
}
lazy.MailServices.accounts.saveVirtualFolders();
this.#server = smartServer;
this.#account = lazy.MailServices.accounts.findAccountForServer(
this.#server
);
}
/**
* Returns the smart folder with the specified name. Attempts to create it, if
* it does not exist yet.
*
* @param {string} name
* @returns {?nsIMsgFolder}
*/
getSmartFolder(name) {
// Note: Smart folder URIs use the names as listed in the folderTypes array
// (e.g.: mailbox://nobody@smart%20mailboxes/Inbox), but their actual names
// will be localized. A folder lookup here via getChildNamed() will therefore
// fail on localized systems (unless the localized name is used for the lookup).
const folderType = folderTypes.find(f => f.name == name);
const folderFromUri = this.#rootFolder.getChildWithURI(
`${this.#rootFolder.URI}/${folderType.name}`,
false,
true
);
if (folderFromUri) {
return folderFromUri;
}
try {
const searchFolders = [];
const recurse = function (mainFolder) {
let subFolders;
try {
subFolders = mainFolder.subFolders;
} catch (ex) {
console.error(
new Error(`Unable to access the subfolders of ${mainFolder.URI}`, {
cause: ex,
})
);
}
if (!subFolders?.length) {
return;
}
for (const sf of subFolders) {
// Add all of the subfolders except the ones that belong to
// a different folder type.
if (!(sf.flags & allFlags)) {
searchFolders.push(sf);
recurse(sf);
}
}
};
for (const server of lazy.MailServices.accounts.allServers) {
for (const f of server.rootFolder.getFoldersWithFlags(
folderType.flag
)) {
searchFolders.push(f);
recurse(f);
}
}
const wrapper = lazy.VirtualFolderHelper.createNewVirtualFolder(
folderType.name,
this.#rootFolder,
searchFolders,
"ALL",
true
);
const folder = wrapper.virtualFolder;
folder.setFlag(folderType.flag);
const msgDatabase = folder.msgDatabase;
const folderInfo = msgDatabase.dBFolderInfo;
folderInfo.setUint32Property("searchFolderFlag", folderType.flag);
msgDatabase.summaryValid = true;
msgDatabase.close(true);
this.#rootFolder.notifyFolderAdded(folder);
} catch (ex) {
console.error(`Failed to create smart folder <${folderType.name}>`, ex);
}
return null;
}
/**
* Returns the virtual folder searching messages for `tag`, creates it if
* does not exist yet.
*
* @param {nsIMsgTag} tag
* @returns {nsIMsgFolder}
*/
getTagFolder(tag) {
// Use getChildWithURI() to get the folder via its known URI.
const uri = this.#TagFolderURIs.get(tag.key);
if (uri) {
const folderFromUri = this.#tagsFolder.getChildWithURI(uri, false, true);
if (folderFromUri) {
return folderFromUri;
}
}
// Use folder.getChildNamed() to identify the tag folder by its name.
const folderFromName = this.#tagsFolder.getChildNamed(tag.tag);
if (folderFromName) {
this.#TagFolderURIs.set(tag.key, folderFromName.URI);
return folderFromName;
}
try {
const folder = this.#tagsFolder.createLocalSubfolder(tag.key);
folder.flags |= Ci.nsMsgFolderFlags.Virtual;
folder.name = tag.tag;
this.#TagFolderURIs.set(tag.key, folder.URI);
const msgDatabase = folder.msgDatabase;
const folderInfo = msgDatabase.dBFolderInfo;
folderInfo.setCharProperty("searchStr", `AND (tag,contains,${tag.key})`);
folderInfo.setCharProperty("searchFolderUri", "*");
folderInfo.setUint32Property(
"searchFolderFlag",
Ci.nsMsgFolderFlags.Inbox
);
folderInfo.setBooleanProperty("searchOnline", false);
msgDatabase.summaryValid = true;
msgDatabase.close(true);
this.#tagsFolder.notifyFolderAdded(folder);
return folder;
} catch (ex) {
console.error(`Failed to create tag folder <${tag.tag}>`, ex);
}
return null;
}
/**
* Returns the currently known URI of the tag folder associated with a given
* key. The folder does not necessarily have to exist.
*
* @param {string} key
* @returns {string}
*/
getTagFolderUriForKey(key) {
return this.#TagFolderURIs.get(key);
}
}
let smartMailboxInstance = null;
export const SmartMailboxUtils = {
/**
* @returns {SmartMailbox}
*/
getSmartMailbox() {
if (!smartMailboxInstance) {
smartMailboxInstance = new SmartMailbox();
} else {
smartMailboxInstance.verify();
}
return smartMailboxInstance;
},
/**
* Remove the smart mailbox account (including the server), if it exists.
*
* @param {boolean} [removeFiles=false] - Remove data directory (local directory).
*/
removeAll(removeFiles = false) {
const smartServer = lazy.MailServices.accounts.findServer(
"nobody",
"smart mailboxes",
"none"
);
if (!smartServer) {
return;
}
const account =
lazy.MailServices.accounts.findAccountForServer(smartServer);
if (account) {
lazy.MailServices.accounts.removeAccount(account, removeFiles);
} else {
lazy.MailServices.accounts.removeIncomingServer(smartServer, removeFiles);
}
},
/**
* Returns a clone of the folder type array defined at the top of this module.
*
* @returns {object[]}
*/
getFolderTypes() {
return folderTypes.map(folderType => ({ ...folderType }));
},
};