summaryrefslogtreecommitdiffstats
path: root/mailnews/base/src/virtualFolderWrapper.js
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/src/virtualFolderWrapper.js')
-rw-r--r--mailnews/base/src/virtualFolderWrapper.js255
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;
+ }
+};