зеркало из https://github.com/mozilla/gecko-dev.git
Bug 791829 - Add support for resuming a stopped/partial update. r=rstrong
This commit is contained in:
Родитель
1b5a07f273
Коммит
d7a69396d1
|
@ -49,6 +49,8 @@ const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
|
|||
const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
|
||||
const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
|
||||
const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors";
|
||||
const PREF_APP_UPDATE_SOCKET_ERRORS = "app.update.socket.maxErrors";
|
||||
const PREF_APP_UPDATE_RETRY_TIMEOUT = "app.update.socket.retryTimeout";
|
||||
|
||||
const PREF_APP_DISTRIBUTION = "distribution.id";
|
||||
const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
|
||||
|
@ -164,6 +166,13 @@ const UPDATE_WINDOW_NAME = "Update:Wizard";
|
|||
// setting the app.update.service.enabled preference to false.
|
||||
const DEFAULT_SERVICE_MAX_ERRORS = 10;
|
||||
|
||||
// The number of consecutive socket errors to allow before falling back to
|
||||
// downloading a different MAR file or failing if already downloading the full.
|
||||
const DEFAULT_SOCKET_MAX_ERRORS = 10;
|
||||
|
||||
// The number of milliseconds to wait before retrying a connection error.
|
||||
const DEFAULT_UPDATE_RETRY_TIMEOUT = 2000;
|
||||
|
||||
var gLocale = null;
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
|
@ -1537,6 +1546,7 @@ const UpdateServiceFactory = {
|
|||
* @constructor
|
||||
*/
|
||||
function UpdateService() {
|
||||
LOG("Creating UpdateService");
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
}
|
||||
|
||||
|
@ -1557,6 +1567,16 @@ UpdateService.prototype = {
|
|||
*/
|
||||
_registeredOnlineObserver: false,
|
||||
|
||||
/**
|
||||
* The current number of consecutive socket errors
|
||||
*/
|
||||
_consecutiveSocketErrors: 0,
|
||||
|
||||
/**
|
||||
* A timer used to retry socket errors
|
||||
*/
|
||||
_retryTimer: null,
|
||||
|
||||
/**
|
||||
* Handle Observer Service notifications
|
||||
* @param subject
|
||||
|
@ -1578,6 +1598,10 @@ UpdateService.prototype = {
|
|||
case "xpcom-shutdown":
|
||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||
|
||||
if (this._retryTimer) {
|
||||
this._retryTimer.cancel();
|
||||
}
|
||||
|
||||
this.pauseDownload();
|
||||
// Prevent leaking the downloader (bug 454964)
|
||||
this._downloader = null;
|
||||
|
@ -1904,7 +1928,7 @@ UpdateService.prototype = {
|
|||
"another background check");
|
||||
|
||||
// the background checker is contained in notify
|
||||
this.notify(null);
|
||||
this._attemptResume();
|
||||
},
|
||||
|
||||
// nsIUpdateCheckListener
|
||||
|
@ -1949,6 +1973,32 @@ UpdateService.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Called when a connection should be resumed
|
||||
*/
|
||||
_attemptResume: function AUS_attemptResume() {
|
||||
LOG("UpdateService:_attemptResume")
|
||||
// If a download is in progress, then resume it.
|
||||
if (this._downloader && this._downloader._patch &&
|
||||
this._downloader._patch.state == STATE_DOWNLOADING &&
|
||||
this._downloader._update) {
|
||||
LOG("UpdateService:_attemptResume - _patch.state: " +
|
||||
this._downloader._patch.state);
|
||||
// Make sure downloading is the state for selectPatch to work correctly
|
||||
writeStatusFile(getUpdatesDir(), STATE_DOWNLOADING);
|
||||
var status = this.downloadUpdate(this._downloader._update,
|
||||
this._downloader.background);
|
||||
LOG("UpdateService:_attemptResume - downloadUpdate status: " + status);
|
||||
if (status == STATE_NONE) {
|
||||
cleanupActiveUpdate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.backgroundChecker.checkForUpdates(this, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notified when a timer fires
|
||||
* @param timer
|
||||
|
@ -1956,8 +2006,10 @@ UpdateService.prototype = {
|
|||
*/
|
||||
notify: function AUS_notify(timer) {
|
||||
// If a download is in progress or the patch has been staged do nothing.
|
||||
if (this.isDownloading || this._downloader && this._downloader.patchIsStaged)
|
||||
return;
|
||||
if (this.isDownloading ||
|
||||
this._downloader && this._downloader.patchIsStaged) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.backgroundChecker.checkForUpdates(this, false);
|
||||
},
|
||||
|
@ -2365,7 +2417,7 @@ UpdateService.prototype = {
|
|||
}
|
||||
// Set the previous application version prior to downloading the update.
|
||||
update.previousAppVersion = Services.appinfo.version;
|
||||
this._downloader = new Downloader(background);
|
||||
this._downloader = new Downloader(background, this);
|
||||
return this._downloader.downloadUpdate(update);
|
||||
},
|
||||
|
||||
|
@ -2805,6 +2857,7 @@ Checker.prototype = {
|
|||
* See nsIUpdateService.idl
|
||||
*/
|
||||
checkForUpdates: function UC_checkForUpdates(listener, force) {
|
||||
LOG("Checker: checkForUpdates, force: " + force);
|
||||
if (!listener)
|
||||
throw Cr.NS_ERROR_NULL_POINTER;
|
||||
|
||||
|
@ -3028,10 +3081,14 @@ Checker.prototype = {
|
|||
* @param background
|
||||
* Whether or not this downloader is operating in background
|
||||
* update mode.
|
||||
* @param updateService
|
||||
* The update service that created this downloader.
|
||||
* @constructor
|
||||
*/
|
||||
function Downloader(background) {
|
||||
function Downloader(background, updateService) {
|
||||
LOG("Creating Downloader");
|
||||
this.background = background;
|
||||
this.updateService = updateService;
|
||||
}
|
||||
Downloader.prototype = {
|
||||
/**
|
||||
|
@ -3060,6 +3117,7 @@ Downloader.prototype = {
|
|||
* Cancels the active download.
|
||||
*/
|
||||
cancel: function Downloader_cancel(cancelError) {
|
||||
LOG("Downloader: cancel");
|
||||
if (cancelError === undefined) {
|
||||
cancelError = Cr.NS_BINDING_ABORTED;
|
||||
}
|
||||
|
@ -3085,15 +3143,19 @@ Downloader.prototype = {
|
|||
* this point.
|
||||
*/
|
||||
_verifyDownload: function Downloader__verifyDownload() {
|
||||
LOG("Downloader:_verifyDownload called");
|
||||
if (!this._request)
|
||||
return false;
|
||||
|
||||
var destination = this._request.destination;
|
||||
|
||||
// Ensure that the file size matches the expected file size.
|
||||
if (destination.fileSize != this._patch.size)
|
||||
if (destination.fileSize != this._patch.size) {
|
||||
LOG("Downloader:_verifyDownload downloaded size != expected size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG("Downloader:_verifyDownload downloaded size == expected size.");
|
||||
var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
|
||||
|
@ -3119,7 +3181,13 @@ Downloader.prototype = {
|
|||
|
||||
fileStream.close();
|
||||
|
||||
return digest == this._patch.hashValue.toLowerCase();
|
||||
if (digest == this._patch.hashValue.toLowerCase()) {
|
||||
LOG("Downloader:_verifyDownload hashes match.");
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG("Downloader:_verifyDownload hashes do not match. ");
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -3232,6 +3300,7 @@ Downloader.prototype = {
|
|||
* A nsIUpdate object to download a patch for. Cannot be null.
|
||||
*/
|
||||
downloadUpdate: function Downloader_downloadUpdate(update) {
|
||||
LOG("UpdateService:_downloadUpdate");
|
||||
if (!update)
|
||||
throw Cr.NS_ERROR_NULL_POINTER;
|
||||
|
||||
|
@ -3373,6 +3442,7 @@ Downloader.prototype = {
|
|||
if (listener instanceof Ci.nsIProgressEventSink)
|
||||
listener.onProgress(request, context, progress, maxProgress);
|
||||
}
|
||||
this.updateService._consecutiveSocketErrors = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -3417,7 +3487,16 @@ Downloader.prototype = {
|
|||
// But what happens when there is already a UI showing?
|
||||
var state = this._patch.state;
|
||||
var shouldShowPrompt = false;
|
||||
var shouldRegisterOnlineObserver = false;
|
||||
var shouldRetrySoon = false;
|
||||
var deleteActiveUpdate = false;
|
||||
var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_RETRY_TIMEOUT,
|
||||
DEFAULT_UPDATE_RETRY_TIMEOUT);
|
||||
var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_ERRORS,
|
||||
DEFAULT_SOCKET_MAX_ERRORS);
|
||||
LOG("Downloader:onStopRequest - status: " + status + ", " +
|
||||
"current fail: " + this.updateService._consecutiveSocketErrors + ", " +
|
||||
"max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
|
||||
if (Components.isSuccessCode(status)) {
|
||||
if (this._verifyDownload()) {
|
||||
state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING
|
||||
|
@ -3451,10 +3530,28 @@ Downloader.prototype = {
|
|||
// Destroy the updates directory, since we're done with it.
|
||||
cleanUpUpdatesDir();
|
||||
}
|
||||
}
|
||||
else if (status != Cr.NS_BINDING_ABORTED &&
|
||||
status != Cr.NS_ERROR_ABORT &&
|
||||
status != Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
|
||||
} else if (status == Cr.NS_ERROR_OFFLINE) {
|
||||
// Register an online observer to try again.
|
||||
// The online observer will continue the incremental download by
|
||||
// calling downloadUpdate on the active update which continues
|
||||
// downloading the file from where it was.
|
||||
LOG("Downloader:onStopRequest - offline, register online observer: true");
|
||||
shouldRegisterOnlineObserver = true;
|
||||
deleteActiveUpdate = false;
|
||||
// Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED, and
|
||||
// NS_ERROR_NET_RESET can be returned when disconnecting the internet while
|
||||
// a download of a MAR is in progress. There may be others but I have not
|
||||
// encountered them during testing.
|
||||
} else if ((status == Cr.NS_ERROR_NET_TIMEOUT ||
|
||||
status == Cr.NS_ERROR_CONNECTION_REFUSED ||
|
||||
status == Cr.NS_ERROR_NET_RESET) &&
|
||||
this.updateService._consecutiveSocketErrors < maxFail) {
|
||||
LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
|
||||
shouldRetrySoon = true;
|
||||
deleteActiveUpdate = false;
|
||||
} else if (status != Cr.NS_BINDING_ABORTED &&
|
||||
status != Cr.NS_ERROR_ABORT &&
|
||||
status != Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
|
||||
LOG("Downloader:onStopRequest - non-verification failure");
|
||||
// Some sort of other failure, log this in the |statusText| property
|
||||
state = STATE_DOWNLOAD_FAILED;
|
||||
|
@ -3484,10 +3581,15 @@ Downloader.prototype = {
|
|||
}
|
||||
um.saveUpdates();
|
||||
|
||||
var listeners = this._listeners.concat();
|
||||
var listenerCount = listeners.length;
|
||||
for (var i = 0; i < listenerCount; ++i)
|
||||
listeners[i].onStopRequest(request, context, status);
|
||||
// Only notify listeners about the stopped state if we
|
||||
// aren't handling an internal retry.
|
||||
if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
|
||||
var listeners = this._listeners.concat();
|
||||
var listenerCount = listeners.length;
|
||||
for (var i = 0; i < listenerCount; ++i) {
|
||||
listeners[i].onStopRequest(request, context, status);
|
||||
}
|
||||
}
|
||||
|
||||
this._request = null;
|
||||
|
||||
|
@ -3552,8 +3654,23 @@ Downloader.prototype = {
|
|||
applyUpdateInBackground(this._update);
|
||||
}
|
||||
|
||||
// Prevent leaking the update object (bug 454964)
|
||||
this._update = null;
|
||||
if (shouldRegisterOnlineObserver) {
|
||||
LOG("Downloader:onStopRequest - Registering online observer");
|
||||
this.updateService._registerOnlineObserver();
|
||||
} else if (shouldRetrySoon) {
|
||||
LOG("Downloader:onStopRequest - Retrying soon");
|
||||
this.updateService._consecutiveSocketErrors++;
|
||||
if (this.updateService._retryTimer) {
|
||||
this.updateService._retryTimer.cancel();
|
||||
}
|
||||
this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this.updateService._retryTimer.initWithCallback(function() {
|
||||
this._attemptResume();
|
||||
}.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
} else {
|
||||
// Prevent leaking the update object (bug 454964)
|
||||
this._update = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче