Bug 335418 - Pause downloads when computer is about to go to sleep. r=sdwilsh, a1.9=beltzner
authoredward.lee@engineering.uiuc.edu
Wed, 19 Mar 2008 18:16:02 -0700
changeset 13349 1603382f26788e72c475fa4e4dd92c7f02166e18
parent 13348 9ccf387500ff4cf51e1479efcbd1f00b41251dd8
child 13350 42cda9062d59238d96334d0a4bb08617b46e2ae1
push idunknown
push userunknown
push dateunknown
reviewerssdwilsh
bugs335418
milestone1.9b5pre
Bug 335418 - Pause downloads when computer is about to go to sleep. r=sdwilsh, a1.9=beltzner
browser/app/profile/firefox.js
toolkit/components/downloads/src/nsDownloadManager.cpp
toolkit/components/downloads/src/nsDownloadManager.h
toolkit/components/downloads/test/unit/test_sleep_wake.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -233,16 +233,17 @@ pref("browser.download.manager.showWhenS
 pref("browser.download.manager.useWindow", true);
 pref("browser.download.manager.closeWhenDone", false);
 pref("browser.download.manager.openDelay", 0);
 pref("browser.download.manager.focusWhenStarting", false);
 pref("browser.download.manager.flashCount", 2);
 pref("browser.download.manager.addToRecentDocs", true);
 pref("browser.download.manager.quitBehavior", 0);
 pref("browser.download.manager.scanWhenDone", true);
+pref("browser.download.manager.resumeOnWakeDelay", 10000);
 
 // search engines URL
 pref("browser.search.searchEnginesURL",      "https://%LOCALE%.add-ons.mozilla.com/%LOCALE%/firefox/%VERSION%/search-engines/");
 
 // pointer to the default engine name
 pref("browser.search.defaultenginename",      "chrome://browser-region/locale/region.properties");
 
 // disable logging for the search service by default
--- a/toolkit/components/downloads/src/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/src/nsDownloadManager.cpp
@@ -91,16 +91,17 @@
 #define DOWNLOAD_MANAGER_BUNDLE "chrome://mozapps/locale/downloads/downloads.properties"
 #define DOWNLOAD_MANAGER_ALERT_ICON "chrome://mozapps/skin/downloads/downloadIcon.png"
 #define PREF_BDM_SHOWALERTONCOMPLETE "browser.download.manager.showAlertOnComplete"
 #define PREF_BDM_SHOWALERTINTERVAL "browser.download.manager.showAlertInterval"
 #define PREF_BDM_RETENTION "browser.download.manager.retention"
 #define PREF_BDM_QUITBEHAVIOR "browser.download.manager.quitBehavior"
 #define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
 #define PREF_BDM_SCANWHENDONE "browser.download.manager.scanWhenDone"
+#define PREF_BDM_RESUMEONWAKEDELAY "browser.download.manager.resumeOnWakeDelay"
 #define PREF_BH_DELETETEMPFILEONEXIT "browser.helperApps.deleteTempFileOnExit"
 
 static const PRInt64 gUpdateInterval = 400 * PR_USEC_PER_MSEC;
 
 #define DM_SCHEMA_VERSION      8
 #define DM_DB_NAME             NS_LITERAL_STRING("downloads.sqlite")
 #define DM_DB_CORRUPT_FILENAME NS_LITERAL_STRING("downloads.sqlite.corrupt")
 
@@ -224,16 +225,24 @@ nsDownloadManager::RemoveAllDownloads()
     // Track the failure, but don't miss out on other downloads
     if (NS_FAILED(result))
       rv = result;
   }
 
   return rv;
 }
 
