mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 00:32:11 +00:00
Bug 1348087 Part 1 - Stop using nsIIncrementalDownload for application update. r=rstrong
--HG-- extra : source : 34638bc920b79fe3f60555d81fc9ac13991b9250
This commit is contained in:
parent
cdc677c157
commit
3262f458f0
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user