b2g/components/UpdatePrompt.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: Java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 * vim: sw=2 ts=8 et :
michael@0 3 */
michael@0 4 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 5 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 6 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 7
michael@0 8 const Cc = Components.classes;
michael@0 9 const Ci = Components.interfaces;
michael@0 10 const Cu = Components.utils;
michael@0 11 const Cr = Components.results;
michael@0 12
michael@0 13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 14 Cu.import("resource://gre/modules/Services.jsm");
michael@0 15 Cu.import("resource://gre/modules/WebappsUpdater.jsm");
michael@0 16
michael@0 17 const VERBOSE = 1;
michael@0 18 let log =
michael@0 19 VERBOSE ?
michael@0 20 function log_dump(msg) { dump("UpdatePrompt: "+ msg +"\n"); } :
michael@0 21 function log_noop(msg) { };
michael@0 22
michael@0 23 const PREF_APPLY_PROMPT_TIMEOUT = "b2g.update.apply-prompt-timeout";
michael@0 24 const PREF_APPLY_IDLE_TIMEOUT = "b2g.update.apply-idle-timeout";
michael@0 25 const PREF_DOWNLOAD_WATCHDOG_TIMEOUT = "b2g.update.download-watchdog-timeout";
michael@0 26 const PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES = "b2g.update.download-watchdog-max-retries";
michael@0 27
michael@0 28 const NETWORK_ERROR_OFFLINE = 111;
michael@0 29 const HTTP_ERROR_OFFSET = 1000;
michael@0 30
michael@0 31 const STATE_DOWNLOADING = 'downloading';
michael@0 32
michael@0 33 XPCOMUtils.defineLazyServiceGetter(Services, "aus",
michael@0 34 "@mozilla.org/updates/update-service;1",
michael@0 35 "nsIApplicationUpdateService");
michael@0 36
michael@0 37 XPCOMUtils.defineLazyServiceGetter(Services, "um",
michael@0 38 "@mozilla.org/updates/update-manager;1",
michael@0 39 "nsIUpdateManager");
michael@0 40
michael@0 41 XPCOMUtils.defineLazyServiceGetter(Services, "idle",
michael@0 42 "@mozilla.org/widget/idleservice;1",
michael@0 43 "nsIIdleService");
michael@0 44
michael@0 45 XPCOMUtils.defineLazyServiceGetter(Services, "settings",
michael@0 46 "@mozilla.org/settingsService;1",
michael@0 47 "nsISettingsService");
michael@0 48
michael@0 49 XPCOMUtils.defineLazyServiceGetter(Services, 'env',
michael@0 50 '@mozilla.org/process/environment;1',
michael@0 51 'nsIEnvironment');
michael@0 52
michael@0 53 function useSettings() {
michael@0 54 // When we're running in the real phone, then we can use settings.
michael@0 55 // But when we're running as part of xpcshell, there is no settings database
michael@0 56 // and trying to use settings in this scenario causes lots of weird
michael@0 57 // assertions at shutdown time.
michael@0 58 if (typeof useSettings.result === "undefined") {
michael@0 59 useSettings.result = !Services.env.get("XPCSHELL_TEST_PROFILE_DIR");
michael@0 60 }
michael@0 61 return useSettings.result;
michael@0 62 }
michael@0 63
michael@0 64 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
michael@0 65 "resource://gre/modules/SystemAppProxy.jsm");
michael@0 66
michael@0 67 function UpdateCheckListener(updatePrompt) {
michael@0 68 this._updatePrompt = updatePrompt;
michael@0 69 }
michael@0 70
michael@0 71 UpdateCheckListener.prototype = {
michael@0 72 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener]),
michael@0 73
michael@0 74 _updatePrompt: null,
michael@0 75
michael@0 76 onCheckComplete: function UCL_onCheckComplete(request, updates, updateCount) {
michael@0 77 if (Services.um.activeUpdate) {
michael@0 78 // We're actively downloading an update, that's the update the user should
michael@0 79 // see, even if a newer update is available.
michael@0 80 this._updatePrompt.setUpdateStatus("active-update");
michael@0 81 this._updatePrompt.showUpdateAvailable(Services.um.activeUpdate);
michael@0 82 return;
michael@0 83 }
michael@0 84
michael@0 85 if (updateCount == 0) {
michael@0 86 this._updatePrompt.setUpdateStatus("no-updates");
michael@0 87 return;
michael@0 88 }
michael@0 89
michael@0 90 let update = Services.aus.selectUpdate(updates, updateCount);
michael@0 91 if (!update) {
michael@0 92 this._updatePrompt.setUpdateStatus("already-latest-version");
michael@0 93 return;
michael@0 94 }
michael@0 95
michael@0 96 this._updatePrompt.setUpdateStatus("check-complete");
michael@0 97 this._updatePrompt.showUpdateAvailable(update);
michael@0 98 },
michael@0 99
michael@0 100 onError: function UCL_onError(request, update) {
michael@0 101 // nsIUpdate uses a signed integer for errorCode while any platform errors
michael@0 102 // require all 32 bits.
michael@0 103 let errorCode = update.errorCode >>> 0;
michael@0 104 let isNSError = (errorCode >>> 31) == 1;
michael@0 105
michael@0 106 if (errorCode == NETWORK_ERROR_OFFLINE) {
michael@0 107 this._updatePrompt.setUpdateStatus("retry-when-online");
michael@0 108 } else if (isNSError) {
michael@0 109 this._updatePrompt.setUpdateStatus("check-error-" + errorCode);
michael@0 110 } else if (errorCode > HTTP_ERROR_OFFSET) {
michael@0 111 let httpErrorCode = errorCode - HTTP_ERROR_OFFSET;
michael@0 112 this._updatePrompt.setUpdateStatus("check-error-http-" + httpErrorCode);
michael@0 113 }
michael@0 114
michael@0 115 Services.aus.QueryInterface(Ci.nsIUpdateCheckListener);
michael@0 116 Services.aus.onError(request, update);
michael@0 117 }
michael@0 118 };
michael@0 119
michael@0 120 function UpdatePrompt() {
michael@0 121 this.wrappedJSObject = this;
michael@0 122 this._updateCheckListener = new UpdateCheckListener(this);
michael@0 123 Services.obs.addObserver(this, "update-check-start", false);
michael@0 124 }
michael@0 125
michael@0 126 UpdatePrompt.prototype = {
michael@0 127 classID: Components.ID("{88b3eb21-d072-4e3b-886d-f89d8c49fe59}"),
michael@0 128 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt,
michael@0 129 Ci.nsIUpdateCheckListener,
michael@0 130 Ci.nsIRequestObserver,
michael@0 131 Ci.nsIProgressEventSink,
michael@0 132 Ci.nsIObserver]),
michael@0 133 _xpcom_factory: XPCOMUtils.generateSingletonFactory(UpdatePrompt),
michael@0 134
michael@0 135 _update: null,
michael@0 136 _applyPromptTimer: null,
michael@0 137 _waitingForIdle: false,
michael@0 138 _updateCheckListner: null,
michael@0 139
michael@0 140 get applyPromptTimeout() {
michael@0 141 return Services.prefs.getIntPref(PREF_APPLY_PROMPT_TIMEOUT);
michael@0 142 },
michael@0 143
michael@0 144 get applyIdleTimeout() {
michael@0 145 return Services.prefs.getIntPref(PREF_APPLY_IDLE_TIMEOUT);
michael@0 146 },
michael@0 147
michael@0 148 handleContentStart: function UP_handleContentStart() {
michael@0 149 SystemAppProxy.addEventListener("mozContentEvent", this);
michael@0 150 },
michael@0 151
michael@0 152 // nsIUpdatePrompt
michael@0 153
michael@0 154 // FIXME/bug 737601: we should have users opt-in to downloading
michael@0 155 // updates when on a billed pipe. Initially, opt-in for 3g, but
michael@0 156 // that doesn't cover all cases.
michael@0 157 checkForUpdates: function UP_checkForUpdates() { },
michael@0 158
michael@0 159 showUpdateAvailable: function UP_showUpdateAvailable(aUpdate) {
michael@0 160 if (!this.sendUpdateEvent("update-available", aUpdate)) {
michael@0 161
michael@0 162 log("Unable to prompt for available update, forcing download");
michael@0 163 this.downloadUpdate(aUpdate);
michael@0 164 }
michael@0 165 },
michael@0 166
michael@0 167 showUpdateDownloaded: function UP_showUpdateDownloaded(aUpdate, aBackground) {
michael@0 168 // The update has been downloaded and staged. We send the update-downloaded
michael@0 169 // event right away. After the user has been idle for a while, we send the
michael@0 170 // update-prompt-restart event, increasing the chances that we can apply the
michael@0 171 // update quietly without user intervention.
michael@0 172 this.sendUpdateEvent("update-downloaded", aUpdate);
michael@0 173
michael@0 174 if (Services.idle.idleTime >= this.applyIdleTimeout) {
michael@0 175 this.showApplyPrompt(aUpdate);
michael@0 176 return;
michael@0 177 }
michael@0 178
michael@0 179 let applyIdleTimeoutSeconds = this.applyIdleTimeout / 1000;
michael@0 180 // We haven't been idle long enough, so register an observer
michael@0 181 log("Update is ready to apply, registering idle timeout of " +
michael@0 182 applyIdleTimeoutSeconds + " seconds before prompting.");
michael@0 183
michael@0 184 this._update = aUpdate;
michael@0 185 this.waitForIdle();
michael@0 186 },
michael@0 187
michael@0 188 showUpdateError: function UP_showUpdateError(aUpdate) {
michael@0 189 log("Update error, state: " + aUpdate.state + ", errorCode: " +
michael@0 190 aUpdate.errorCode);
michael@0 191 this.sendUpdateEvent("update-error", aUpdate);
michael@0 192 this.setUpdateStatus(aUpdate.statusText);
michael@0 193 },
michael@0 194
michael@0 195 showUpdateHistory: function UP_showUpdateHistory(aParent) { },
michael@0 196 showUpdateInstalled: function UP_showUpdateInstalled() {
michael@0 197 if (useSettings()) {
michael@0 198 let lock = Services.settings.createLock();
michael@0 199 lock.set("deviceinfo.last_updated", Date.now(), null, null);
michael@0 200 }
michael@0 201 },
michael@0 202
michael@0 203 // Custom functions
michael@0 204
michael@0 205 waitForIdle: function UP_waitForIdle() {
michael@0 206 if (this._waitingForIdle) {
michael@0 207 return;
michael@0 208 }
michael@0 209
michael@0 210 this._waitingForIdle = true;
michael@0 211 Services.idle.addIdleObserver(this, this.applyIdleTimeout / 1000);
michael@0 212 Services.obs.addObserver(this, "quit-application", false);
michael@0 213 },
michael@0 214
michael@0 215 setUpdateStatus: function UP_setUpdateStatus(aStatus) {
michael@0 216 if (useSettings()) {
michael@0 217 log("Setting gecko.updateStatus: " + aStatus);
michael@0 218
michael@0 219 let lock = Services.settings.createLock();
michael@0 220 lock.set("gecko.updateStatus", aStatus, null);
michael@0 221 }
michael@0 222 },
michael@0 223
michael@0 224 showApplyPrompt: function UP_showApplyPrompt(aUpdate) {
michael@0 225 if (!this.sendUpdateEvent("update-prompt-apply", aUpdate)) {
michael@0 226 log("Unable to prompt, forcing restart");
michael@0 227 this.restartProcess();
michael@0 228 return;
michael@0 229 }
michael@0 230
michael@0 231 #ifdef MOZ_B2G_RIL
michael@0 232 let window = Services.wm.getMostRecentWindow("navigator:browser");
michael@0 233 let pinReq = window.navigator.mozIccManager.getCardLock("pin");
michael@0 234 pinReq.onsuccess = function(e) {
michael@0 235 if (e.target.result.enabled) {
michael@0 236 // The SIM is pin locked. Don't use a fallback timer. This means that
michael@0 237 // the user has to press Install to apply the update. If we use the
michael@0 238 // timer, and the timer reboots the phone, then the phone will be
michael@0 239 // unusable until the SIM is unlocked.
michael@0 240 log("SIM is pin locked. Not starting fallback timer.");
michael@0 241 } else {
michael@0 242 // This means that no pin lock is enabled, so we go ahead and start
michael@0 243 // the fallback timer.
michael@0 244 this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
michael@0 245 }
michael@0 246 }.bind(this);
michael@0 247 pinReq.onerror = function(e) {
michael@0 248 this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
michael@0 249 }.bind(this);
michael@0 250 #else
michael@0 251 // Schedule a fallback timeout in case the UI is unable to respond or show
michael@0 252 // a prompt for some reason.
michael@0 253 this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
michael@0 254 #endif
michael@0 255 },
michael@0 256
michael@0 257 _copyProperties: ["appVersion", "buildID", "detailsURL", "displayVersion",
michael@0 258 "errorCode", "isOSUpdate", "platformVersion",
michael@0 259 "previousAppVersion", "state", "statusText"],
michael@0 260
michael@0 261 sendUpdateEvent: function UP_sendUpdateEvent(aType, aUpdate) {
michael@0 262 let detail = {};
michael@0 263 for each (let property in this._copyProperties) {
michael@0 264 detail[property] = aUpdate[property];
michael@0 265 }
michael@0 266
michael@0 267 let patch = aUpdate.selectedPatch;
michael@0 268 if (!patch && aUpdate.patchCount > 0) {
michael@0 269 // For now we just check the first patch to get size information if a
michael@0 270 // patch hasn't been selected yet.
michael@0 271 patch = aUpdate.getPatchAt(0);
michael@0 272 }
michael@0 273
michael@0 274 if (patch) {
michael@0 275 detail.size = patch.size;
michael@0 276 detail.updateType = patch.type;
michael@0 277 } else {
michael@0 278 log("Warning: no patches available in update");
michael@0 279 }
michael@0 280
michael@0 281 this._update = aUpdate;
michael@0 282 return this.sendChromeEvent(aType, detail);
michael@0 283 },
michael@0 284
michael@0 285 sendChromeEvent: function UP_sendChromeEvent(aType, aDetail) {
michael@0 286 let detail = aDetail || {};
michael@0 287 detail.type = aType;
michael@0 288
michael@0 289 let sent = SystemAppProxy.dispatchEvent(detail);
michael@0 290 if (!sent) {
michael@0 291 log("Warning: Couldn't send update event " + aType +
michael@0 292 ": no content browser. Will send again when content becomes available.");
michael@0 293 return false;
michael@0 294 }
michael@0 295 return true;
michael@0 296 },
michael@0 297
michael@0 298 handleAvailableResult: function UP_handleAvailableResult(aDetail) {
michael@0 299 // If the user doesn't choose "download", the updater will implicitly call
michael@0 300 // showUpdateAvailable again after a certain period of time
michael@0 301 switch (aDetail.result) {
michael@0 302 case "download":
michael@0 303 this.downloadUpdate(this._update);
michael@0 304 break;
michael@0 305 }
michael@0 306 },
michael@0 307
michael@0 308 handleApplyPromptResult: function UP_handleApplyPromptResult(aDetail) {
michael@0 309 if (this._applyPromptTimer) {
michael@0 310 this._applyPromptTimer.cancel();
michael@0 311 this._applyPromptTimer = null;
michael@0 312 }
michael@0 313
michael@0 314 switch (aDetail.result) {
michael@0 315 case "wait":
michael@0 316 // Wait until the user is idle before prompting to apply the update
michael@0 317 this.waitForIdle();
michael@0 318 break;
michael@0 319 case "restart":
michael@0 320 this.finishUpdate();
michael@0 321 this._update = null;
michael@0 322 break;
michael@0 323 }
michael@0 324 },
michael@0 325
michael@0 326 downloadUpdate: function UP_downloadUpdate(aUpdate) {
michael@0 327 if (!aUpdate) {
michael@0 328 aUpdate = Services.um.activeUpdate;
michael@0 329 if (!aUpdate) {
michael@0 330 log("No active update found to download");
michael@0 331 return;
michael@0 332 }
michael@0 333 }
michael@0 334
michael@0 335 let status = Services.aus.downloadUpdate(aUpdate, true);
michael@0 336 if (status == STATE_DOWNLOADING) {
michael@0 337 Services.aus.addDownloadListener(this);
michael@0 338 return;
michael@0 339 }
michael@0 340
michael@0 341 // If the update has already been downloaded and applied, then
michael@0 342 // Services.aus.downloadUpdate will return immediately and not
michael@0 343 // call showUpdateDownloaded, so we detect this.
michael@0 344 if (aUpdate.state == "applied" && aUpdate.errorCode == 0) {
michael@0 345 this.showUpdateDownloaded(aUpdate, true);
michael@0 346 return;
michael@0 347 }
michael@0 348
michael@0 349 log("Error downloading update " + aUpdate.name + ": " + aUpdate.errorCode);
michael@0 350 let errorCode = aUpdate.errorCode >>> 0;
michael@0 351 if (errorCode == Cr.NS_ERROR_FILE_TOO_BIG) {
michael@0 352 aUpdate.statusText = "file-too-big";
michael@0 353 }
michael@0 354 this.showUpdateError(aUpdate);
michael@0 355 },
michael@0 356
michael@0 357 handleDownloadCancel: function UP_handleDownloadCancel() {
michael@0 358 log("Pausing download");
michael@0 359 Services.aus.pauseDownload();
michael@0 360 },
michael@0 361
michael@0 362 finishUpdate: function UP_finishUpdate() {
michael@0 363 if (!this._update.isOSUpdate) {
michael@0 364 // Standard gecko+gaia updates will just need to restart the process
michael@0 365 this.restartProcess();
michael@0 366 return;
michael@0 367 }
michael@0 368
michael@0 369 try {
michael@0 370 Services.aus.applyOsUpdate(this._update);
michael@0 371 }
michael@0 372 catch (e) {
michael@0 373 this._update.errorCode = Cr.NS_ERROR_FAILURE;
michael@0 374 this.showUpdateError(this._update);
michael@0 375 }
michael@0 376 },
michael@0 377
michael@0 378 restartProcess: function UP_restartProcess() {
michael@0 379 log("Update downloaded, restarting to apply it");
michael@0 380
michael@0 381 let callbackAfterSet = function() {
michael@0 382 #ifndef MOZ_WIDGET_GONK
michael@0 383 let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
michael@0 384 .getService(Ci.nsIAppStartup);
michael@0 385 appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
michael@0 386 #else
michael@0 387 // NB: on Gonk, we rely on the system process manager to restart us.
michael@0 388 let pmService = Cc["@mozilla.org/power/powermanagerservice;1"]
michael@0 389 .getService(Ci.nsIPowerManagerService);
michael@0 390 pmService.restart();
michael@0 391 #endif
michael@0 392 }
michael@0 393
michael@0 394 if (useSettings()) {
michael@0 395 // Save current os version in deviceinfo.previous_os
michael@0 396 let lock = Services.settings.createLock({
michael@0 397 handle: callbackAfterSet,
michael@0 398 handleAbort: function(error) {
michael@0 399 log("Abort callback when trying to set previous_os: " + error);
michael@0 400 callbackAfterSet();
michael@0 401 }
michael@0 402 });
michael@0 403 lock.get("deviceinfo.os", {
michael@0 404 handle: function(name, value) {
michael@0 405 log("Set previous_os to: " + value);
michael@0 406 lock.set("deviceinfo.previous_os", value, null, null);
michael@0 407 }
michael@0 408 });
michael@0 409 }
michael@0 410 },
michael@0 411
michael@0 412 forceUpdateCheck: function UP_forceUpdateCheck() {
michael@0 413 log("Forcing update check");
michael@0 414
michael@0 415 let checker = Cc["@mozilla.org/updates/update-checker;1"]
michael@0 416 .createInstance(Ci.nsIUpdateChecker);
michael@0 417 checker.checkForUpdates(this._updateCheckListener, true);
michael@0 418 },
michael@0 419
michael@0 420 handleEvent: function UP_handleEvent(evt) {
michael@0 421 if (evt.type !== "mozContentEvent") {
michael@0 422 return;
michael@0 423 }
michael@0 424
michael@0 425 let detail = evt.detail;
michael@0 426 if (!detail) {
michael@0 427 return;
michael@0 428 }
michael@0 429
michael@0 430 switch (detail.type) {
michael@0 431 case "force-update-check":
michael@0 432 this.forceUpdateCheck();
michael@0 433 break;
michael@0 434 case "update-available-result":
michael@0 435 this.handleAvailableResult(detail);
michael@0 436 // If we started the apply prompt timer, this means that we're waiting
michael@0 437 // for the user to press Later or Install Now. In this situation we
michael@0 438 // don't want to clear this._update, becuase handleApplyPromptResult
michael@0 439 // needs it.
michael@0 440 if (this._applyPromptTimer == null && !this._waitingForIdle) {
michael@0 441 this._update = null;
michael@0 442 }
michael@0 443 break;
michael@0 444 case "update-download-cancel":
michael@0 445 this.handleDownloadCancel();
michael@0 446 break;
michael@0 447 case "update-prompt-apply-result":
michael@0 448 this.handleApplyPromptResult(detail);
michael@0 449 break;
michael@0 450 }
michael@0 451 },
michael@0 452
michael@0 453 // nsIObserver
michael@0 454
michael@0 455 observe: function UP_observe(aSubject, aTopic, aData) {
michael@0 456 switch (aTopic) {
michael@0 457 case "idle":
michael@0 458 this._waitingForIdle = false;
michael@0 459 this.showApplyPrompt(this._update);
michael@0 460 // Fall through
michael@0 461 case "quit-application":
michael@0 462 Services.idle.removeIdleObserver(this, this.applyIdleTimeout / 1000);
michael@0 463 Services.obs.removeObserver(this, "quit-application");
michael@0 464 break;
michael@0 465 case "update-check-start":
michael@0 466 WebappsUpdater.updateApps();
michael@0 467 break;
michael@0 468 }
michael@0 469 },
michael@0 470
michael@0 471 // nsITimerCallback
michael@0 472
michael@0 473 notify: function UP_notify(aTimer) {
michael@0 474 if (aTimer == this._applyPromptTimer) {
michael@0 475 log("Timed out waiting for result, restarting");
michael@0 476 this._applyPromptTimer = null;
michael@0 477 this.finishUpdate();
michael@0 478 this._update = null;
michael@0 479 return;
michael@0 480 }
michael@0 481 if (aTimer == this._watchdogTimer) {
michael@0 482 log("Download watchdog fired");
michael@0 483 this._watchdogTimer = null;
michael@0 484 this._autoRestartDownload = true;
michael@0 485 Services.aus.pauseDownload();
michael@0 486 return;
michael@0 487 }
michael@0 488 },
michael@0 489
michael@0 490 createTimer: function UP_createTimer(aTimeoutMs) {
michael@0 491 let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 492 timer.initWithCallback(this, aTimeoutMs, timer.TYPE_ONE_SHOT);
michael@0 493 return timer;
michael@0 494 },
michael@0 495
michael@0 496 // nsIRequestObserver
michael@0 497
michael@0 498 _startedSent: false,
michael@0 499
michael@0 500 _watchdogTimer: null,
michael@0 501
michael@0 502 _autoRestartDownload: false,
michael@0 503 _autoRestartCount: 0,
michael@0 504
michael@0 505 startWatchdogTimer: function UP_startWatchdogTimer() {
michael@0 506 let watchdogTimeout = 120000; // 120 seconds
michael@0 507 try {
michael@0 508 watchdogTimeout = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_TIMEOUT);
michael@0 509 } catch (e) {
michael@0 510 // This means that the preference doesn't exist. watchdogTimeout will
michael@0 511 // retain its default assigned above.
michael@0 512 }
michael@0 513 if (watchdogTimeout <= 0) {
michael@0 514 // 0 implies don't bother using the watchdog timer at all.
michael@0 515 this._watchdogTimer = null;
michael@0 516 return;
michael@0 517 }
michael@0 518 if (this._watchdogTimer) {
michael@0 519 this._watchdogTimer.cancel();
michael@0 520 } else {
michael@0 521 this._watchdogTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 522 }
michael@0 523 this._watchdogTimer.initWithCallback(this, watchdogTimeout,
michael@0 524 Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 525 },
michael@0 526
michael@0 527 stopWatchdogTimer: function UP_stopWatchdogTimer() {
michael@0 528 if (this._watchdogTimer) {
michael@0 529 this._watchdogTimer.cancel();
michael@0 530 this._watchdogTimer = null;
michael@0 531 }
michael@0 532 },
michael@0 533
michael@0 534 touchWatchdogTimer: function UP_touchWatchdogTimer() {
michael@0 535 this.startWatchdogTimer();
michael@0 536 },
michael@0 537
michael@0 538 onStartRequest: function UP_onStartRequest(aRequest, aContext) {
michael@0 539 // Wait until onProgress to send the update-download-started event, in case
michael@0 540 // this request turns out to fail for some reason
michael@0 541 this._startedSent = false;
michael@0 542 this.startWatchdogTimer();
michael@0 543 },
michael@0 544
michael@0 545 onStopRequest: function UP_onStopRequest(aRequest, aContext, aStatusCode) {
michael@0 546 this.stopWatchdogTimer();
michael@0 547 Services.aus.removeDownloadListener(this);
michael@0 548 let paused = !Components.isSuccessCode(aStatusCode);
michael@0 549 if (!paused) {
michael@0 550 // The download was successful, no need to restart
michael@0 551 this._autoRestartDownload = false;
michael@0 552 }
michael@0 553 if (this._autoRestartDownload) {
michael@0 554 this._autoRestartDownload = false;
michael@0 555 let watchdogMaxRetries = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES);
michael@0 556 this._autoRestartCount++;
michael@0 557 if (this._autoRestartCount > watchdogMaxRetries) {
michael@0 558 log("Download - retry count exceeded - error");
michael@0 559 // We exceeded the max retries. Treat the download like an error,
michael@0 560 // which will give the user a chance to restart manually later.
michael@0 561 this._autoRestartCount = 0;
michael@0 562 if (Services.um.activeUpdate) {
michael@0 563 this.showUpdateError(Services.um.activeUpdate);
michael@0 564 }
michael@0 565 return;
michael@0 566 }
michael@0 567 log("Download - restarting download - attempt " + this._autoRestartCount);
michael@0 568 this.downloadUpdate(null);
michael@0 569 return;
michael@0 570 }
michael@0 571 this._autoRestartCount = 0;
michael@0 572 this.sendChromeEvent("update-download-stopped", {
michael@0 573 paused: paused
michael@0 574 });
michael@0 575 },
michael@0 576
michael@0 577 // nsIProgressEventSink
michael@0 578
michael@0 579 onProgress: function UP_onProgress(aRequest, aContext, aProgress,
michael@0 580 aProgressMax) {
michael@0 581 if (aProgress == aProgressMax) {
michael@0 582 // The update.mar validation done by onStopRequest may take
michael@0 583 // a while before the onStopRequest callback is made, so stop
michael@0 584 // the timer now.
michael@0 585 this.stopWatchdogTimer();
michael@0 586 } else {
michael@0 587 this.touchWatchdogTimer();
michael@0 588 }
michael@0 589 if (!this._startedSent) {
michael@0 590 this.sendChromeEvent("update-download-started", {
michael@0 591 total: aProgressMax
michael@0 592 });
michael@0 593 this._startedSent = true;
michael@0 594 }
michael@0 595
michael@0 596 this.sendChromeEvent("update-download-progress", {
michael@0 597 progress: aProgress,
michael@0 598 total: aProgressMax
michael@0 599 });
michael@0 600 },
michael@0 601
michael@0 602 onStatus: function UP_onStatus(aRequest, aUpdate, aStatus, aStatusArg) { }
michael@0 603 };
michael@0 604
michael@0 605 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]);

mercurial