diff options
Diffstat (limited to 'mailnews/base/src/virtualFolderWrapper.js')
-rw-r--r-- | mailnews/base/src/virtualFolderWrapper.js | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/mailnews/base/src/virtualFolderWrapper.js b/mailnews/base/src/virtualFolderWrapper.js new file mode 100644 index 000000000..efb4ed68a --- /dev/null +++ b/mailnews/base/src/virtualFolderWrapper.js @@ -0,0 +1,255 @@ +/* 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/. */ + +/* + * Wrap everything about virtual folders. + */ + +this.EXPORTED_SYMBOLS = ['VirtualFolderHelper']; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; + +Cu.import("resource:///modules/mailServices.js"); +Cu.import("resource:///modules/iteratorUtils.jsm"); + +var VirtualFolderHelper = { + /** + * Create a new virtual folder (an actual nsIMsgFolder that did not previously + * exist), wrapping it in a VirtualFolderWrapper, and returning that wrapper. + * + * If the call to addSubfolder fails (and therefore throws), we will NOT catch + * it. + * + * @param aFolderName The name of the new folder to create. + * @param aParentFolder The folder in which to create the search folder. + * @param aSearchFolders A list of nsIMsgFolders that you want to use as the + * sources for the virtual folder OR a string that is the already '|' + * delimited list of folder URIs to use. + * @param aSearchTerms The search terms to use for the virtual folder. This + * should be a JS list/nsIMutableArray/nsISupportsArray of + * nsIMsgSearchTermbs. + * @param aOnlineSearch Should the search attempt to use the server's search + * capabilities when possible and appropriate? + * + * @return The VirtualFolderWrapper wrapping the newly created folder. You + * would probably only want this for its virtualFolder attribute which has + * the nsIMsgFolder we created. Be careful about accessing any of the + * other attributes, as they will bring its message database back to life. + */ + createNewVirtualFolder: function (aFolderName, aParentFolder, aSearchFolders, + aSearchTerms, aOnlineSearch) { + let msgFolder = aParentFolder.addSubfolder(aFolderName); + msgFolder.prettyName = aFolderName; + msgFolder.setFlag(Ci.nsMsgFolderFlags.Virtual); + + let wrappedVirt = new VirtualFolderWrapper(msgFolder); + wrappedVirt.searchTerms = aSearchTerms; + wrappedVirt.searchFolders = aSearchFolders; + wrappedVirt.onlineSearch = aOnlineSearch; + + let msgDatabase = msgFolder.msgDatabase; + msgDatabase.summaryValid = true; + msgDatabase.Close(true); + + aParentFolder.NotifyItemAdded(msgFolder); + MailServices.accounts.saveVirtualFolders(); + + return wrappedVirt; + }, + + /** + * Given an existing nsIMsgFolder that is a virtual folder, wrap it into a + * VirtualFolderWrapper. + */ + wrapVirtualFolder: function (aMsgFolder) { + return new VirtualFolderWrapper(aMsgFolder); + }, +}; + +/** + * Abstracts dealing with the properties of a virtual folder that differentiate + * it from a non-virtual folder. A virtual folder is an odd duck. When + * holding an nsIMsgFolder that is a virtual folder, it is distinguished by + * the virtual flag and a number of properties that tell us the string + * representation of its search, the folders it searches over, and whether we + * use online searching or not. + * Virtual folders and their defining attributes are loaded from + * virtualFolders.dat (in the profile directory) by the account manager at + * startup, (re-)creating them if need be. It also saves them back to the + * file at shutdown. The most important thing the account manager does is to + * create VirtualFolderChangeListener instances that are registered with the + * message database service. This means that if one of the databases for the + * folders that the virtual folder includes is opened for some reason (for + * example, new messages are added to the folder because of a filter or they + * are delivered there), the virtual folder gets a chance to know about this + * and update the virtual folder's "cache" of information, such as the message + * counts or the presence of the message in the folder. + * The odd part is that a lot of the virtual folder logic also happens as a + * result of the nsMsgDBView subclasses being told the search query and the + * underlying folders. This makes for an odd collaboration of UI and backend + * logic. + * + * Justification for this class: Virtual folders aren't all that complex, but + * they are complex enough that we don't want to have the same code duplicated + * all over the place. We also don't want to have a loose assembly of global + * functions for working with them. So here we are. + * + * Important! Accessing any of our attributes results in the message database + * being loaded so that we can access the dBFolderInfo associated with the + * database. The message database is not automatically forgotten by the + * folder, which can lead to an (effective) memory leak. Please make sure + * that you are playing your part in not leaking memory by only using the + * wrapper when you have a serious need to access the database, and by + * forcing the folder to forget about the database when you are done by + * setting the database to null (unless you know with confidence someone else + * definitely wants the database around and will clean it up.) + */ +function VirtualFolderWrapper(aVirtualFolder) { + this.virtualFolder = aVirtualFolder; +}; +VirtualFolderWrapper.prototype = { + /** + * @return the list of nsIMsgFolders that this virtual folder is a + * search over. + */ + get searchFolders() { + let rdfService = Cc['@mozilla.org/rdf/rdf-service;1'] + .getService(Ci.nsIRDFService); + let virtualFolderUris = + this.dbFolderInfo.getCharProperty("searchFolderUri").split("|"); + let folders = []; + for (let folderURI of virtualFolderUris) { + if (folderURI) + folders.push(rdfService.GetResource(folderURI) + .QueryInterface(Ci.nsIMsgFolder)); + } + return folders; + }, + /** + * Set the search folders that back this virtual folder. + * + * @param aFolders Either a "|"-delimited string of folder URIs or a list of + * nsIMsgFolders that fixIterator can traverse (JS array/nsIMutableArray/ + * nsISupportsArray). + */ + set searchFolders(aFolders) { + if (typeof(aFolders) == "string") { + this.dbFolderInfo.setCharProperty("searchFolderUri", aFolders); + } + else { + let uris = Array.from(fixIterator(aFolders, Ci.nsIMsgFolder)) + .map(folder => folder.URI); + this.dbFolderInfo.setCharProperty("searchFolderUri", uris.join("|")); + } + }, + + /** + * @return a "|"-delimited string containing the URIs of the folders that back + * this virtual folder. + */ + get searchFolderURIs() { + return this.dbFolderInfo.getCharProperty("searchFolderUri"); + }, + + /** + * @return the list of search terms that define this virtual folder. + */ + get searchTerms() { + return this.searchTermsSession.searchTerms; + }, + /** + * @return a newly created filter with the search terms loaded into it that + * define this virtual folder. The filter is apparently useful as an + * nsIMsgSearchSession stand-in to some code. + */ + get searchTermsSession() { + // Temporary means it doesn't get exposed to the UI and doesn't get saved to + // disk. Which is good, because this is just a trick to parse the string + // into search terms. + let filterList = MailServices.filters.getTempFilterList(this.virtualFolder); + let tempFilter = filterList.createFilter("temp"); + filterList.parseCondition(tempFilter, this.searchString); + return tempFilter; + }, + + /** + * Set the search string for this virtual folder to the stringified version of + * the provided list of nsIMsgSearchTerm search terms. If you already have + * a strinigified version of the search constraint, just set |searchString| + * directly. + * + * @param aTerms Some collection that fixIterator can traverse. A JS list or + * XPCOM array (nsIMutableArray or nsISupportsArray) should work. + */ + set searchTerms(aTerms) { + let condition = ""; + for (let term in fixIterator(aTerms, Ci.nsIMsgSearchTerm)) { + if (condition.length) + condition += " "; + if (term.matchAll) { + condition = "ALL"; + break; + } + condition += (term.booleanAnd) ? "AND (" : "OR ("; + condition += term.termAsString + ")"; + } + this.searchString = condition; + }, + + /** + * @return the set of search terms that define this virtual folder as a + * string. You may prefer to use |searchTerms| which converts them + * into a list of nsIMsgSearchTerms instead. + */ + get searchString() { + return this.dbFolderInfo.getCharProperty("searchStr"); + }, + /** + * Set the search that defines this virtual folder from a string. If you have + * a list of nsIMsgSearchTerms, you should use |searchTerms| instead. + */ + set searchString(aSearchString) { + this.dbFolderInfo.setCharProperty("searchStr", aSearchString); + }, + + /** + * @return whether the virtual folder is configured for online search. + */ + get onlineSearch() { + return this.dbFolderInfo.getBooleanProperty("searchOnline", false); + }, + /** + * Set whether the virtual folder is configured for online search. + */ + set onlineSearch(aOnlineSearch) { + this.dbFolderInfo.setBooleanProperty("searchOnline", aOnlineSearch); + }, + + /** + * @return the dBFolderInfo associated with the virtual folder directly. May + * be null. Will cause the message database to be opened, which may have + * memory bloat/leak ramifications, so make sure the folder's database was + * already going to be opened anyways or that you call + * |cleanUpMessageDatabase|. + */ + get dbFolderInfo() { + let msgDatabase = this.virtualFolder.msgDatabase; + return (msgDatabase && msgDatabase.dBFolderInfo); + }, + + /** + * Avoid memory bloat by making the virtual folder forget about its database. + * If the database is actually in use (read: someone is keeping it alive by + * having references to it from places other than the nsIMsgFolder), the + * folder will be able to re-establish the reference for minimal cost. + */ + cleanUpMessageDatabase: + function VirtualFolderWrapper_cleanUpMessageDatabase() { + this.virtualFolder.msgDatabase.Close(true); + this.virtualFolder.msgDatabase = null; + } +}; |