+void // static
+nsDownloadManager::ResumeOnWakeCallback(nsITimer *aTimer, void *aClosure)
+{
+  // Resume the downloads that were set to autoResume
+  nsDownloadManager *dlMgr = static_cast<nsDownloadManager *>(aClosure);
+  (void)dlMgr->ResumeAllDownloads(PR_FALSE);
+}
+
 nsresult
 nsDownloadManager::InitDB(PRBool *aDoImport)
 {
   nsresult rv;
   *aDoImport = PR_FALSE;
 
   nsCOMPtr<mozIStorageService> storage =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
@@ -904,16 +913,18 @@ nsDownloadManager::Init()
   // failure to add an observer is not critical
   //
   // These observers will be cleaned up automatically at app shutdown.  We do
   // not bother explicitly breaking the observers because we are a singleton
   // that lives for the duration of the app.
   mObserverService->AddObserver(this, "quit-application", PR_FALSE);
   mObserverService->AddObserver(this, "quit-application-requested", PR_FALSE);
   mObserverService->AddObserver(this, "offline-requested", PR_FALSE);
+  mObserverService->AddObserver(this, "sleep_notification", PR_FALSE);
+  mObserverService->AddObserver(this, "wake_notification", PR_FALSE);
 
   return NS_OK;
 }
 
 PRInt32
 nsDownloadManager::GetRetentionBehavior()
 {
   // We use 0 as the default, which is "remove when done"
@@ -1756,16 +1767,32 @@ nsDownloadManager::Observe(nsISupports *
                            NS_LITERAL_STRING("offlineCancelDownloadsAlertMsgMultiple").get(),
                            NS_LITERAL_STRING("offlineCancelDownloadsAlertMsg").get(),
                            NS_LITERAL_STRING("dontGoOfflineButton").get());
   } else if (strcmp(aTopic, "alertclickcallback") == 0) {
     nsCOMPtr<nsIDownloadManagerUI> dmui =
       do_GetService("@mozilla.org/download-manager-ui;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     return dmui->Show(nsnull, 0);
+  } else if (strcmp(aTopic, "sleep_notification") == 0) {
+    // Pause downloads if we're sleeping, and mark the downloads as auto-resume
+    (void)PauseAllDownloads(PR_TRUE);
+  } else if (strcmp(aTopic, "wake_notification") == 0) {
+    PRInt32 resumeOnWakeDelay = 10000;
+    nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
+    if (pref)
+      (void)pref->GetIntPref(PREF_BDM_RESUMEONWAKEDELAY, &resumeOnWakeDelay);
+
+    // Wait a little bit before trying to resume to avoid resuming when network
+    // connections haven't restarted yet
+    mResumeOnWakeTimer = do_CreateInstance("@mozilla.org/timer;1");
+    if (resumeOnWakeDelay >= 0 && mResumeOnWakeTimer) {
+      (void)mResumeOnWakeTimer->InitWithFuncCallback(ResumeOnWakeCallback,
+        this, resumeOnWakeDelay, nsITimer::TYPE_ONE_SHOT);
+    }
   }
 
   return NS_OK;
 }
 
 void
 nsDownloadManager::ConfirmCancelDownloads(PRInt32 aCount,
                                           nsISupportsPRBool *aCancelDownloads,
--- a/toolkit/components/downloads/src/nsDownloadManager.h
+++ b/toolkit/components/downloads/src/nsDownloadManager.h
@@ -60,16 +60,17 @@
 #include "nsISupportsPrimitives.h"
 #include "nsIMIMEInfo.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageStatement.h"
 #include "nsCOMArray.h"
 #include "nsArrayEnumerator.h"
 #include "nsAutoPtr.h"
 #include "nsIObserverService.h"
+#include "nsITimer.h"
 
 typedef PRInt16 DownloadState;
 typedef PRInt16 DownloadType;
 
 class nsDownload;
 
 #if defined(XP_WIN) && !defined(__MINGW32__)
 class nsDownloadScanner;
@@ -183,16 +184,27 @@ protected:
   /**
    * Stop tracking the active downloads. Only use this when we're about to quit
    * the download manager because we destroy our list of active downloads to
    * break the dlmgr<->dl cycle. Active downloads that aren't real-paused will
    * be canceled.
    */
   nsresult RemoveAllDownloads();
 
+  /**
+   * Callback used for resuming downloads after getting a wake notification.
+   *
+   * @param aTimer
+   *        Timer object fired after some delay after a wake notification
+   * @param aClosure
+   *        nsDownloadManager object used to resume downloads
+   */
+  static void ResumeOnWakeCallback(nsITimer *aTimer, void *aClosure);
+  nsCOMPtr<nsITimer> mResumeOnWakeTimer;
+
   void ConfirmCancelDownloads(PRInt32 aCount,
                               nsISupportsPRBool *aCancelDownloads,
                               const PRUnichar *aTitle,
                               const PRUnichar *aCancelMessageMultiple,
                               const PRUnichar *aCancelMessageSingle,
                               const PRUnichar *aDontCancelButton);
 
   PRInt32 GetRetentionBehavior();
new file mode 100644
--- /dev/null
+++ b/toolkit/components/downloads/test/unit/test_sleep_wake.js
@@ -0,0 +1,170 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Download Manager Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Edward Lee <edward.lee@engineering.uiuc.edu>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * 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 ***** */
+
+/**
+ * Test bug 335418 by making sure the download manager responds to sleep and
+ * wake notifications by pausing and resuming downloads.
+ */
+
+const nsIF = Ci.nsIFile;
+const nsIDM = Ci.nsIDownloadManager;
+const nsIWBP = Ci.nsIWebBrowserPersist;
+const nsIWPL = Ci.nsIWebProgressListener;
+const dm = Cc["@mozilla.org/download-manager;1"].getService(nsIDM);
+dm.cleanUp();
+
+function notify(aTopic)
+{
+  Cc["@mozilla.org/observer-service;1"].
+  getService(Ci.nsIObserverService).
+  notifyObservers(null, aTopic, null);
+}
+
+function run_test()
+{
+  /**
+   * 0. Speed up the resume on wake delay from 10 seconds
+   */
+  Cc["@mozilla.org/preferences-service;1"].
+  getService(Ci.nsIPrefBranch).
+  setIntPref("browser.download.manager.resumeOnWakeDelay", 1000);
+
+  /**
+   * 1. Create data for http server to send
+   */
+  // data starts at 10 bytes
+  let data = "1234567890";
+  // data * 10^4 = 100,000 bytes (actually 101,111 bytes with newline)
+  for (let i = 0; i < 4; i++)
+    data = [data,data,data,data,data,data,data,data,data,data,"\n"].join("");
+
+  /**
+   * 2. Start the http server that can handle resume
+   */
+  let httpserv = new nsHttpServer();
+  let didResumeServer = false;
+  httpserv.registerPathHandler("/resume", function(meta, resp) {
+    let body = data;
+    resp.setHeader("Content-Type", "text/html", false);
+    if (meta.hasHeader("Range")) {
+      // track that we resumed for testing
+      didResumeServer = true;
+      // Syntax: bytes=[from]-[to] (we don't support multiple ranges)
+      let matches = meta.getHeader("Range").match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+      let from = (matches[1] === undefined) ? 0 : matches[1];
+      let to = (matches[2] === undefined) ? data.length - 1 : matches[2];
+      if (from >= data.length) {
+        resp.setStatusLine(meta.httpVersion, 416, "Start pos too high");
+        resp.setHeader("Content-Range", "*/" + data.length);
+        return;
+      }
+      body = body.substring(from, to + 1);
+      // always respond to successful range requests with 206
+      resp.setStatusLine(meta.httpVersion, 206, "Partial Content");
+      resp.setHeader("Content-Range", from + "-" + to + "/" + data.length);
+    }
+    resp.bodyOutputStream.write(body, body.length);
+  });
+  httpserv.start(4444);
+
+  /**
+   * 3. Perform various actions for certain download states
+   */
+  let didPause = false;
+  let didResumeDownload = false;
+  dm.addListener({
+    onDownloadStateChange: function(a, aDl) {
+      if (aDl.state == nsIDM.DOWNLOAD_DOWNLOADING && !didPause) {
+        /**
+         * (1) queued -> downloading = pause the download with sleep
+         */
+        notify("sleep_notification");
+      } else if (aDl.state == nsIDM.DOWNLOAD_PAUSED) {
+        /**
+         * (2) downloading -> paused
+         */
+        didPause = true;
+      } else if (aDl.state == nsIDM.DOWNLOAD_FINISHED) {
+        /**
+         * (4) downloading (resumed) -> finished = check tests
+         */
+        // did we pause at all?
+        do_check_true(didPause);
+        // did we real-resume and not fake-resume?
+        do_check_true(didResumeDownload);
+        // extra real-resume check for the server
+        do_check_true(didResumeServer);
+
+        httpserv.stop();
+        aDl.targetFile.remove(false);
+        // we're done with the test!
+        do_test_finished();
+      }
+    },
+    onStateChange: function(a, b, aState, d, aDl) {
+      if ((aState & nsIWPL.STATE_STOP) && didPause && !didResumeServer &&
+          !didResumeDownload) {
+        /**
+         * (3) paused -> stopped = resume the download by waking
+         */
+        notify("wake_notification");
+        didResumeDownload = true;
+      }
+    },
+    onProgressChange: function(a, b, c, d, e, f, g) { },
+    onSecurityChange: function(a, b, c, d) { }
+  });
+
+  /**
+   * 4. Start the download
+   */
+  let destFile = dirSvc.get("ProfD", nsIF);
+  destFile.append("sleep_wake");
+  let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].
+                createInstance(nsIWBP);
+  persist.persistFlags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+                         nsIWBP.PERSIST_FLAGS_BYPASS_CACHE |
+                         nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
+  let dl = dm.addDownload(nsIDM.DOWNLOAD_TYPE_DOWNLOAD,
+                          createURI("http://localhost:4444/resume"),
+                          createURI(destFile), null, null,
+                          Math.round(Date.now() * 1000), null, persist);
+  persist.progressListener = dl.QueryInterface(nsIWPL);
+  persist.saveURI(dl.source, null, null, null, null, dl.targetFile);
+
+  // Mark as pending, so clear this when we actually finish the download
+  do_test_pending();
+}