1 /* ***** BEGIN LICENSE BLOCK *****
  2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3  *
  4  * The contents of this file are subject to the Mozilla Public License Version
  5  * 1.1 (the "License"); you may not use this file except in compliance with
  6  * the License. You may obtain a copy of the License at
  7  * http://www.mozilla.org/MPL/
  8  *
  9  * Software distributed under the License is distributed on an "AS IS" basis,
 10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11  * for the specific language governing rights and limitations under the
 12  * License.
 13  *
 14  * The Original Code is Mail utility functions for GMail Conversation View
 15  *
 16  * The Initial Developer of the Original Code is
 17  * Jonathan Protzenko
 18  * Portions created by the Initial Developer are Copyright (C) 2010
 19  * the Initial Developer. All Rights Reserved.
 20  *
 21  * Contributor(s):
 22  *
 23  * Alternatively, the contents of this file may be used under the terms of
 24  * either the GNU General Public License Version 2 or later (the "GPL"), or
 25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 26  * in which case the provisions of the GPL or the LGPL are applicable instead
 27  * of those above. If you wish to allow use of your version of this file only
 28  * under the terms of either the GPL or the LGPL, and not to allow others to
 29  * use your version of this file under the terms of the MPL, indicate your
 30  * decision by deleting the provisions above and replace them with the notice
 31  * and other provisions required by the GPL or the LGPL. If you do not delete
 32  * the provisions above, a recipient may use your version of this file under
 33  * the terms of any one of the MPL, the GPL or the LGPL.
 34  *
 35  * ***** END LICENSE BLOCK ***** */
 36 
 37 /**
 38  * @fileoverview A whole bunch of utility functions that will abstract away
 39  *  various low-level nsIMsgDbHdr operations. The idea is to save time by not
 40  *  having to lookup how to do simple actions.
 41  * @author Jonathan Protzenko
 42  */
 43 
 44 var EXPORTED_SYMBOLS = [
 45   // Low-level XPCOM boring stuff
 46   'msgHdrToMessageBody', 'msgHdrToNeckoURL', 'msgHdrGetTags', 'msgUriToMsgHdr', 'msgHdrGetUri',
 47   // Quickly identify a message
 48   'msgHdrIsDraft', 'msgHdrIsSent', 'msgHdrIsArchive', 'msgHdrIsInbox',
 49   'msgHdrIsRss', 'msgHdrIsNntp', 'msgHdrIsJunk',
 50   // Actions on a set of message headers
 51   'msgHdrsMarkAsRead', 'msgHdrsArchive', 'msgHdrsDelete',
 52   // Doesn't really belong here
 53   'getMail3Pane',
 54 ]
 55 
 56 const Ci = Components.interfaces;
 57 const Cc = Components.classes;
 58 const Cu = Components.utils;
 59 const Cr = Components.results;
 60 
 61 // from mailnews/base/public/nsMsgFolderFlags.idl
 62 const nsMsgFolderFlags_SentMail = 0x00000200;
 63 const nsMsgFolderFlags_Drafts   = 0x00000400;
 64 const nsMsgFolderFlags_Archive  = 0x00004000;
 65 const nsMsgFolderFlags_Inbox    = 0x00001000;
 66 
 67 const gMessenger = Cc["@mozilla.org/messenger;1"]
 68                    .createInstance(Ci.nsIMessenger);
 69 const gMsgTagService = Cc["@mozilla.org/messenger/tagservice;1"]
 70                        .getService(Ci.nsIMsgTagService);
 71 
 72 
 73 /**
 74  * Get a given message header's uri.
 75  * @param {nsIMsgDbHdr} aMsg The message
 76  * @return {String}
 77  */
 78 function msgHdrGetUri (aMsg)
 79   aMsg.folder.getUriForMsg(aMsg)
 80 
 81 /**
 82  * Get a msgHdr from a message URI (msgHdr.URI).
 83  * @param {String} aUri The URI of the message
 84  * @return {nsIMsgDbHdr}
 85  */
 86 function msgUriToMsgHdr(aUri) {
 87   let messageService = gMessenger.messageServiceFromURI(aUri);
 88   return messageService.messageURIToMsgHdr(aUri);
 89 }
 90 
 91 /**
 92  * Tells if the message is in the account's inbox
 93  * @param {nsIMsgDbHdr} msgHdr The message header to examine
 94  * @return {bool}
 95  */
 96 function msgHdrIsInbox(msgHdr)
 97   msgHdr.folder.getFlag(nsMsgFolderFlags_Inbox)
 98 
 99 /**
100  * Tells if the message is a draft message
101  * @param {nsIMsgDbHdr} msgHdr The message header to examine
102  * @return {bool}
103  */
104 function msgHdrIsDraft(msgHdr)
105   msgHdr.folder.getFlag(nsMsgFolderFlags_Drafts)
106 
107 /**
108  * Tells if the message is a sent message
109  * @param {nsIMsgDbHdr} msgHdr The message header to examine
110  * @return {bool}
111  */
112 function msgHdrIsSent(msgHdr)
113   msgHdr.folder.getFlag(nsMsgFolderFlags_SentMail)
114 
115 /**
116  * Tells if the message is an archived message
117  * @param {nsIMsgDbHdr} msgHdr The message header to examine
118  * @return {bool}
119  */
120 function msgHdrIsArchive(msgHdr) {
121   return msgHdr.folder.getFlag(nsMsgFolderFlags_Archive);
122 }
123 
124 /**
125  * Get a string containing the body of a messsage.
126  * @param {nsIMsgDbHdr} aMessageHeader The message header
127  * @param {bool} aStripHtml Keep html?
128  * @return {string}
129  */
130 function msgHdrToMessageBody(aMessageHeader, aStripHtml, aLength) {
131   let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);  
132   let listener = Cc["@mozilla.org/network/sync-stream-listener;1"].createInstance(Ci.nsISyncStreamListener);  
133   let uri = aMessageHeader.folder.getUriForMsg(aMessageHeader);  
134   messenger.messageServiceFromURI(uri).streamMessage(uri, listener, null, null, false, "");  
135   let folder = aMessageHeader.folder;  
136   /*
137    * AUTF8String getMsgTextFromStream(in nsIInputStream aStream, in ACString aCharset,
138                                       in unsigned long aBytesToRead, in unsigned long aMaxOutputLen, 
139                                       in boolean aCompressQuotes, in boolean aStripHTMLTags,
140                                       out ACString aContentType);
141   */
142   return folder.getMsgTextFromStream(
143     listener.inputStream, aMessageHeader.Charset, 2*aLength, aLength, false, aStripHtml, { });  
144 }  
145 
146 /**
147  * Get a nsIURI from a nsIMsgDBHdr
148  * @param {nsIMsgDbHdr} aMsgHdr The message header
149  * @return {nsIURI}
150  */
151 function msgHdrToNeckoURL(aMsgHdr) {
152   let uri = aMsgHdr.folder.getUriForMsg(aMsgHdr);
153   let neckoURL = {};
154   let msgService = gMessenger.messageServiceFromURI(uri);
155   msgService.GetUrlForUri(uri, neckoURL, null);
156   return neckoURL.value;
157 }
158 
159 /**
160  * Given a msgHdr, return a list of tag objects. This function
161  * just does the messy work of understanding how tags are
162  * stored in nsIMsgDBHdrs.
163  *
164  * @param {nsIMsgDbHdr} aMsgHdr the msgHdr whose tags we want
165  * @return {nsIMsgTag array} a list of tag objects
166  */
167 function msgHdrGetTags (aMsgHdr) {
168   let keywords = aMsgHdr.getStringProperty("keywords");
169   let keywordList = keywords.split(' ');
170   let keywordMap = {};
171   for (let iKeyword = 0; iKeyword < keywordList.length; iKeyword++) {
172     let keyword = keywordList[iKeyword];
173     keywordMap[keyword] = true;
174   }
175 
176   let tagArray = gMsgTagService.getAllTags({});
177   let tags = [];
178   for (let iTag = 0; iTag < tagArray.length; iTag++) {
179     let tag = tagArray[iTag];
180     if (tag.key in keywordMap)
181       tags.push(tag);
182   }
183   return tags;
184 }
185 
186 /**
187  * Mark an array of msgHdrs read (or unread)
188  * @param {nsIMsgDbHdr array} msgHdrs The message headers
189  * @param {bool} read True to mark them read, false to mark them unread
190  */
191 function msgHdrsMarkAsRead(msgHdrs, read) {
192   let pending = {};
193   for each (msgHdr in msgHdrs) {
194     if (msgHdr.isRead == read)
195       continue;
196     if (!pending[msgHdr.folder.URI]) {
197       pending[msgHdr.folder.URI] = {
198         folder: msgHdr.folder,
199         msgs: Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray)
200       };
201     }
202     pending[msgHdr.folder.URI].msgs.appendElement(msgHdr, false);
203   }
204   for each (let { folder, msgs } in pending) {
205     folder.markMessagesRead(msgs, read);
206     folder.msgDatabase = null; /* don't leak */
207   }
208 }
209 
210 /**
211  * Delete a set of messages.
212  * @param {nsIMsgDbHdr array} msgHdrs The message headers
213  */
214 function msgHdrsDelete(msgHdrs) {
215   let pending = {};
216   for each (msgHdr in msgHdrs) {
217     if (!pending[msgHdr.folder.URI]) {
218       pending[msgHdr.folder.URI] = {
219         folder: msgHdr.folder,
220         msgs: Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray)
221       };
222     }
223     pending[msgHdr.folder.URI].msgs.appendElement(msgHdr, false);
224   }
225   for each (let { folder, msgs } in pending) {
226     folder.deleteMessages(msgs, getMail3Pane().msgWindow, false, false, null, true);
227     folder.msgDatabase = null; /* don't leak */
228   }
229 }
230 
231 let w = null;
232 
233 /**
234  * Get the main Thunderbird window. Used heavily to get a reference to globals
235  *  that are defined in mail/base/content/.
236  * You should call this function with true everytime your overlay on the main
237  *  Thunderbird window is loaded, so that it invalidates the previous cached
238  *  reference to the main window (closing the main window doesn't necessarily
239  *  mean restarting Thunderbird).
240  * @param {bool} aForce The results are cached, should we reset the cache?
241  * @return The window object for the main window.
242  */
243 function getMail3Pane(aForce) {
244   if (!w || aForce)
245     w = Cc["@mozilla.org/appshell/window-mediator;1"]
246           .getService(Ci.nsIWindowMediator)
247           .getMostRecentWindow("mail:3pane");
248   return w;
249 }
250 
251 /**
252  * Archive a set of messages
253  * @param {nsIMsgDbHdr array} msgHdrs The message headers
254  */
255 function msgHdrsArchive(msgHdrs) {
256   /* See
257    * http://mxr.mozilla.org/comm-central/source/suite/mailnews/mailWindowOverlay.js#1337
258    *
259    * The window is here because otherwise we don't have access to
260    * BatchMessageMover.
261    * */
262   let mail3PaneWindow = getMail3Pane();
263   let batchMover = new mail3PaneWindow.BatchMessageMover();
264   batchMover.archiveMessages(msgHdrs);
265 }
266 
267 /**
268  * Tell if a message is an RSS feed iteme
269  * @param {nsIMsgDbHdr} msgHdr The message header
270  * @return {Bool}
271  */
272 function msgHdrIsRss(msgHdr)
273   (msgHdr.folder.server instanceof Ci.nsIRssIncomingServer)
274 
275 /**
276  * Tell if a message is a NNTP message
277  * @param {nsIMsgDbHdr} msgHdr The message header
278  * @return {Bool}
279  */
280 function msgHdrIsNntp(msgHdr)
281   (msgHdr.folder.server instanceof Ci.nsINntpIncomingServer)
282 
283 /**
284  * Tell if a message has been marked as junk.
285  * @param {nsIMsgDbHdr} msgHdr The message header
286  * @return {Bool}
287  */
288 function msgHdrIsJunk(aMsgHdr)
289   aMsgHdr.getStringProperty("junkscore") == Ci.nsIJunkMailPlugin.IS_SPAM_SCORE
290 
291 // XXX implement some day
292 function msgHdrMarkAsJunk(msgHdr) {
293   //starts here http://mxr.mozilla.org/comm-central/source/mailnews/base/content/junkCommands.js#384
294   /* 2733   nsCOMPtr<nsIJunkMailPlugin> junkPlugin;
295   2734 
296   2735   // if this is a junk command, get the junk plugin.
297   2736   if (command == nsMsgViewCommandType::junk ||
298   2737       command == nsMsgViewCommandType::unjunk)
299   2738   {
300   2739     // get the folder from the first item; we assume that
301   2740     // all messages in the view are from the same folder (no
302   2741     // more junk status column in the 'search messages' dialog
303   2742     // like in earlier versions...)
304   2743 
305   2744      nsCOMPtr<nsIMsgIncomingServer> server;
306   2745      rv = folder->GetServer(getter_AddRefs(server));
307   2746      NS_ENSURE_SUCCESS(rv, rv);
308   2747 
309   2748     nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
310   2749     rv = server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
311   2750     NS_ENSURE_SUCCESS(rv, rv);
312   2751 
313   2752     junkPlugin = do_QueryInterface(filterPlugin, &rv);
314   2753     NS_ENSURE_SUCCESS(rv, rv);
315   2754     if (!mJunkHdrs)
316   2755     {
317   2756       mJunkHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
318   2757       NS_ENSURE_SUCCESS(rv,rv);
319   2758     }
320   2759   }
321 
322 
323     2817       case nsMsgViewCommandType::junk:
324   2818         mNumMessagesRemainingInBatch++;
325   2819         mJunkHdrs->AppendElement(msgHdr, PR_FALSE);
326   2820         rv = SetMsgHdrJunkStatus(junkPlugin.get(), msgHdr,
327   2821                                  nsIJunkMailPlugin::JUNK);
328   2822         break;
329 
330     2837     // Provide junk-related batch notifications
331   2838     if ((command == nsMsgViewCommandType::junk) &&
332   2839         (command == nsMsgViewCommandType::unjunk)) {
333   2840       nsCOMPtr<nsIMsgFolderNotificationService>
334   2841         notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
335   2842       if (notifier)
336   2843         notifier->NotifyItemEvent(messages,
337   2844                                   NS_LITERAL_CSTRING("JunkStatusChanged"),
338   2845                                   (command == nsMsgViewCommandType::junk) ?
339   2846                                     kJunkMsgAtom : kNotJunkMsgAtom);
340   2847     } */
341 
342   //check OnMessageClassified for the rest of the actions
343   //http://mxr.mozilla.org/comm-central/source/mailnews/base/content/junkCommands.js#241
344   //the listener is for automatic classification, for manual marking, the
345   //junkstatusorigin needs to be changed
346 }
347