More activity manager refactoring, and some better error handling.
authorShawn Wilsher <me@shawnwilsher.com>
Wed, 04 Nov 2009 08:58:07 -0800
changeset 26 c17f9e0355744ad91fc8aaff952b3693c3953001
parent 25 001342e11a1e65f30ee9e6cf1ee3ded40d3e82b9
child 27 dde6646eae16bba9206531d0b29c9ed3a81b4f4f
push id16
push usersdwilsh@shawnwilsher.com
push dateThu, 05 Nov 2009 16:17:28 +0000
More activity manager refactoring, and some better error handling.
content/reply.js
resource/BugzillaAPI.jsm
resource/BugzillaHelper.jsm
--- a/content/reply.js
+++ b/content/reply.js
@@ -1,28 +1,12 @@
 Components.utils.import("resource://bugzilla-helper/BugzillaAPI.jsm");
 Components.utils.import("resource://bugzilla-helper/BugzillaHelper.jsm");
 
 ////////////////////////////////////////////////////////////////////////////////
-//// Constants
-
-// DEBUG constants
-const kServer = new BugzillaServer(
-  "https://api-dev.bugzilla.mozilla.org/stage/0.1/",
-  "75:B2:EF:89:81:51:8F:99:13:DC:BF:44:47:0E:A9:8D"
-);
-
-/*
-const kServer = new BugzillaServer(
-  "https://api-dev.bugzilla.mozilla.org/0.1/",
-  "75:B2:EF:89:81:51:8F:99:13:DC:BF:44:47:0E:A9:8D"
-);
-*/
-
-////////////////////////////////////////////////////////////////////////////////
 //// Dialog Controller
 
 var ReplyDialog = {
   initialize: function RD_initialize()
   {
     let label = document.getElementById("comment-label");
     label.value = "Add a comment to bug " + window.arguments[0] + ":";
     let comment = document.getElementById("comment");
@@ -37,28 +21,19 @@ var ReplyDialog = {
   onAccept: function RD_onAccept()
   {
     let bugID = window.arguments[0];
     let [username, password, store] = Helper.getLoginInformation();
     // XXX handle incorrect auth
     let auth = new BugzillaAuth(username, password);
     let comment = document.getElementById("comment").value;
     let isPrivate = document.getElementById("private").checked;
-    let callback = Helper.commentCallback;
-    let context = {
-      bug: bugID,
-      auth: store ? auth : null,
-      comment: comment,
-      isPrivate: isPrivate,
-      activity: new CommentActivity(bugID),
-    };
 
-    BugzillaAPI.addComment(kServer, bugID, auth, comment, isPrivate, callback,
-                           context);
-    // TODO add to activity manager
+    let activity = new CommentActivity(bugID);
+    activity.start(auth, comment, isPrivate, store);
     return true;
   },
 
   togglePrivate: function RD_togglePrivate()
   {
     let textbox = document.getElementById("comment");
     let checkbox = document.getElementById("private");
     textbox.className = checkbox.checked ? "private" : "";
--- a/resource/BugzillaAPI.jsm
+++ b/resource/BugzillaAPI.jsm
@@ -288,25 +288,35 @@ function performRequest(aServer,
                   createInstance(Ci.nsISimpleStreamListener)) {
     let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
     pipe.init(true, true, 0, 0xffffffff, null);
     listener.init(pipe.outputStream, {
       onStartRequest: function(aRequest, aContext) { },
       onStopRequest: function(aRequest, aContext, aStatusCode) {
         // It's possible we have a security error here.  If we do, see if we
         // added an exception, and retry if we did.
-        Components.utils.reportError(bcl.addedException);
         if (aStatusCode == SEC_ERROR_UNKNOWN_ISSUER && bcl.addedException) {
-          Components.utils.reportError("trying again!");
           performRequest(aServer, aURI, aCallback, aData);
           return;
         }
-        Components.utils.reportError(aStatusCode);
         pipe.outputStream.close();
         let is = Cc["@mozilla.org/scriptableinputstream;1"].
                  createInstance(Ci.nsIScriptableInputStream);
         is.init(pipe.inputStream);
-        aCallback(channel.responseStatus, is.read(is.available()));
+
+        // available will throw if there is no data since we closed the output
+        // end of the pipe.
+        let len = 0;
+        try {
+          len = is.available();
+        }
+        catch (e) {
+        }
+
+        if (len)
+          aCallback(channel.responseStatus, is.read(len));
+        else
+          aCallback(0, null);
       },
     });
     channel.asyncOpen(listener, null);
   }
 }
--- a/resource/BugzillaHelper.jsm
+++ b/resource/BugzillaHelper.jsm
@@ -33,34 +33,42 @@
  * decision by deleting the provisions above and replace them with the notice
  * 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 ***** */
 
 Components.utils.import("resource://bugzilla-helper/BugzillaAPI.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 let EXPORTED_SYMBOLS = [
   "Helper",
   "CommentActivity",
 ];
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Constants
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 // DEBUG constants
 const kBugzillaURI = "https://bugzilla-stage-tip.mozilla.org/";
+const kServer = new BugzillaServer(
+  "https://api-dev.bugzilla.mozilla.org/stage/0.1/",
+  "75:B2:EF:89:81:51:8F:99:13:DC:BF:44:47:0E:A9:8D"
+);
 
 /*
 const kBugzillaURI = "https://bugzilla.mozilla.org/";
+const kServer = new BugzillaServer(
+  "https://api-dev.bugzilla.mozilla.org/0.1/",
+  "75:B2:EF:89:81:51:8F:99:13:DC:BF:44:47:0E:A9:8D"
 */
 const kBugzillaRealm = "bugzilla-helper";
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Globals
 
 this.__defineGetter__("activityManager", function() {
   delete this.activityManager;
@@ -68,45 +76,16 @@ this.__defineGetter__("activityManager",
                                 getService(Ci.nsIActivityManager);
 });
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Dialog Controller
 
 const Helper = {
   /**
-   * Callback that should be passed to the BugzillaAPI.addComment method.
-   *
-   * @param aResponseCode
-   *        The HTTP response code.  HTTP/201 is what is expected upon success.
-   * @param aResponse
-   *        The body of the response sent to the server.
-   * @param aContext
-   *        The context given to BugzillaAPI.addComment.
-   */
-  commentCallback: function H_commentHandler(aResponseCode,
-                                             aResponse,
-                                             aContext)
-  {
-    let activity = aContext.activity;
-    if (aResponseCode == 201) {
-      activity.complete();
-      let auth = aContext.auth;
-      if (auth)
-        storePassword(auth.username, auth.password);
-      return;
-    }
-    activity.error();
-    // We had something unexpected.  Tell the user.
-    // XXX dispatch to notification box (id="mail-notification-box" in parent)
-    Components.utils.reportError("Got HTTP/" + aResponseCode + "\n" +
-                                 aResponse + "\n for comment" + aContext.comment);
-  },
-
-  /**
    * Obtains the login information by either checking the login manager, or
    * prompting the user.
    *
    * @return a triple containing the username, password, and a boolean value
    *         indicating if the authentication should be saved or not.
    */
   getLoginInformation: function H_getLoginInformation()
   {
@@ -128,43 +107,106 @@ const Helper = {
  * Creates and manages the interaction of an activity for a comment to the
  * given bug number.
  *
  * @param aBugNumber
  *        The bug number this comment is for.
  */
 function CommentActivity(aBugNumber)
 {
+  this._bug = aBugNumber;
+
+  // Create our process activity.
   let process = Cc["@mozilla.org/activity-process;1"].
                 createInstance(Ci.nsIActivityProcess);
   process.init("Comment to bug " + aBugNumber, null);
+  process.retryHandler = this;
 
   // Add the process.
   activityManager.addActivity(process);
   this._process = process;
 }
 
 CommentActivity.prototype = {
   /**
+   * Starts the activity.
+   *
+   * @param aAuth
+   *        The BugzillaAuth to use to authenticate.
+   * @param aComment
+   *        The text of the comment to add to aBugNumber.
+   * @param aIsPrivate
+   *        Indicates if this comment should be marked as private or not.
+   * @param aStoreAuth
+   *        Indicates if authentication should be stored or not upon success.
+   */
+  start: function CA_start(aAuth,
+                           aComment,
+                           aIsPrivate,
+                           aStoreAuth)
+  {
+    this._auth = aAuth;
+    this._context = {
+      bug: this._bug,
+      auth: aStoreAuth ? aAuth : null,
+      comment: aComment,
+      isPrivate: aIsPrivate,
+      activity: this,
+    };
+    BugzillaAPI.addComment(kServer, this._bug, aAuth, aComment, aIsPrivate,
+                           commentHandler, this._context);
+
+    // Set our state to being in progress.
+    this._process.state = Ci.nsIActivityProcess.STATE_INPROGRESS;
+  },
+
+  /**
    * To be called when the action is completed.  Updates the UI to indicate
    * that the comment was posted successfully.
    */
   complete: function CA_complete()
   {
     this._process.setProgress("Submitted", 1, 1);
+    this._process.state = Ci.nsIActivityProcess.STATE_COMPLETED;
+
+    // We need to break the cycle between this and the process.
+    this._process = null;
   },
 
   /**
    * To be called when an error occurred.  Updates the UI to indicate that an
    * error occurred and the user should retry.
    */
   error: function CA_error()
   {
     this._process.state = Ci.nsIActivityProcess.STATE_WAITINGFORRETRY;
+
+    // We need to break the cycle between this and the process.
+    this._process = null;
   },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIActivityRetryHandler
+
+  retry: function CA_retry(aProcess)
+  {
+    // Recreate our cycle - it will break again when we complete.
+    this._process = aProcess;
+
+    // Retry by starting over.
+    let context = this._context;
+    this.start(this._bug, this._auth, context.comment, context.isPrivate);
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIActivityRetryHandler,
+  ]),
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Global Functions
 
 /**
  * Obtains the stored username and password from the login manager.
  *
@@ -221,8 +263,37 @@ function storePassword(aUsername,
                     .createInstance(Components.interfaces.nsILoginInfo);
   l.init(kBugzillaURI, null, kBugzillaRealm, aUsername, aPassword, "", "");
 
   // And store it.
   let lm = Components.classes["@mozilla.org/login-manager;1"]
                      .getService(Components.interfaces.nsILoginManager);
   lm.addLogin(l);
 }
+
+/**
+ * Callback that should be passed to the BugzillaAPI.addComment method.
+ *
+ * @param aResponseCode
+ *        The HTTP response code.  HTTP/201 is what is expected upon success.
+ * @param aResponse
+ *        The body of the response sent to the server.
+ * @param aContext
+ *        The context given to BugzillaAPI.addComment.
+ */
+function commentHandler(aResponseCode,
+                        aResponse,
+                        aContext)
+{
+  let activity = aContext.activity;
+  if (aResponseCode == 201) {
+    activity.complete();
+    let auth = aContext.auth;
+    if (auth)
+      storePassword(auth.username, auth.password);
+    return;
+  }
+  activity.error();
+  // We had something unexpected.  Tell the user.
+  // XXX dispatch to notification box (id="mail-notification-box" in parent)
+  Components.utils.reportError("Got HTTP/" + aResponseCode + "\n" +
+                               aResponse + "\n for comment" + aContext.comment);
+}