Bug 1348087 Part 1 - Stop using nsIIncrementalDownload for application update. r=rstrong

--HG--
extra : source : 34638bc920b79fe3f60555d81fc9ac13991b9250
This commit is contained in:
Matt Howell 2017-10-18 13:12:48 -07:00
parent cdc677c157
commit 3262f458f0
6 changed files with 348 additions and 240 deletions

View File

@ -9,10 +9,6 @@ pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%
pref("startup.homepage_welcome_url.additional", "");
// The time interval between checks for a new version (in seconds)
pref("app.update.interval", 28800); // 8 hours
// The time interval between the downloading of mar file chunks in the
// background (in seconds)
// 0 means "download everything at once"
pref("app.update.download.backgroundInterval", 0);
// Give the user x seconds to react before showing the big UI. default=192 hours
pref("app.update.promptWaitTime", 691200);
// URL user can browse to manually if for some reason all update installation

View File

@ -7,10 +7,6 @@ pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%
pref("startup.homepage_welcome_url.additional", "");
// The time interval between checks for a new version (in seconds)
pref("app.update.interval", 7200); // 2 hours
// The time interval between the downloading of mar file chunks in the
// background (in seconds)
// 0 means "download everything at once"
pref("app.update.download.backgroundInterval", 0);
// Give the user x seconds to react before showing the big UI. default=12 hours
pref("app.update.promptWaitTime", 43200);
// URL user can browse to manually if for some reason all update installation

View File

@ -7,10 +7,6 @@ pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%
pref("startup.homepage_welcome_url.additional", "");
// Interval: Time between checks for a new version (in seconds)
pref("app.update.interval", 43200); // 12 hours
// The time interval between the downloading of mar file chunks in the
// background (in seconds)
// 0 means "download everything at once"
pref("app.update.download.backgroundInterval", 0);
// Give the user x seconds to react before showing the big UI. default=192 hours
pref("app.update.promptWaitTime", 691200);
// URL user can browse to manually if for some reason all update installation

View File

@ -7,9 +7,6 @@ pref("startup.homepage_welcome_url", "");
pref("startup.homepage_welcome_url.additional", "");
// The time interval between checks for a new version (in seconds)
pref("app.update.interval", 86400); // 24 hours
// The time interval between the downloading of mar file chunks in the
// background (in seconds)
pref("app.update.download.backgroundInterval", 60);
// Give the user x seconds to react before showing the big UI. default=24 hours
pref("app.update.promptWaitTime", 86400);
// URL user can browse to manually if for some reason all update installation

View File

@ -176,6 +176,7 @@ this.AUSTLMY = {
DWNLD_ERR_DOCUMENT_NOT_CACHED: 12,
DWNLD_ERR_VERIFY_NO_REQUEST: 13,
DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL: 14,
DWNLD_RESUME_FAILURE: 15,
/**
* Submit a telemetry ping for the update download result code.

View File

@ -21,7 +21,6 @@ const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";
const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype";
const PREF_APP_UPDATE_AUTO = "app.update.auto";
const PREF_APP_UPDATE_BACKGROUNDINTERVAL = "app.update.download.backgroundInterval";
const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
const PREF_APP_UPDATE_CANCELATIONS = "app.update.cancelations";
@ -157,10 +156,6 @@ const NETWORK_ERROR_OFFLINE = 111;
// Error codes should be < 1000. Errors above 1000 represent http status codes
const HTTP_ERROR_OFFSET = 1000;
const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
const DOWNLOAD_FOREGROUND_INTERVAL = 0;
const UPDATE_WINDOW_NAME = "Update:Wizard";
// The number of consecutive failures when updating using the service before
@ -191,6 +186,10 @@ const APPID_TO_TOPIC = {
"{33cb9019-c295-46dd-be21-8c4936574bee}": "xul-window-visible",
};
// Download progress notifications are throttled to fire at least this many
// milliseconds apart, to keep the UI from updating too fast to read.
const DOWNLOAD_PROGRESS_INTERVAL = 500; // ms
// A var is used for the delay so tests can set a smaller value.
var gSaveUpdateXMLDelay = 2000;
var gUpdateMutexHandle = null;
@ -205,6 +204,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
@ -1145,6 +1146,9 @@ function UpdatePatch(patch) {
case "selected":
this.selected = attr.value == "true";
break;
case "entityID":
this.setProperty("entityID", attr.value);
break;
case "size":
if (0 == parseInt(attr.value)) {
LOG("UpdatePatch:init - 0-sized patch!");
@ -1274,8 +1278,6 @@ function Update(update) {
this.unsupported = false;
this.channel = "default";
this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
this.backgroundInterval = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDINTERVAL,
DOWNLOAD_BACKGROUND_INTERVAL);
// Null <update>, assume this is a message container and do no
// further initialization
@ -1333,10 +1335,6 @@ function Update(update) {
if (!isNaN(attr.value)) {
this.promptWaitTime = parseInt(attr.value);
}
} else if (attr.name == "backgroundInterval") {
if (!isNaN(attr.value)) {
this.backgroundInterval = parseInt(attr.value);
}
} else if (attr.name == "unsupported") {
this.unsupported = attr.value == "true";
} else {
@ -1367,9 +1365,6 @@ function Update(update) {
this.displayVersion = this.appVersion;
}
// Don't allow the background download interval to be greater than 10 minutes.
this.backgroundInterval = Math.min(this.backgroundInterval, 600);
// The Update Name is either the string provided by the <update> element, or
// the string: "<App Name> <Update App Version>"
var name = "";
@ -1475,7 +1470,6 @@ Update.prototype = {
}
var update = updates.createElementNS(URI_UPDATE_NS, "update");
update.setAttribute("appVersion", this.appVersion);
update.setAttribute("backgroundInterval", this.backgroundInterval);
update.setAttribute("buildID", this.buildID);
update.setAttribute("channel", this.channel);
update.setAttribute("displayVersion", this.displayVersion);
@ -2470,7 +2464,7 @@ UpdateService.prototype = {
}
// Set the previous application version prior to downloading the update.
update.previousAppVersion = Services.appinfo.version;
this._downloader = new Downloader(background, this);
this._downloader = getDownloader(background, this);
return this._downloader.downloadUpdate(update);
},
@ -3207,48 +3201,35 @@ Checker.prototype = {
* update mode.
* @param updateService
* The update service that created this downloader.
* @constructor
*/
function Downloader(background, updateService) {
LOG("Creating Downloader");
class CommonDownloader {
constructor(background, updateService) {
this.background = background;
this.updateService = updateService;
}
Downloader.prototype = {
/**
* The nsIUpdatePatch that we are downloading
*/
_patch: null,
this._patch = null;
/**
* The nsIUpdate that we are downloading
*/
_update: null,
/**
* The nsIIncrementalDownload object handling the download
*/
_request: null,
this._update = null;
/**
* Whether or not the update being downloaded is a complete replacement of
* the user's existing installation or a patch representing the difference
* between the new version and the previous version.
*/
isCompleteUpdate: null,
this.isCompleteUpdate = null;
/**
* Cancels the active download.
* An array of download listeners to notify when we receive
* nsIRequestObserver or nsIProgressEventSink method calls.
*/
cancel: function Downloader_cancel(cancelError) {
LOG("Downloader: cancel");
if (cancelError === undefined) {
cancelError = Cr.NS_BINDING_ABORTED;
this._listeners = [];
}
if (this._request && this._request instanceof Ci.nsIRequest) {
this._request.cancel(cancelError);
}
},
/**
* Whether or not a patch has been downloaded and staged for installation.
@ -3261,33 +3242,29 @@ Downloader.prototype = {
return readState == STATE_PENDING || readState == STATE_PENDING_SERVICE ||
readState == STATE_PENDING_ELEVATE ||
readState == STATE_APPLIED || readState == STATE_APPLIED_SERVICE;
},
}
/**
* Verify the downloaded file. We assume that the download is complete at
* this point.
*
* @param patchFile
* nsIFile representing the fully downloaded file
*/
_verifyDownload: function Downloader__verifyDownload() {
LOG("Downloader:_verifyDownload called");
if (!this._request) {
AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
AUSTLMY.DWNLD_ERR_VERIFY_NO_REQUEST);
return false;
}
let destination = this._request.destination;
_verifyDownload(patchFile) {
LOG("CommonDownloader:_verifyDownload called");
// Ensure that the file size matches the expected file size.
if (destination.fileSize != this._patch.size) {
LOG("Downloader:_verifyDownload downloaded size != expected size.");
if (patchFile.fileSize != this._patch.size) {
LOG("CommonDownloader:_verifyDownload downloaded size != expected size.");
AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
AUSTLMY.DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL);
return false;
}
LOG("Downloader:_verifyDownload downloaded size == expected size.");
LOG("CommonDownloader:_verifyDownload downloaded size == expected size.");
return true;
},
}
/**
* Select the patch to use given the current state of updateDir and the given
@ -3298,7 +3275,7 @@ Downloader.prototype = {
* A nsIFile representing the update directory
* @return A nsIUpdatePatch object to download
*/
_selectPatch: function Downloader__selectPatch(update, updateDir) {
_selectPatch(update, updateDir) {
// Given an update to download, we will always try to download the patch
// for a partial update over the patch for a full update.
@ -3328,22 +3305,22 @@ Downloader.prototype = {
// that we do not know about, then remove it and use our default logic.
var useComplete = false;
if (selectedPatch) {
LOG("Downloader:_selectPatch - found existing patch with state: " +
LOG("CommonDownloader:_selectPatch - found existing patch with state: " +
state);
if (state == STATE_DOWNLOADING) {
LOG("Downloader:_selectPatch - resuming download");
LOG("CommonDownloader:_selectPatch - resuming download");
return selectedPatch;
}
if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
state == STATE_PENDING_ELEVATE || state == STATE_APPLIED ||
state == STATE_APPLIED_SERVICE) {
LOG("Downloader:_selectPatch - already downloaded");
LOG("CommonDownloader:_selectPatch - already downloaded");
return null;
}
if (update && selectedPatch.type == "complete") {
// This is a pretty fatal error. Just bail.
LOG("Downloader:_selectPatch - failed to apply complete patch!");
LOG("CommonDownloader:_selectPatch - failed to apply complete patch!");
writeStatusFile(updateDir, STATE_NONE);
writeVersionFile(getUpdatesDir(), null);
return null;
@ -3384,98 +3361,224 @@ Downloader.prototype = {
um.activeUpdate = update;
return selectedPatch;
},
/**
* Whether or not we are currently downloading something.
*/
get isBusy() {
return this._request != null;
},
/**
* Download and stage the given update.
* @param update
* A nsIUpdate object to download a patch for. Cannot be null.
*/
downloadUpdate: function Downloader_downloadUpdate(update) {
LOG("UpdateService:_downloadUpdate");
if (!update) {
AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE);
throw Cr.NS_ERROR_NULL_POINTER;
}
var updateDir = getUpdatesDir();
this._update = update;
// This function may return null, which indicates that there are no patches
// to download.
this._patch = this._selectPatch(update, updateDir);
if (!this._patch) {
LOG("Downloader:downloadUpdate - no patch to download");
AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE_PATCH);
return readStatusFile(updateDir);
}
this.isCompleteUpdate = this._patch.type == "complete";
let patchFile = getUpdatesDir().clone();
patchFile.append(FILE_UPDATE_MAR);
update.QueryInterface(Ci.nsIPropertyBag);
let interval = this.background ? update.getProperty("backgroundInterval")
: DOWNLOAD_FOREGROUND_INTERVAL;
LOG("Downloader:downloadUpdate - url: " + this._patch.URL + ", path: " +
patchFile.path + ", interval: " + interval);
var uri = Services.io.newURI(this._patch.URL);
this._request = Cc["@mozilla.org/network/incremental-download;1"].
createInstance(Ci.nsIIncrementalDownload);
this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
this._request.start(this, null);
writeStatusFile(updateDir, STATE_DOWNLOADING);
this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
this._patch.state = STATE_DOWNLOADING;
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.saveUpdates();
return STATE_DOWNLOADING;
},
/**
* An array of download listeners to notify when we receive
* nsIRequestObserver or nsIProgressEventSink method calls.
*/
_listeners: [],
/**
* Adds a listener to the download process
* @param listener
* A download listener, implementing nsIRequestObserver and
* nsIProgressEventSink
*/
addDownloadListener: function Downloader_addDownloadListener(listener) {
for (var i = 0; i < this._listeners.length; ++i) {
if (this._listeners[i] == listener)
addDownloadListener(listener) {
for (let i = 0; i < this._listeners.length; ++i) {
if (this._listeners[i] == listener) {
return;
}
}
this._listeners.push(listener);
},
}
/**
* Removes a download listener
* @param listener
* The listener to remove.
*/
removeDownloadListener: function Downloader_removeDownloadListener(listener) {
for (var i = 0; i < this._listeners.length; ++i) {
removeDownloadListener(listener) {
for (let i = 0; i < this._listeners.length; ++i) {
if (this._listeners[i] == listener) {
this._listeners.splice(i, 1);
return;
}
}
},
}
}
/**
* Update downloader which uses an nsHttpChannel
*/
class ChannelDownloader extends CommonDownloader {
constructor(background, updateService) {
LOG("Creating ChannelDownloader");
super(background, updateService);
/**
* Background file saver, for writing the downloaded file on another thread
*/
this._bkgFileSaver = null;
/**
* The channel object handling the download
*/
this._channel = null;
/**
* Timestamp of the last time we notified listeners for OnProgress.
* Used to throttle how frequently those notifications can be sent,
* to avoid updating user interfaces faster than they can be read.
*/
this._lastProgressTimeMs = 0;
/**
* If a previous download is being resumed, this is set to the number of
* bytes that had already been downloaded.
*/
this._resumedFrom = 0;
this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIStreamListener,
Ci.nsIChannelEventSink,
Ci.nsIProgressEventSink,
Ci.nsIRequestObserver,
Ci.nsIInterfaceRequestor]);
}
/**
* Verify the downloaded file. We assume that the download is complete at
* this point.
*/
_verifyDownload() {
if (!this._channel) {
AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
AUSTLMY.DWNLD_ERR_VERIFY_NO_REQUEST);
return false;
}
let patchFile = getUpdatesDir().clone();
patchFile.append(FILE_UPDATE_MAR);
return super._verifyDownload(patchFile);
}
/**
* Cancels the active download.
*/
cancel(cancelError) {
LOG("ChannelDownloader: cancel");
if (cancelError === undefined) {
cancelError = Cr.NS_BINDING_ABORTED;
}
if (this._bkgFileSaver) {
this._bkgFileSaver.finish(cancelError);
this._bkgFileSaver.observer = null;
this._bkgFileSaver = null;
}
if (this._channel) {
this._channel.cancel(cancelError);
this._channel = null;
}
}
/**
* Whether or not we are currently downloading something.
*/
get isBusy() {
return this._channel != null;
}
/**
* Download and stage the given update.
* @param update
* A nsIUpdate object to download a patch for. Cannot be null.
*/
downloadUpdate(update) {
LOG("ChannelDownloader:downloadUpdate");
if (!update) {
AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE);
throw Cr.NS_ERROR_NULL_POINTER;
}
let updateDir = getUpdatesDir();
this._update = update;
// This function may return null, which indicates that there are no patches
// to download.
this._patch = this._selectPatch(update, updateDir);
if (!this._patch) {
LOG("ChannelDownloader:downloadUpdate - no patch to download");
AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE_PATCH);
return readStatusFile(updateDir);
}
this.isCompleteUpdate = this._patch.type == "complete";
this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
let patchFile = getUpdatesDir().clone();
patchFile.append(FILE_UPDATE_MAR);
LOG("ChannelDownloader:downloadUpdate - url: " + this._patch.URL +
", path: " + patchFile.path);
let uri = Services.io.newURI(this._patch.URL);
let BackgroundFileSaver = Components.Constructor(
"@mozilla.org/network/background-file-saver;1?mode=streamlistener",
"nsIBackgroundFileSaver");
this._bkgFileSaver = new BackgroundFileSaver();
this._bkgFileSaver.QueryInterface(Ci.nsIStreamListener);
this._channel = NetUtil.newChannel({uri, loadUsingSystemPrincipal: true});
this._channel.notificationCallbacks = this;
this._channel.asyncOpen2(this.QueryInterface(Ci.nsIStreamListener));
if (this._channel instanceof Ci.nsIResumableChannel &&
patchFile.exists()) {
let resumeFrom;
let entityID = this._patch.getProperty("entityID");
if (!entityID) {
LOG("ChannelDownloader:downloadUpdate - failed to resume download, " +
"couldn't get entityID for the selected patch");
} else {
try {
resumeFrom = patchFile.fileSize;
} catch (e) {
LOG("ChannelDownloader:downloadUpdate - failed to resume download, " +
"couldn't open partially downloaded file, exception: " + e);
}
}
if (entityID && resumeFrom !== undefined) {
this._channel.resumeAt(resumeFrom, entityID);
this._bkgFileSaver.enableAppend();
this._resumedFrom = resumeFrom;
LOG("ChannelDownloader:downloadUpdate - resuming previous download " +
"starting after " + resumeFrom + " bytes");
} else {
AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
AUSTLMY.DWNLD_RESUME_FAILURE);
}
}
this._bkgFileSaver.setTarget(patchFile, true);
writeStatusFile(updateDir, STATE_DOWNLOADING);
this._patch.state = STATE_DOWNLOADING;
let um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.saveUpdates();
return STATE_DOWNLOADING;
}
/**
* See nsIChannelEventSink.idl
*/
asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
LOG("ChannelDownloader: redirected from " + oldChannel.URI +
" to " + newChannel.URI);
this._patch.finalURL = newChannel.URI;
callback.onRedirectVerifyCallback(Cr.NS_OK);
this._channel = newChannel;
}
/**
* See nsIProgressEventSink.idl
*/
onStatus(request, context, status, statusText) {
LOG("ChannelDownloader:onStatus - status: " + status +
", statusText: " + statusText);
for (let listener of this._listeners) {
if (listener instanceof Ci.nsIProgressEventSink) {
listener.onStatus(request, context, status, statusText);
}
}
}
/**
* When the async request begins
@ -3484,39 +3587,40 @@ Downloader.prototype = {
* @param context
* Additional data
*/
onStartRequest: function Downloader_onStartRequest(request, context) {
if (request instanceof Ci.nsIIncrementalDownload)
LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
", final URI spec: " + request.finalURI.spec);
// Always set finalURL in onStartRequest since it can change.
this._patch.finalURL = request.finalURI.spec;
onStartRequest(request, context) {
if (!this._channel || !this._bkgFileSaver) {
// Sometimes the channel calls onStartRequest after being canceled.
return;
}
LOG("ChannelDownloader:onStartRequest");
this._bkgFileSaver.onStartRequest(request, context);
if (request instanceof Ci.nsIResumableChannel) {
this._patch.setProperty("entityID", request.entityID);
}
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.saveUpdates();
var listeners = this._listeners.concat();
var listenerCount = listeners.length;
for (var i = 0; i < listenerCount; ++i)
for (var i = 0; i < listenerCount; ++i) {
listeners[i].onStartRequest(request, context);
},
}
}
/**
* When new data has been downloaded
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
* @param progress
* The current number of bytes transferred
* @param maxProgress
* The total number of bytes that must be transferred
* See nsIProgressEventSink.idl
*/
onProgress: function Downloader_onProgress(request, context, progress,
maxProgress) {
LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
onProgress(request, context, progress, maxProgress) {
LOG("ChannelDownloader:onProgress - progress: " + progress +
"/" + maxProgress);
if (progress > this._patch.size) {
LOG("Downloader:onProgress - progress: " + progress +
LOG("ChannelDownloader:onProgress - progress: " + progress +
" is higher than patch size: " + this._patch.size);
AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
AUSTLMY.DWNLD_ERR_PATCH_SIZE_LARGER);
@ -3524,8 +3628,9 @@ Downloader.prototype = {
return;
}
if (maxProgress != this._patch.size) {
LOG("Downloader:onProgress - maxProgress: " + maxProgress +
if ((maxProgress + this._resumedFrom) != this._patch.size) {
LOG("ChannelDownloader:onProgress - maxProgress: " +
(maxProgress + this._resumedFrom) +
" is not equal to expected patch size: " + this._patch.size);
AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
AUSTLMY.DWNLD_ERR_PATCH_SIZE_NOT_EQUAL);
@ -3533,54 +3638,64 @@ Downloader.prototype = {
return;
}
var listeners = this._listeners.concat();
var listenerCount = listeners.length;
for (var i = 0; i < listenerCount; ++i) {
var listener = listeners[i];
if (listener instanceof Ci.nsIProgressEventSink)
listener.onProgress(request, context, progress, maxProgress);
let currentTime = Date.now();
if ((currentTime - this._lastProgressTimeMs) > DOWNLOAD_PROGRESS_INTERVAL) {
this._lastProgressTimeMs = currentTime;
let listeners = this._listeners.concat();
let listenerCount = listeners.length;
for (let i = 0; i < listenerCount; ++i) {
let listener = listeners[i];
if (listener instanceof Ci.nsIProgressEventSink) {
listener.onProgress(request, context, progress + this._resumedFrom,
this._patch.size);
}
}
}
this.updateService._consecutiveSocketErrors = 0;
},
/**
* When we have new status text
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
* @param status
* A status code
* @param statusText
* Human readable version of |status|
*/
onStatus: function Downloader_onStatus(request, context, status, statusText) {
LOG("Downloader:onStatus - status: " + status + ", statusText: " +
statusText);
var listeners = this._listeners.concat();
var listenerCount = listeners.length;
for (var i = 0; i < listenerCount; ++i) {
var listener = listeners[i];
if (listener instanceof Ci.nsIProgressEventSink)
listener.onStatus(request, context, status, statusText);
}
},
/**
* When data transfer ceases
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
* @param status
* Status code containing the reason for the cessation.
* See nsIStreamListener.idl
*/
onStopRequest: function Downloader_onStopRequest(request, context, status) {
if (request instanceof Ci.nsIIncrementalDownload)
LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
", final URI spec: " + request.finalURI.spec + ", status: " + status);
onDataAvailable(request, context, stream, offset, count) {
// this._bkgFileSaver may not exist anymore if we've been canceled.
if (this._bkgFileSaver) {
this._bkgFileSaver.onDataAvailable(request, context, stream, offset, count);
}
}
/**
* See nsIRequestObserver.idl
*/
onStopRequest(request, context, status) {
// this._bkgFileSaver may not exist anymore if we've been canceled.
if (this._bkgFileSaver) {
this._bkgFileSaver.onStopRequest(request, context, status);
}
if (Components.isSuccessCode(status)) {
this._bkgFileSaver.observer = {
onTargetChange() { },
onSaveComplete: (aSaver, aStatus) => {
this._bkgFileSaver.observer = null;
this._finishDownload(request, context, aStatus);
}
};
this._bkgFileSaver.finish(status);
} else {
this._finishDownload(request, context, status);
}
}
/**
* Handler for when the download attempt is completely finished;
* either the file must be ready to use or the download must have
* conclusively failed.
*
* The interface to this function is the same as onStopRequest,
* so see nsIRequestObserver.idl for the details.
*/
_finishDownload(request, context, status) {
// XXX ehsan shouldShowPrompt should always be false here.
// But what happens when there is already a UI showing?
var state = this._patch.state;
@ -3596,7 +3711,7 @@ Downloader.prototype = {
DEFAULT_SOCKET_MAX_ERRORS);
// Prevent the preference from setting a value greater than 20.
maxFail = Math.min(maxFail, 20);
LOG("Downloader:onStopRequest - status: " + status + ", " +
LOG("ChannelDownloader:finishDownload - status: " + status + ", " +
"current fail: " + this.updateService._consecutiveSocketErrors + ", " +
"max fail: " + maxFail + ", " +
"retryTimeout: " + retryTimeout);
@ -3621,7 +3736,7 @@ Downloader.prototype = {
this._update.statusText = gUpdateBundle.GetStringFromName("installPending");
Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
} else {
LOG("Downloader:onStopRequest - download verification failed");
LOG("ChannelDownloader:finishDownload - download verification failed");
state = STATE_DOWNLOAD_FAILED;
status = Cr.NS_ERROR_CORRUPTED_CONTENT;
@ -3641,7 +3756,7 @@ Downloader.prototype = {
// 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");
LOG("ChannelDownloader:finishDownload - offline, register online observer: true");
AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
AUSTLMY.DWNLD_RETRY_OFFLINE);
shouldRegisterOnlineObserver = true;
@ -3656,7 +3771,7 @@ Downloader.prototype = {
status == Cr.NS_ERROR_NET_RESET ||
status == Cr.NS_ERROR_DOCUMENT_NOT_CACHED) &&
this.updateService._consecutiveSocketErrors < maxFail) {
LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
LOG("ChannelDownloader:finishDownload - socket error, shouldRetrySoon: true");
let dwnldCode = AUSTLMY.DWNLD_RETRY_CONNECTION_REFUSED;
if (status == Cr.NS_ERROR_NET_TIMEOUT) {
dwnldCode = AUSTLMY.DWNLD_RETRY_NET_TIMEOUT;
@ -3670,7 +3785,7 @@ Downloader.prototype = {
deleteActiveUpdate = false;
} else if (status != Cr.NS_BINDING_ABORTED &&
status != Cr.NS_ERROR_ABORT) {
LOG("Downloader:onStopRequest - non-verification failure");
LOG("ChannelDownloader:finishDownload - non-verification failure");
let dwnldCode = AUSTLMY.DWNLD_ERR_BINDING_ABORTED;
if (status == Cr.NS_ERROR_ABORT) {
dwnldCode = AUSTLMY.DWNLD_ERR_ABORT;
@ -3691,7 +3806,7 @@ Downloader.prototype = {
deleteActiveUpdate = true;
}
LOG("Downloader:onStopRequest - setting state to: " + state);
LOG("ChannelDownloader:finishDownload - setting state to: " + state);
this._patch.state = state;
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
@ -3715,13 +3830,14 @@ Downloader.prototype = {
}
}
this._request = null;
this._channel = null;
this._bkgFileSaver = null;
if (state == STATE_DOWNLOAD_FAILED) {
var allFailed = true;
// Check if there is a complete update patch that can be downloaded.
if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
LOG("Downloader:onStopRequest - verification of patch failed, " +
LOG("ChannelDownloader:finishDownload - verification of patch failed, " +
"downloading complete update patch");
this._update.isCompleteUpdate = true;
let updateStatus = this.downloadUpdate(this._update);
@ -3741,14 +3857,14 @@ Downloader.prototype = {
let maxAttempts = Math.min(getPref("getIntPref", PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2), 10);
if (downloadAttempts > maxAttempts) {
LOG("Downloader:onStopRequest - notifying observers of error. " +
LOG("ChannelDownloader:finishDownload - notifying observers of error. " +
"topic: update-error, status: download-attempts-exceeded, " +
"downloadAttempts: " + downloadAttempts + " " +
"maxAttempts: " + maxAttempts);
Services.obs.notifyObservers(this._update, "update-error", "download-attempts-exceeded");
} else {
this._update.selectedPatch.selected = false;
LOG("Downloader:onStopRequest - notifying observers of error. " +
LOG("ChannelDownloader:finishDownload - notifying observers of error. " +
"topic: update-error, status: download-attempt-failed");
Services.obs.notifyObservers(this._update, "update-error", "download-attempt-failed");
}
@ -3758,6 +3874,7 @@ Downloader.prototype = {
// downloading) and if at any point this was a foreground download
// notify the user about the error. If the update was a background
// update there is no notification since the user won't be expecting it.
this._update.QueryInterface(Ci.nsIPropertyBag);
if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) &&
this._update.getProperty("foregroundDownload") == "true") {
let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
@ -3775,7 +3892,7 @@ Downloader.prototype = {
if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
state == STATE_PENDING_ELEVATE) {
if (getCanStageUpdates()) {
LOG("Downloader:onStopRequest - attempting to stage update: " +
LOG("ChannelDownloader:finishDownload - attempting to stage update: " +
this._update.name);
// Initiate the update in the background
@ -3786,7 +3903,7 @@ Downloader.prototype = {
} catch (e) {
// Fail gracefully in case the application does not support the update
// processor service.
LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
LOG("ChannelDownloader:finishDownload - failed to stage update. Exception: " +
e);
if (this.background) {
shouldShowPrompt = true;
@ -3807,10 +3924,10 @@ Downloader.prototype = {
}
if (shouldRegisterOnlineObserver) {
LOG("Downloader:onStopRequest - Registering online observer");
LOG("ChannelDownloader:finishDownload - Registering online observer");
this.updateService._registerOnlineObserver();
} else if (shouldRetrySoon) {
LOG("Downloader:onStopRequest - Retrying soon");
LOG("ChannelDownloader:finishDownload - Retrying soon");
this.updateService._consecutiveSocketErrors++;
if (this.updateService._retryTimer) {
this.updateService._retryTimer.cancel();
@ -3823,26 +3940,31 @@ Downloader.prototype = {
// Prevent leaking the update object (bug 454964)
this._update = null;
}
},
}
/**
* See nsIInterfaceRequestor.idl
*/
getInterface: function Downloader_getInterface(iid) {
getInterface(iid) {
// The network request may require proxy authentication, so provide the
// default nsIAuthPrompt if requested.
if (iid.equals(Ci.nsIAuthPrompt)) {
var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
createInstance();
return prompt.QueryInterface(iid);
} else if (iid.equals(Ci.nsIProgressEventSink)) {
return this.QueryInterface(iid);
}
throw Cr.NS_NOINTERFACE;
},
}
}
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
Ci.nsIProgressEventSink,
Ci.nsIInterfaceRequestor])
};
/**
* Construct the correct type of Downloader object.
*/
function getDownloader(background, updateService) {
return new ChannelDownloader(background, updateService);
}
/**
* UpdatePrompt