Part of Bug 440794 Leverage Offline capabilities to make sending email appear faster - improve the activity manager UI to give split send/copy progress and associaite sent mails with identities. r/sr=bienvenu,ui-review=clarkbw
authorMark Banner <bugzilla@standard8.plus.com>
Wed, 27 May 2009 11:24:35 +0100
changeset 2710 33d632a3a73916f689019e7ba2886a0e2850478e
parent 2709 38c88455ec35aaec934d043851fad3659c13da66
child 2711 5816359932c8d36b793f0d54f24a5cbe4e5b281a
push idunknown
push userunknown
push dateunknown
bugs440794
Part of Bug 440794 Leverage Offline capabilities to make sending email appear faster - improve the activity manager UI to give split send/copy progress and associaite sent mails with identities. r/sr=bienvenu,ui-review=clarkbw
mail/components/activity/content/activity.xml
mail/components/activity/modules/sendLater.js
mail/components/activity/nsActivity.js
mail/components/activity/nsIActivity.idl
mail/locales/en-US/chrome/messenger/activity.properties
mailnews/base/src/nsMsgOfflineManager.cpp
mailnews/compose/public/nsIMsgSendLaterListener.idl
mailnews/compose/src/nsMsgSendLater.cpp
mailnews/compose/src/nsMsgSendLater.h
--- a/mail/components/activity/content/activity.xml
+++ b/mail/components/activity/content/activity.xml
@@ -499,34 +499,52 @@
 
           // Set the button visibility
           this.setVisibility("button_cancel", !hideCancelBut);
           this.setVisibility("button_retry", !hideRetryBut);
           this.setVisibility("button_pause", !hidePauseBut);
           this.setVisibility("button_resume", !hideResumeBut);
           this.setVisibility("progressmeter", !hideProgressMeter);
 
+          // Ensure progress meter not active when hidden
+          if (hideProgressMeter) {
+            let meter = document.getAnonymousElementByAttribute(this, "anonid",
+                                                                "progressmeter");
+            meter.setAttribute("mode", "determined");
+            meter.setAttribute("value", 0);
+          }
+
           // Update Status text and Display Text Areas
           // In some states we need to modify Display Text area of
           // the process (e.g. Failure).
           this.displayText = displayText;
           this.statusText = statusText;
          ]]>
         </body>
       </method>
       <method name="onProgressChanged">
         <parameter name="aActivity"/>
         <parameter name="aStatusText"/>
         <parameter name="aWorkUnitsComplete"/>
         <parameter name="aTotalWorkUnits"/>
         <body>
           <![CDATA[
-            let _percentComplete = 100.0 * aWorkUnitsComplete / aTotalWorkUnits;
-            document.getAnonymousElementByAttribute(this, "anonid","progressmeter")
-                    .setAttribute('value', _percentComplete);
+            let element =
+              document.getAnonymousElementByAttribute(this, "anonid",
+                                                      "progressmeter");
+            if (aTotalWorkUnits == 0) {
+              element.setAttribute("mode", "undetermined");
+              element.setAttribute("value", 0);
+            }
+            else {
+              let _percentComplete = 100.0 * aWorkUnitsComplete /
+                                     aTotalWorkUnits;
+              element.setAttribute("mode", "determined");
+              element.setAttribute('value', _percentComplete);
+            }
             this.statusText = aStatusText;
           ]]>
         </body>
       </method>
       <method name="onHandlerChanged">
         <parameter name="aActivity"/>
         <body>
           <![CDATA[
--- a/mail/components/activity/modules/sendLater.js
+++ b/mail/components/activity/modules/sendLater.js
@@ -70,25 +70,28 @@ let sendMsgProgressListener = {
 
   startMeteors: function() {
   },
 
   stopMeteors: function() {
   },
 
   showProgress: function (aPercentage) {
-    sendLaterModule.onMsgProgress(aPercentage);
+    sendLaterModule.onMessageSendProgress(0, 0, aPercentage, 0);
   }
 };
 
 // This module provides a link between the send later service and the activity
 // manager.
 let sendLaterModule =
 {
-  _process: null,
+  _sendProcess: null,
+  _copyProcess: null,
+  _identity: null,
+  _subject: null,
 
   get log() {
     delete this.log;
     return this.log = Log4Moz.getConfiguredLogger("sendLaterModule");
   },
 
   get activityMgr() {
     delete this.activityMgr;
@@ -102,141 +105,182 @@ let sendLaterModule =
                       .getService(Ci.nsIStringBundleService);
 
     return this.bundle = bundleSvc
       .createBundle("chrome://messenger/locale/activity.properties");
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgSendLaterListener]),
 
-  _newProcess: function(aTo) {
-    let displayText;
+  _displayTextForHeader: function(aLocaleStringBase, aSubject) {
+    return aSubject ?
+           this.bundle.formatStringFromName(aLocaleStringBase + "WithSubject",
+                                            [aSubject], 1) :
+           this.bundle.GetStringFromName(aLocaleStringBase);
+  },
 
-    if (aTo) {
-      displayText = this.bundle.formatStringFromName("sendingMessageTo",
-                                                     [aTo], 1);
-    }
-    else {
-      displayText = this.bundle.GetStringFromName("sendingMessage");
-    }
-
-    let process = new nsActProcess(displayText, this.activityMgr);
+  _newProcess: function(aLocaleStringBase, aAddSubject) {
+    let process =
+      new nsActProcess(this._displayTextForHeader(aLocaleStringBase,
+                                                  aAddSubject ?
+                                                  this._subject :
+                                                  ""),
+                                   this.activityMgr);
 
     process.iconClass = "sendMail";
-    // XXX For now group these standalone, later we can group by identity or
-    // something more meaningful.
-    process.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_STANDALONE;
+    process.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_BYCONTEXT;
+    process.contextObj = this;
+    process.contextType = "SendLater";
+    process.contextDisplayText = this.bundle.GetStringFromName("sendingMessages");
 
     return process;
   },
 
+  // Use this to group an activity by the identity if we have one.
+  _applyIdentityGrouping: function(aActivity) {
+    if (this._identity) {
+      aActivity.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_BYCONTEXT;
+      aActivity.contextType = this._identity.key;
+      aActivity.contextObj = this._identity;
+      let contextDisplayText = this._identity.identityName;
+      if (!contextDisplayText)
+        contextDisplayText = this._identity.email;
+
+      aActivity.contextDisplayText = contextDisplayText;
+
+    }
+    else
+      aActivity.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_STANDALONE;
+  },
+
   // Replaces the process with an event that reflects a completed process.
   _replaceProcessWithEvent: function(aProcess) {
     this.activityMgr.removeActivity(aProcess.id);
 
-    let event = new nsActEvent(this.bundle.GetStringFromName("sentMessage"),
+    let event = new nsActEvent(this._displayTextForHeader("sentMessage",
+                                                          this._subject),
                                this.activityMgr, null, aProcess.startTime,
                                new Date());
 
-    event.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_STANDALONE;
     event.iconClass = "sendMail";
-
+    this._applyIdentityGrouping(event);
+ 
     this.activityMgr.addActivity(event);
   },
 
   // Replaces the process with a warning that reflects the failed process.
-  _replaceProcessWithWarning: function(aProcess, aStatus) {
+  _replaceProcessWithWarning: function(aProcess, aCopyOrSend, aStatus, aMsg,
+                                       aMessageHeader) {
     this.activityMgr.removeActivity(aProcess.id);
 
-    let warning = new nsActWarning(this.bundle.GetStringFromName("failedToSendMessage"),
-                                   this.activityMgr, "");
+    let warning =
+      new nsActWarning(this._displayTextForHeader("failedTo" + aCopyOrSend,
+                                                  this._subject),
+                       this.activityMgr, "");
 
     warning.groupingStyle = Ci.nsIActivity.GROUPING_STYLE_STANDALONE;
+    this._applyIdentityGrouping(warning);
 
     this.activityMgr.addActivity(warning);
   },
 
   onStartSending: function(aTotalMessageCount) {
-    try {
-      if (!aTotalMessageCount) {
-        this.log.error("onStartSending called with zero messages\n");
-        return;
-      }
-
-      this.currentMsg = 0;
-
-      // Create the first process for the sending
-      let process = this._newProcess("");
-
-      this._process = process;
-
-      this.activityMgr.addActivity(process);
-    }
-    catch (ex) {
-      dump(ex);
+    if (!aTotalMessageCount) {
+      this.log.error("onStartSending called with zero messages\n");
+      return;
     }
   },
 
-  onProgress: function(aCurrentMessage, aTotalMessages) {
-    if (this._process.state != Ci.nsIActivityProcess.STATE_COMPLETED) {
-      this.log.debug("Warning, last send did not reach 100%");
-      this._process.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+  onMessageStartSending: function(aCurrentMessage, aTotalMessageCount,
+                                  aMessageHeader, aIdentity) {
+
+    // We want to use the identity and subject later, so store them for now.
+    this._identity = aIdentity;
+    if (aMessageHeader)
+      this._subject = aMessageHeader.subject;
+
+    // Create the process to display the send activity.
+    let process = this._newProcess("sendingMessage", true);
+    this._sendProcess = process;
+    this.activityMgr.addActivity(process);
+
+    // Now the one for the copy process.
+    process = this._newProcess("copyMessage", false);
+    this._copyProcess = process;
+    this.activityMgr.addActivity(process);
+  },
+
+  onMessageSendProgress: function(aCurrentMessage, aTotalMessageCount,
+                                  aMessageSendPercent,
+                                  aMessageCopyPercent) {
+    if (aMessageSendPercent < 100) {
+      // Ensure we are in progress...
+      if (this._sendProcess.state != Ci.nsIActivityProcess.STATE_INPROGRESS)
+        this._sendProcess.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
+
+      // ... and update the progress.
+      this._sendProcess.setProgress(this._sendProcess.lastStatusText,
+                                    aMessageSendPercent, 100);
     }
+    else if (aMessageSendPercent == 100) {
+      if (aMessageCopyPercent == 0) {
+        // Set send state to completed
+        if (this._sendProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED)
+          this._sendProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
 
-    // When we get onProgress we always know we've been successful in sending
-    // the message.
-    this._replaceProcessWithEvent(this._process);
-    this._process = null;
+        // Set copy state to in progress.
+        if (this._copyProcess.state != Ci.nsIActivityProcess.STATE_INPROGRESS)
+          this._copyProcess.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
+
+        // We don't know the progress of the copy, so just set to 0, and we'll
+        // display an undetermined progress meter.
+        this._copyProcess.setProgress(this._copyProcess.lastStatusText,
+                                      0, 0);
+      }
+      else if (aMessageCopyPercent < 100) {
+      }
+      else {
+        // We need to set this to completed otherwise activity manager
+        // complains.
+        if (this._copyProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED)
+          this._copyProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
 
-    if (aCurrentMessage < aTotalMessages) {
-      ++this.currentMsg;
+        this._replaceProcessWithEvent(this._sendProcess);
+        // Just drop the copy process, we don't need it now.
+        this.activityMgr.removeActivity(this._copyProcess.id);
+        this._sendProcess = null;
+        this._copyProcess = null;
+      }
+    }
+  },
 
-      // Create the first process for the sending
-      let process = this._newProcess("");
+  onMessageSendError: function(aCurrentMessage, aMessageHeader, aStatus,
+                               aMsg) {
+    if (this._sendProcess &&
+        this._sendProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED) {
+      this._sendProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+      this._replaceProcessWithWarning(this._sendProcess, "SendMessage", aStatus, aMsg,
+                                      aMessageHeader);
+      this._sendProcess = null;
 
-      this._process = process;
-
-      this.activityMgr.addActivity(process);
+      if (this._copyProcess &&
+          this._copyProcess.state != Ci.nsIActivityProcess.STATE_COMPLETED) {
+        this._copyProcess.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+        this.activityMgr.removeActivity(this._copyProcess.id);
+        this._copyProcess = null;
+      }
     }
   },
 
   onMsgStatus: function(aStatusText) {
-    this._process.setProgress(aStatusText, this._process.workUnitComplete,
-                              this._process.totalWorkUnits);
-  },
-
-  onMsgProgress: function(aCurrentProgress) {
-    // For some reason we never get 100%, but we do get 0!
-    if (aCurrentProgress == 0 && this._process.workUnitComplete > 0)
-      aCurrentProgress = 100;
-
-    if (aCurrentProgress < 100) {
-      if (this._process.state != Ci.nsIActivityProcess.STATE_INPROGRESS)
-        this._process.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
-
-      this._process.setProgress(this._process.lastStatusText, aCurrentProgress,
-                                100);
-    }
-    else
-      this._process.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+    this._sendProcess.setProgress(aStatusText, this._sendProcess.workUnitComplete,
+                                  this._sendProcess.totalWorkUnits);
   },
 
   onStopSending: function(aStatus, aMsg, aTotalTried, aSuccessful) {
-    if (this._process.state != Ci.nsIActivityProcess.STATE_COMPLETED) {
-      this.log.debug("Warning, last send did not reach 100%");
-      this._process.state = Ci.nsIActivityProcess.STATE_COMPLETED;
-    }
-
-    if (aStatus == 0)
-      this._replaceProcessWithEvent(this._process);
-    else
-      this._replaceProcessWithWarning(this._process, aStatus);
-    this._process = null;
-
-    this._process = null;
   },
 
   init: function() {
     // We should need to remove the listener as we're not being held by anyone
     // except by the send later instance.
     let sendLaterService = Cc["@mozilla.org/messengercompose/sendlater;1"]
                              .getService(Ci.nsIMsgSendLater);
 
--- a/mail/components/activity/nsActivity.js
+++ b/mail/components/activity/nsActivity.js
@@ -191,19 +191,26 @@ nsActivityProcess.prototype = {
       }
       catch(e) {
         this.log.error("Exception thrown by onStateChanged listener: "+ e);
       }
     }
   },
 
   setProgress: function(aStatusText, aWorkUnitsComplete, aTotalWorkUnits) {
-    this.percentComplete = parseInt(100 * aWorkUnitsComplete / aTotalWorkUnits);
-    this.workUnitComplete = aWorkUnitsComplete;
-    this.totalWorkUnits = aTotalWorkUnits;
+    if (aTotalWorkUnits == 0) {
+      this.percentComplete = -1;
+      this.workUnitComplete = 0;
+      this.totalWorkUnits = 0;
+    }
+    else {
+      this.percentComplete = parseInt(100.0 * aWorkUnitsComplete / aTotalWorkUnits);
+      this.workUnitComplete = aWorkUnitsComplete;
+      this.totalWorkUnits = aTotalWorkUnits;
+    }
     this.lastStatusText = aStatusText;
 
     // notify listeners
     for each (let [, value] in Iterator(this._listeners)) {
       try {
         value.onProgressChanged(this, aStatusText, aWorkUnitsComplete,
                                 aTotalWorkUnits);
       }
--- a/mail/components/activity/nsIActivity.idl
+++ b/mail/components/activity/nsIActivity.idl
@@ -375,18 +375,20 @@ interface nsIActivityProcess : nsIActivi
    * Updates the activity progress info.
    *
    * @param aStatusText A localized text describing the current status of the
    *                    process
    * @param aWorkUnitComplete The amount of work units completed. Not used by
    *                          Activity Manager or default binding for any
    *                          purpose.
    * @param aTotalWorkUnits Total amount of work units. Not used by
-   *                          Activity Manager or default binding for any
-   *                          purpose.
+   *                        Activity Manager or default binding for any
+   *                        purpose. If set to zero, this indicates that the
+   *                        number of work units is unknown, and the percentage
+   *                        attribute will be set to -1.
    */
   void setProgress(in AString aStatusText,
                    in unsigned long aWorkUnitComplete,
                    in unsigned long aTotalWorkUnits);
   
   /**
    * Component initialization method.
    *
--- a/mail/locales/en-US/chrome/messenger/activity.properties
+++ b/mail/locales/en-US/chrome/messenger/activity.properties
@@ -8,26 +8,31 @@ waitingForRetry=Waiting for retry
 completed=Completed
 canceled=Canceled
 
 # LOCALIZATION NOTE (yesterday): Displayed time for files finished yesterday
 yesterday=Yesterday
 # LOCALIZATION NOTE (monthDate): #1 month name; #2 date number; e.g., January 22
 monthDate=#1 #2
 
-# Send Message
+# LOCALIZATION NOTE (sendingMessages): this is used as a title for grouping processes in the activity manager when sending email.
+sendingMessages=Sending Messages
+# LOCALIZATION NOTE (sendingMessageWithSubject): %S will be replaced by the subject of the message being sent.
 sendingMessage=Sending Message
-# LOCALIZATION NOTE (sendingMessageTo): %S will be replaced by the first name or newsgroup that the message is being sent to.
-sendingMessageTo=Sending Message to %S
+sendingMessageWithSubject=Sending Message: %S
+copyMessage=Copying message to sent folder
 sentMessage=Sent Message
-# LOCALIZATION NOTE (sentMessageTo): %S will be replaced by the first name or newsgroup that the message was sent to.
-sentMessageTo=Sent Message to %S
+# LOCALIZATION NOTE (sentMessageWithSubject): %S will be replaced by the subject of the message being sent.
+sentMessageWithSubject=Sent Message: %S
 failedToSendMessage=Failed to send message
-# LOCALIZATION NOTE (failedToSendMessageTo): %S will be replaced by the first name or newsgroup that the message was being sent to.
-failedToSendMessageTo=Failed to send message to %S
+failedToCopyMessage=Failed to copy message
+# LOCALIZATION NOTE (failedToSendMessageWithSubject): %S will be replaced by the subject of the message being sent.
+failedToSendMessageWithSubject=Failed to send message: %S
+# LOCALIZATION NOTE (failedToCopyMessageWithSubject): %S will be replaced by the subject of the message being sent.
+failedToCopyMessageWithSubject=Failed to copy message: %S
 
 # LOCALIZATION NOTE (autosyncProcessProgress): Do not translate the word "%1$S", "%2$S" and "%3$S" below.
 # Place the word %1$S in your translation where the number of the message being downloaded should appear.
 # Place the word %2$S in your translation where the total number of pending messages should appear.
 # Place the word %3$S in your translation where the name of the folder being processed should appear.
 autosyncProcessProgress=Downloading %1$S of %2$S in %3$S
 # LOCALIZATION NOTE (autosyncProcessDisplayText): %S will be replaced by the folder name
 autosyncProcessDisplayText=Bringing folder %S up to date
--- a/mailnews/base/src/nsMsgOfflineManager.cpp
+++ b/mailnews/base/src/nsMsgOfflineManager.cpp
@@ -385,31 +385,57 @@ nsMsgOfflineManager::OnStopRunningUrl(ns
 }
 
 NS_IMETHODIMP nsMsgOfflineManager::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData)
 {
   return NS_OK;
 }
 
 // nsIMsgSendLaterListener implementation 
-NS_IMETHODIMP nsMsgOfflineManager::OnStartSending(PRUint32 aTotalMessageCount)
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStartSending(PRUint32 aTotalMessageCount)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageStartSending(PRUint32 aCurrentMessage,
+                                           PRUint32 aTotalMessageCount,
+                                           nsIMsgDBHdr *aMessageHeader,
+                                           nsIMsgIdentity *aIdentity)
 {
   return NS_OK;
 }
 
-NS_IMETHODIMP nsMsgOfflineManager::OnProgress(PRUint32 aCurrentMessage, PRUint32 aTotalMessage)
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageSendProgress(PRUint32 aCurrentMessage,
+                                           PRUint32 aTotalMessageCount,
+                                           PRUint32 aMessageSendPercent,
+                                           PRUint32 aMessageCopyPercent)
 {
-  if (m_statusFeedback && aTotalMessage)
-    return m_statusFeedback->ShowProgress ((100 * aCurrentMessage) / aTotalMessage);
-  else
-    return NS_OK;
+  if (m_statusFeedback && aTotalMessageCount)
+    return m_statusFeedback->ShowProgress((100 * aCurrentMessage) /
+                                          aTotalMessageCount);
+
+  return NS_OK;
 }
 
-NS_IMETHODIMP nsMsgOfflineManager::OnStopSending(nsresult aStatus, const PRUnichar *aMsg, PRUint32 aTotalTried, 
-                                 PRUint32 aSuccessful) 
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageSendError(PRUint32 aCurrentMessage,
+                                        nsIMsgDBHdr *aMessageHeader,
+                                        nsresult aStatus,
+                                        const PRUnichar *aMsg)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStopSending(nsresult aStatus,
+                                   const PRUnichar *aMsg, PRUint32 aTotalTried, 
+                                   PRUint32 aSuccessful)
 {
 #ifdef NS_DEBUG
   if (NS_SUCCEEDED(aStatus))
     printf("SendLaterListener::OnStopSending: Tried to send %d messages. %d successful.\n",
             aTotalTried, aSuccessful);
 #endif
   return AdvanceToNextState(aStatus);
 }
--- a/mailnews/compose/public/nsIMsgSendLaterListener.idl
+++ b/mailnews/compose/public/nsIMsgSendLaterListener.idl
@@ -33,44 +33,87 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
+interface nsIMsgDBHdr;
+interface nsIMsgIdentity;
+
 /**
  * Implement this interface and add to nsIMsgSendLater to receive notifications
  * of send later actions.
  */
-[scriptable, uuid(bb9fb203-d615-42d2-962b-447c8326bf79)]
+[scriptable, uuid(a7bc603b-d0da-4959-a82f-4b99c138b9f4)]
 interface nsIMsgSendLaterListener : nsISupports {
   /**
-   * Notify the observer that the operation of sending unsent messages has
+   * Notify the observer that the operation of sending messages later has
    * started.
    *
-   * @param aTotalMessageCount  Number of messages to be sent.
+   * @param aTotalMessageCount  Number of messages to be sent. This will not
+   *                            change over the time we are doing this sequence.
    */
   void onStartSending(in unsigned long aTotalMessageCount);
 
   /**
-   * Notify the observer that progress as occurred for send operation.
+   * Notify the observer that the next message send/copy is starting and
+   * provide details about the message.
+   *
+   * @param aCurrentMessage      The current message number that is being sent.
+   * @param aTotalMessageCount   The total number of messages that we are
+   *                             trying to send.
+   * @param aMessageHeader       The header information for the message that is
+   *                             being sent.
+   * @param aMessageIdentity     The identity being used to send the message.
+   */
+  void onMessageStartSending(in unsigned long aCurrentMessage,
+                             in unsigned long aTotalMessageCount,
+                             in nsIMsgDBHdr aMessageHeader,
+                             in nsIMsgIdentity aIdentity);
+
+  /**
+   * Notify the observer of the current progress of sending a message. The one
+   * function covers sending the message over the network and copying to the
+   * appropriate sent folder.
    *
-   * @param aTotalSent      The number of messages currently sent.
-   * @param aTotalMessages  The total number of messages to be sent.
+   * @param aCurrentMessage      The current message number that is being sent.
+   * @param aTotalMessageCount   The total number of messages that we are
+   *                             trying to send.
+   * @param aMessageSendPercent  The percentage of the message sent (0 to 100)
+   * @param aMessageCopyPercent  The percentage of the copy completed (0 to
+   *                             100). If there is no copy for this message,
+   *                             this may be set to 100 at the same time as
+   *                             aMessageSendPercent.
    */
-  void onProgress(in unsigned long aCurrentMessage,
-                  in unsigned long aTotalMessages);
+  void onMessageSendProgress(in unsigned long aCurrentMessage,
+                             in unsigned long aTotalMessageCount,
+                             in unsigned long aMessageSendPercent,
+                             in unsigned long aMessageCopyPercent);
+
+  /**
+   * Notify the observer of an error in the send message later function.
+   *
+   * @param aCurrentMessage      The current message number that is being sent.
+   * @param aMessageHeader       The header information for the message that is
+   *                             being sent.
+   * @param aStatus              The error status code.
+   * @param aMsg                 A text string describing the error.
+   */
+  void onMessageSendError(in unsigned long aCurrentMessage,
+                          in nsIMsgDBHdr aMessageHeader,
+                          in nsresult aStatus,
+                          in wstring aMsg);
 
   /**
    * Notify the observer that the send unsent messages operation has finished.
    * This is called regardless of the success/failure of the operation.
    *
    * @param aStatus     Status code for the message send.
    * @param aMsg        A text string describing the error.
    * @param aTotalTried Total number of messages that were attempted to be sent.
    * @param aSuccessful How many messages were successfully sent.
    */
   void onStopSending(in nsresult aStatus, in wstring aMsg,
                      in unsigned long aTotalTried, in unsigned long aSuccessful);
 };
-
--- a/mailnews/compose/src/nsMsgSendLater.cpp
+++ b/mailnews/compose/src/nsMsgSendLater.cpp
@@ -462,41 +462,20 @@ SendOperationListener::OnSendNotPerforme
 {
   return NS_OK;
 }
   
 NS_IMETHODIMP
 SendOperationListener::OnStopSending(const char *aMsgID, nsresult aStatus, const PRUnichar *aMsg, 
                                      nsIFile *returnFile)
 {
-  nsresult rv = NS_OK;
-
-  if (mSendLater)
-  {
-    if (NS_SUCCEEDED(aStatus))
-    {
-#ifdef NS_DEBUG
-      printf("nsMsgSendLater: Success on the message send operation!\n");
-#endif
-
-      mSendLater->SetOrigMsgDisposition();
-      mSendLater->DeleteCurrentMessage();
+  if (mSendLater && !mSendLater->OnSendStepFinished(aStatus))
+      NS_RELEASE(mSendLater);
 
-      ++(mSendLater->mTotalSentSuccessfully);
-    }
-    else
-    {
-      mSendLater->EndSendMessages(aStatus, nsnull,
-                                  mSendLater->mTotalSendCount, 
-                                  mSendLater->mTotalSentSuccessfully);
-      NS_RELEASE(mSendLater);
-    }
-  }
-
-  return rv;
+  return NS_OK;
 }
 
 // nsIMsgCopyServiceListener
 
 NS_IMETHODIMP
 SendOperationListener::OnStartCopy(void)
 {
   return NS_OK;
@@ -520,26 +499,19 @@ SendOperationListener::GetMessageId(nsAC
 {
   NS_NOTREACHED("SendOperationListener::GetMessageId()\n");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 SendOperationListener::OnStopCopy(nsresult aStatus)
 {
-  if (mSendLater) 
+  if (mSendLater)
   {
-    // Regardless of the success of the copy we will still keep trying
-    // to send the rest...
-    nsresult rv;
-    rv = mSendLater->StartNextMailFileSend();
-    if (NS_FAILED(rv))
-      mSendLater->EndSendMessages(rv, nsnull,
-                                  mSendLater->mTotalSendCount, 
-                                  mSendLater->mTotalSentSuccessfully);
+    mSendLater->OnCopyStepFinished(aStatus);
     NS_RELEASE(mSendLater);
   }
 
   return NS_OK;
 }
 
 nsresult
 nsMsgSendLater::CompleteMailFileSend()
@@ -641,73 +613,87 @@ nsMsgSendLater::CompleteMailFileSend()
                                  nsnull); 
   NS_RELEASE(sendListener);
   return rv;
 }
 
 nsresult
 nsMsgSendLater::StartNextMailFileSend()
 {
-  nsresult      rv = NS_OK;
-  nsCString  messageURI;
-
   PRBool hasMoreElements = PR_FALSE;
   if ((!mEnumerator) ||
       NS_FAILED(mEnumerator->HasMoreElements(&hasMoreElements)) ||
       !hasMoreElements)
   {
+    // Notify that this message has finished being sent.
+    NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 100);
+
     // EndSendMessages resets everything for us
     EndSendMessages(NS_OK, nsnull, mTotalSendCount, mTotalSentSuccessfully);
 
     // XXX Should we be releasing references so that we don't hold onto items
     // unnecessarily.
     return NS_OK;
   }
 
-  // Let everyone know about our progress if we've already sent more than one
-  // message.
+  // If we've already sent a message, and are sending more, send out a progress
+  // update with 100% for both send and copy as we must have finished by now.
   if (mTotalSendCount)
-    NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count());
+    NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 100);
 
   nsCOMPtr<nsISupports> currentItem;
-  rv = mEnumerator->GetNext(getter_AddRefs(currentItem));
+  nsresult rv = mEnumerator->GetNext(getter_AddRefs(currentItem));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mMessage = do_QueryInterface(currentItem); 
   if (!mMessage)
     return NS_ERROR_NOT_AVAILABLE;
 
   if (!mMessageFolder)
     return NS_ERROR_UNEXPECTED;
 
+  nsCString messageURI;
   mMessageFolder->GetUriForMsg(mMessage, messageURI);
 
   rv = nsMsgCreateTempFile("nsqmail.tmp", getter_AddRefs(mTempFile)); 
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIMsgMessageService> messageService;
   rv = GetMessageServiceFromURI(messageURI, getter_AddRefs(messageService));
   if (NS_FAILED(rv) && !messageService)
     return NS_ERROR_FACTORY_NOT_LOADED;
 
   ++mTotalSendCount;
 
+  nsCString identityKey;
+  rv = mMessage->GetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY,
+                                   getter_Copies(identityKey));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIMsgIdentity> identity;
+  rv = GetIdentityFromKey(identityKey.get(), getter_AddRefs(identity));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Notify that we're just about to start sending this message
+  NotifyListenersOnMessageStartSending(mTotalSendCount, mMessagesToSend.Count(),
+                                       identity);
+
   // Setup what we need to parse the data stream correctly
   m_inhead = PR_TRUE;
   m_headersFP = 0;
   m_headersPosition = 0;
   m_bytesRead = 0;
   m_position = 0;
   m_flagsPosition = 0;
   m_headersSize = 0;
   PR_FREEIF(mLeftoverBuffer);
 
   // Now, get our stream listener interface and plug it into the DisplayMessage
   // operation
-  NS_ADDREF(this);
+  AddRef();
 
   rv = messageService->DisplayMessage(messageURI.get(),
                                       static_cast<nsIStreamListener*>(this),
                                       nsnull, nsnull, nsnull, nsnull);
 
   Release();
 
   return rv;
@@ -1291,20 +1277,41 @@ nsMsgSendLater::GetSendingMessages(PRBoo
 
 void
 nsMsgSendLater::NotifyListenersOnStartSending(PRUint32 aTotalMessageCount)
 {
   NOTIFY_LISTENERS(OnStartSending, (aTotalMessageCount));
 }
 
 void
+nsMsgSendLater::NotifyListenersOnMessageStartSending(PRUint32 aCurrentMessage,
+                                                     PRUint32 aTotalMessage,
+                                                     nsIMsgIdentity *aIdentity)
+{
+  NOTIFY_LISTENERS(OnMessageStartSending, (aCurrentMessage, aTotalMessage,
+                                           mMessage, aIdentity));
+}
+
+void
 nsMsgSendLater::NotifyListenersOnProgress(PRUint32 aCurrentMessage,
-                                          PRUint32 aTotalMessage)
+                                          PRUint32 aTotalMessage,
+                                          PRUint32 aSendPercent,
+                                          PRUint32 aCopyPercent)
 {
-  NOTIFY_LISTENERS(OnProgress, (aCurrentMessage, aTotalMessage));
+  NOTIFY_LISTENERS(OnMessageSendProgress, (aCurrentMessage, aTotalMessage,
+                                           aSendPercent, aCopyPercent));
+}
+
+void
+nsMsgSendLater::NotifyListenersOnMessageSendError(PRUint32 aCurrentMessage,
+                                                  nsresult aStatus,
+                                                  const PRUnichar *aMsg)
+{
+  NOTIFY_LISTENERS(OnMessageSendError, (aCurrentMessage, mMessage,
+                                        aStatus, aMsg));
 }
 
 /**
  * This function is called to end sending of messages, it resets the send later
  * system and notifies the relevant parties that we have finished.
  */
 void
 nsMsgSendLater::EndSendMessages(nsresult aStatus, const PRUnichar *aMsg,
@@ -1332,16 +1339,61 @@ nsMsgSendLater::EndSendMessages(nsresult
   // If we've got a shutdown listener, notify it that we've finished.
   if (mShutdownListener)
   {
     mShutdownListener->OnStopRunningUrl(nsnull, NS_OK);
     mShutdownListener = nsnull;
   }
 }
 
+/**
+ * Called when the send part of sending a message is finished. This will set up
+ * for the next step or "end" depending on the status.
+ *
+ * @param aStatus  The success or fail result of the send step.
+ * @return         True if the copy process will continue, false otherwise.
+ */
+PRBool
+nsMsgSendLater::OnSendStepFinished(nsresult aStatus)
+{
+  if (NS_SUCCEEDED(aStatus))
+  {
+    SetOrigMsgDisposition();
+    DeleteCurrentMessage();
+
+    // Send finished, so that is now 100%, copy to proceed...
+    NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 0);
+
+    ++mTotalSentSuccessfully;
+    return PR_TRUE;
+  }
+  else
+    // XXX we don't currently get a message string from the send service.
+    NotifyListenersOnMessageSendError(mTotalSendCount, aStatus, nsnull);
+
+  EndSendMessages(aStatus, nsnull, mTotalSendCount, mTotalSentSuccessfully);
+  return PR_FALSE;
+}
+
+/**
+ * Called when the copy part of sending a message is finished. This will send
+ * the next message or handle failure as appropriate.
+ *
+ * @param aStatus  The success or fail result of the copy step.
+ */
+void
+nsMsgSendLater::OnCopyStepFinished(nsresult aStatus)
+{
+  // Regardless of the success of the copy we will still keep trying
+  // to send the rest...
+  nsresult rv = StartNextMailFileSend();
+  if (NS_FAILED(rv))
+    EndSendMessages(rv, nsnull, mTotalSendCount, mTotalSentSuccessfully);
+}
+
 // XXX todo
 // maybe this should just live in the account manager?
 nsresult
 nsMsgSendLater::GetIdentityFromKey(const char *aKey, nsIMsgIdentity  **aIdentity)
 {
   NS_ENSURE_ARG_POINTER(aIdentity);
 
   nsresult rv;
--- a/mailnews/compose/src/nsMsgSendLater.h
+++ b/mailnews/compose/src/nsMsgSendLater.h
@@ -98,21 +98,32 @@ public:
   // Necessary for creating a valid list of recipients
   nsresult                  BuildHeaders();
   nsresult                  DeliverQueuedLine(char *line, PRInt32 length);
   nsresult                  RebufferLeftovers(char *startBuf,  PRUint32 aLen);
   nsresult                  BuildNewBuffer(const char* aBuf, PRUint32 aCount, PRUint32 *totalBufSize);
 
   // methods for listener array processing...
   void NotifyListenersOnStartSending(PRUint32 aTotalMessageCount);
+  void NotifyListenersOnMessageStartSending(PRUint32 aCurrentMessage,
+                                            PRUint32 aTotalMessage,
+                                            nsIMsgIdentity *aIdentity);
   void NotifyListenersOnProgress(PRUint32 aCurrentMessage,
-                                 PRUint32 aTotalMessage);
+                                 PRUint32 aTotalMessage,
+                                 PRUint32 aSendPercent,
+                                 PRUint32 aCopyPercent);
+  void NotifyListenersOnMessageSendError(PRUint32 aCurrentMessage,
+                                         nsresult aStatus,
+                                         const PRUnichar *aMsg);
   void EndSendMessages(nsresult aStatus, const PRUnichar *aMsg, 
                        PRUint32 aTotalTried, PRUint32 aSuccessful);
 
+  PRBool OnSendStepFinished(nsresult aStatus);
+  void OnCopyStepFinished(nsresult aStatus);
+
   // counters and things for enumeration 
   PRUint32                  mTotalSentSuccessfully;
   PRUint32                  mTotalSendCount;
   nsCOMArray<nsIMsgDBHdr> mMessagesToSend;
   nsCOMPtr<nsISimpleEnumerator> mEnumerator;
   nsCOMPtr<nsIMsgFolder>    mMessageFolder;
   nsCOMPtr<nsIMsgStatusFeedback> mFeedback;