Unify update download/checking error generation and reporting, more work on update history view, fix various polish bugs in downloading page, etc.

This commit is contained in:
ben%bengoodger.com 2005-06-28 03:27:42 +00:00
Родитель e865635624
Коммит adbdd8367b
13 изменённых файлов: 783 добавлений и 332 удалений

Просмотреть файл

@ -448,7 +448,7 @@ function checkForUpdates()
{
var prompter = Components.classes["@mozilla.org/updates/update-prompt;1"]
.createInstance(Components.interfaces.nsIUpdatePrompt);
prompter.checkForUpdates();
prompter.checkForUpdates(window);
}
function buildHelpMenu()

Просмотреть файл

@ -138,14 +138,14 @@ var gAdvancedPane = {
{
var prompter = Components.classes["@mozilla.org/updates/update-prompt;1"]
.createInstance(Components.interfaces.nsIUpdatePrompt);
prompter.checkForUpdates();
prompter.checkForUpdates(window);
},
showUpdates: function ()
{
var prompter = Components.classes["@mozilla.org/updates/update-prompt;1"]
.createInstance(Components.interfaces.nsIUpdatePrompt);
prompter.showUpdateHistory();
prompter.showUpdateHistory(window);
},
showLanguages: function ()

Просмотреть файл

@ -29,7 +29,7 @@
<!ENTITY checkNow.label "Check Now...">
<!ENTITY appCheckNow.accesskey "C">
<!ENTITY extensionsCheckNow.accesskey "h">
<!ENTITY showUpdates.label "Show Updates">
<!ENTITY showUpdates.label "Show Update History">
<!ENTITY showUpdates.accesskey "U">
<!ENTITY securityTab.label "Security">

Просмотреть файл

@ -38,8 +38,9 @@
<!ENTITY downloading.title "Downloading Update">
<!ENTITY downloading.intro "Downloading the update...">
<!ENTITY connecting.label "Connecting to the update server...">
<!ENTITY showCompletedUpdates.label "Show old updates in this list">
<!ENTITY showCompletedUpdates.accesskey "o">
<!ENTITY verificationFailedText.label "&brandShortName; was unable to verify the integrity of the
incremental update it downloaded, so it is now downloading
the complete update package.">
<!ENTITY details.link "Details">
<!ENTITY pause.label "Pause">
@ -66,4 +67,9 @@
<!ENTITY finishedBackground.instruction2 "Click Later to continue without restarting. The update will be installed
the next time you start &brandShortName;.">
<!ENTITY installed.title "Update Installed">
<!ENTITY installed.intro "The update was successfully installed.">
<!ENTITY update.details.label "Details">
<!ENTITY update.installedOn.label "Installed on:">
<!ENTITY update.status.label "Status:">

Просмотреть файл

@ -23,19 +23,22 @@ app.update.url.manual=http://www.mozilla.org/update
app.update.url.override=
closeButtonLabel=Close
statusFormatKBKB=#1 of #2 KB
statusFormatKBMB=#1 KB of #2 MB
statusFormatMBMB=#1 of #2 MB
statusFormatUnknownKB=#1 KB
statusFormatUnknownMB=#1 MB
progressFormatKBKB=#1 of #2 KB
progressFormatKBMB=#1 KB of #2 MB
progressFormatMBMB=#1 of #2 MB
progressFormatUnknownKB=#1 KB
progressFormatUnknownMB=#1 MB
pausedStatus=%S downloaded so far
remain=remain
unknownFilesize=unknown file size
rateFormatKBSec=#1 KB/sec
rateFormatMBSec=#1 MB/sec
statusFormat=#1 at #2; #3 #4
longTimeFormat=#1:#2:#3
shortTimeFormat=#2:#3
rateFormat= at #1
progressFormat=#1#2
timeFormat=; #1 #2
statusFormat=#1#2
downloadingPrefix=Downloading %S...
pausedName=Paused downloading %S
@ -63,4 +66,4 @@ checker_error-2152398849=Failed (Unknown Reason)
checker_error-2152398868=AUS: No data was received (Please try again)
checker_error-2152398919=AUS: Data transfer was interrupted (Please try again)
checker_error-2152398867=AUS: Port not allowed (Contact your Administrator)
checker_error-verification_failed=The integrity of the update could not be verified (Contact your Administrator)

Просмотреть файл

@ -38,44 +38,34 @@
const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var gUpdateHistory = {
_children: null,
_view: null,
/**
* Initialize the User Interface
*/
onLoad: function() {
this._children = document.getElementById("historyChildren");
this._view = document.getElementById("historyItems");
var um =
Components.classes["@mozilla.org/updates/update-manager;1"].
getService(Components.interfaces.nsIUpdateManager);
var uc = um.updateCount;
if (uc) {
while (this._children.hasChildNodes())
this._children.removeChild(this._children.firstChild);
while (this._view.hasChildNodes())
this._view.removeChild(this._view.firstChild);
var bundle = document.getElementById("updateBundle");
for (var i = 0; i < uc; ++i) {
var update = um.getUpdateAt(i);
var treeitem = document.createElementNS(NS_XUL, "treeitem");
var treerow = document.createElementNS(NS_XUL, "treerow");
var nameCell = document.createElementNS(NS_XUL, "treecell");
nameCell.setAttribute("label", update.name);
var dateCell = document.createElementNS(NS_XUL, "treecell");
var formattedDate = this._formatDate(update.installDate);
dateCell.setAttribute("label", formattedDate);
var typeCell = document.createElementNS(NS_XUL, "treecell");
typeCell.setAttribute("label", update.type);
dump("*** name = " + update.name + ", date = " + update.installDate + ", type = " + update.type + ", state = " + update.state + "\n");
var stateCell = document.createElementNS(NS_XUL, "treecell");
stateCell.setAttribute("label", update.selectedPatch.state);
treerow.appendChild(nameCell);
treerow.appendChild(dateCell);
treerow.appendChild(typeCell);
treerow.appendChild(stateCell);
treeitem.appendChild(treerow);
this._children.appendChild(treeitem);
var element = document.createElementNS(NS_XUL, "update");
this._view.appendChild(element);
element.name = update.name;
element.type = bundle.getString("updateType_" + update.type);
element.installDate = this._formatDate(update.installDate);
element.detailsURL = update.detailsURL;
element.status = update.statusText;
}
}
@ -104,20 +94,6 @@ var gUpdateHistory = {
date.getHours(),
date.getMinutes(),
date.getSeconds());
},
/**
* Called when a tree row is selected
*/
onTreeSelect: function() {
},
/**
* Open the Details page for an item
*/
showDetails: function() {
},
}
};

Просмотреть файл

@ -61,34 +61,10 @@
src="chrome://mozapps/locale/update/updates.properties"/>
<label>&history.intro;</label>
<separator/>
<tree id="historyItems" rows="10" hidecolumnpicker="true" seltype="single"
onselect="gUpdateHistory.onTreeSelect()">
<treecols>
<treecol id="updateNameCol" flex="1" primary="true" label="&name.header;"/>
<splitter class="tree-splitter"/>
<treecol id="updateDate" label="&date.header;"/>
<splitter class="tree-splitter"/>
<treecol id="updateType" label="&type.header;"/>
<splitter class="tree-splitter"/>
<treecol id="updateState" label="&state.header;"/>
</treecols>
<treechildren id="historyChildren">
<treeitem>
<treerow>
<treecell>&noupdates.label;</treecell>
<treecell/>
<treecell/>
<treecell/>
</treerow>
</treeitem>
</treechildren>
</tree>
<separator/>
<hbox>
<button id="details"
label="&detailsButton.label;" accesskey="&detailsButton.accesskey;"
oncommand="gUpdateHistory.showDetails();"/>
</hbox>
<separator class="thin"/>
<richlistbox id="historyItems" flex="1">
<label>&noupdates.label;</label>
</richlistbox>
<separator class="thin"/>
</dialog>

Просмотреть файл

@ -126,6 +126,7 @@ wizard[currentpageid="updatesfound"] .wizard-page-box {
#downloadStatus {
margin-top: 6px;
height: 3em !important;
}
#downloadThrobber {
margin: 5px 3px 0px 5px;
@ -135,6 +136,11 @@ wizard[currentpageid="updatesfound"] .wizard-page-box {
list-style-image: url("chrome://global/skin/throbber/Throbber-small.gif");
}
#verificationFailedIcon {
list-style-image: url("chrome://global/skin/icons/notfound.png");
margin: 5px 3px 0px 5px;
}
/**
* Error Page
*/
@ -146,3 +152,40 @@ wizard[currentpageid="updatesfound"] .wizard-page-box {
font-weight: bold;
}
/**
* Update History Window
*/
update {
-moz-binding: url("chrome://mozapps/content/update/updates.xml#update");
display: -moz-box;
-moz-box-orient: vertical;
border-bottom: 1px dotted #C0C0C0;
}
.update-name {
font-weight: bold;
}
.update-label-column {
-moz-box-align: end;
}
.update-details-link {
margin: 0px;
}
.update-type {
font-weight: bold;
color: #990000;
}
#historyItems {
-moz-appearance: listbox;
height: 200px;
margin: 1px 5px 4px 5px;
}
#historyItems > scrollbox {
margin-bottom: 1px;
}

Просмотреть файл

@ -97,18 +97,36 @@ function getPref(func, preference, defaultValue) {
return defaultValue;
}
/**
* A set of shared data and control functions for the wizard as a whole.
*/
var gUpdates = {
update : null,
strings : null,
brandName : null,
wiz : null,
sourceEvent : SRCEVT_FOREGROUND,
/**
* The nsIUpdate object being used by this window (either for downloading,
* notification or both).
*/
update: null,
/**
* The updates.properties <stringbundle> element.
*/
strings: null,
/**
* The Application brandShortName (e.g. "Firefox")
*/
brandName: null,
/**
* The <wizard> element
*/
wiz: null,
/**
* A hash of |pageid| attribute to page object. Can be used to dispatch
* function calls to the appropriate page.
*/
_pages : { },
_pages: { },
/**
* Called when the user presses the "Finish" button on the wizard, dispatches
@ -144,6 +162,12 @@ var gUpdates = {
*/
sourceEvent: SRCEVT_FOREGROUND,
/**
* The global error message - the reason the update failed. This is human
* readable text, used to initialize the error page.
*/
errorMessage: "",
/**
* Called when the wizard UI is loaded.
*/
@ -178,7 +202,8 @@ var gUpdates = {
},
/**
*
* Initialize Logging preferences, formatted like so:
* app.update.log.<moduleName> = <true|false>
*/
_initLoggingPrefs: function() {
try {
@ -203,7 +228,9 @@ var gUpdates = {
*
* U'Prompt Method: Arg0: Update State: Src Event:
* showUpdateAvailable nsIUpdate obj -- background
* showUpdateComplete nsIUpdate obj pending background
* showUpdateDownloaded nsIUpdate obj pending background
* showUpdateInstalled nsIUpdate obj succeeded either
* showUpdateError nsIUpdate obj failed either
* checkForUpdates null -- foreground
*/
get startPage() {
@ -214,10 +241,18 @@ var gUpdates = {
// user that the background checking found an update that requires
// their permission to install, and it's ready for download.
this.update = arg0;
this.sourceEvent = SRCEVT_BACKGROUND;
if (this.update.selectedPatch &&
this.update.selectedPatch.state == STATE_PENDING)
return document.getElementById("finishedBackground");
var p = this.update.selectedPatch;
if (p) {
switch (p.state) {
case STATE_PENDING:
this.sourceEvent = SRCEVT_BACKGROUND;
return document.getElementById("finishedBackground");
case STATE_SUCCEEDED:
return document.getElementById("installed");
case STATE_FAILED:
return document.getElementById("errors");
}
}
return document.getElementById("updatesfound");
}
}
@ -233,57 +268,6 @@ var gUpdates = {
return document.getElementById("checking");
},
/**
* Show the errors page.
* @param reason
* A text message explaining what the error was
*/
advanceToErrorPage: function(reason) {
var errorReason = document.getElementById("errorReason");
errorReason.value = reason;
var errorLink = document.getElementById("errorLink");
var pref = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch2);
var manualURL = pref.getComplexValue(PREF_UPDATE_MANUAL_URL,
Components.interfaces.nsIPrefLocalizedString);
errorLink.href = manualURL.data;
var errorLinkLabel = document.getElementById("errorLinkLabel");
errorLinkLabel.value = manualURL.data;
var pageTitle = this.strings.getString("errorsPageHeader");
var errorPage = document.getElementById("errors");
errorPage.setAttribute("label", pageTitle);
gUpdates.wiz.currentPage = document.getElementById("errors");
gUpdates.wiz.setAttribute("label", pageTitle);
},
/**
* Show a network error message.
* @param code
* An error code to look up in the properties file, either
* a Necko error code or a HTTP response code
* @param defaultCode
* The error code to fall back to if the specified code has no error
* text associated with it in the properties file.
*/
advanceToErrorPageWithCode: function(code, defaultCode) {
var sbs =
Components.classes["@mozilla.org/intl/stringbundle;1"].
getService(Components.interfaces.nsIStringBundleService);
var updateBundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
var reason = updateBundle.GetStringFromName("checker_error-" + defaultCode);
try {
reason = updateBundle.GetStringFromName("checker_error-" + code);
LOG("General", "Transfer Error: " + reason + ", code: " + code);
}
catch (e) {
// Use the default reason
LOG("General", "Transfer Error: " + reason + ", code: " + defaultCode);
}
this.advanceToErrorPage(reason);
},
/**
* Registers a timer to nag the user about something relating to update
* @param timerID
@ -347,6 +331,10 @@ var gUpdates = {
},
}
/**
* The "Checking for Updates" page. Provides feedback on the update checking
* process.
*/
var gCheckingPage = {
/**
* The nsIUpdateChecker that is currently checking for updates. We hold onto
@ -377,6 +365,10 @@ var gCheckingPage = {
}
},
/**
* An object implementing nsIUpdateCheckListener that is notified as the
* update check commences.
*/
updateListener: {
/**
* See nsIUpdateCheckListener
@ -407,21 +399,12 @@ var gCheckingPage = {
/**
* See nsIUpdateCheckListener
*/
onError: function(request) {
onError: function(request, update) {
LOG("UI:CheckingPage", "UpdateCheckListener: error");
try {
var status = request.status;
}
catch (e) {
var req = request.channel.QueryInterface(Components.interfaces.nsIRequest);
status = req.status;
}
gUpdates.update = update;
// If we can't find an error string specific to this status code,
// just use the 200 message from above, which means everything
// "looks" fine but there was probably an XML error or a bogus file.
gUpdates.advanceToErrorPageWithCode(status, 200);
gUpdates.wiz.currentPage = document.getElementById("errors");
},
/**
@ -436,7 +419,13 @@ var gCheckingPage = {
}
};
/**
* The "No Updates Are Available" page
*/
var gNoUpdatesPage = {
/**
* Initialize
*/
onPageShow: function() {
gUpdates.wiz.getButton("back").disabled = true;
gUpdates.wiz.getButton("cancel").disabled = true;
@ -444,6 +433,11 @@ var gNoUpdatesPage = {
}
};
/**
* The "Updates Are Available" page. Provides the user information about the
* available update, extensions it might make incompatible, and a means to
* continue downloading and installing it.
*/
var gUpdatesAvailablePage = {
/**
* An array of installed addons incompatible with this update.
@ -539,8 +533,20 @@ var gUpdatesAvailablePage = {
}
};
/**
* The page which shows the user a license associated with an update. The
* user must agree to the terms of the license before continuing to install
* the update.
*/
var gLicensePage = {
/**
* The <license> element
*/
_licenseContent: null,
/**
* Initialize
*/
onPageShow: function() {
this._licenseContent = document.getElementById("licenseContent");
@ -559,11 +565,20 @@ var gLicensePage = {
gUpdates.wiz._wizardButtons.removeAttribute("type");
},
/**
* When the license document has loaded
*/
onLicenseLoad: function() {
// Now that the license text is available, the user is in a position to
// agree to it, so enable the Agree button.
gUpdates.wiz.getButton("next").disabled = false;
},
/**
* When the user cancels the wizard
*/
onWizardCancel: function() {
// If the license was downloading, stop it.
this._licenseContent.stopDownloading();
// The user said "Do Not Agree", so stop all update checks for this session
@ -587,17 +602,24 @@ function DownloadStatusFormatter() {
var us = gUpdates.strings;
this._statusFormat = us.getString("statusFormat");
this._statusFormatKBMB = us.getString("statusFormatKBMB");
this._statusFormatKBKB = us.getString("statusFormatKBKB");
this._statusFormatMBMB = us.getString("statusFormatMBMB");
this._statusFormatUnknownMB = us.getString("statusFormatUnknownMB");
this._statusFormatUnknownKB = us.getString("statusFormatUnknownKB");
this._progressFormat = us.getString("progressFormat");
this._progressFormatKBMB = us.getString("progressFormatKBMB");
this._progressFormatKBKB = us.getString("progressFormatKBKB");
this._progressFormatMBMB = us.getString("progressFormatMBMB");
this._progressFormatUnknownMB = us.getString("progressFormatUnknownMB");
this._progressFormatUnknownKB = us.getString("progressFormatUnknownKB");
this._rateFormat = us.getString("rateFormat");
this._rateFormatKBSec = us.getString("rateFormatKBSec");
this._rateFormatMBSec = us.getString("rateFormatMBSec");
this._remain = us.getString("remain");
this._unknownFilesize = us.getString("unknownFilesize");
this._timeFormat = us.getString("timeFormat");
this._longTimeFormat = us.getString("longTimeFormat");
this._shortTimeFormat = us.getString("shortTimeFormat");
this._remain = us.getString("remain");
this._unknownFilesize = us.getString("unknownFilesize");
}
DownloadStatusFormatter.prototype = {
/**
@ -620,6 +642,11 @@ DownloadStatusFormatter.prototype = {
*/
_rateFormatted: "",
/**
* Transfer rate, formatted into text container
*/
_rateFormattedContainer: "",
/**
* Number of Kilobytes downloaded so far in the form:
* 376KB of 9.3MB
@ -643,6 +670,9 @@ DownloadStatusFormatter.prototype = {
var total = parseInt(finalSize/1024 + 0.5);
this.progress = this._formatKBytes(parseInt(currSize/1024 + 0.5), total);
var progress = this._replaceInsert(this._progressFormat, 1, this.progress);
var rateFormatted = "";
// 2) Determine the Transfer Rate
var oldElapsed = this._elapsed;
this._elapsed = now - this._startTime;
@ -656,26 +686,38 @@ DownloadStatusFormatter.prototype = {
if (this._rate > 100)
this._rate = Math.round(this._rate);
if (this._rate == 0)
this._rateFormatted = "??.?";
else {
if (this._rate) {
var format = isKB ? this._rateFormatKBSec : this._rateFormatMBSec;
this._rateFormatted = this._replaceInsert(format, 1, this._rate);
this._rateFormattedContainer = this._replaceInsert(" " + this._rateFormat, 1, this._rateFormatted);
}
}
progress = this._replaceInsert(progress, 2, this._rateFormattedContainer);
// 3) Determine the Time Remaining
var remainingTime = this._unknownFileSize;
var remainingTime = "";
if (this._rate && (finalSize > 0)) {
remainingTime = Math.floor(((finalSize - currSize) / 1024) / this._rate);
remainingTime = this._formatSeconds(remainingTime);
remainingTime = this._replaceInsert(this._timeFormat, 1, remainingTime)
remainingTime = this._replaceInsert(remainingTime, 2, this._remain);
}
//
// [statusFormat:
// [progressFormat:
// [[progressFormatKBKB|
// progressFormatKBMB|
// progressFormatMBMB|
// progressFormatUnknownKB|
// progressFormatUnknownMB
// ][rateFormat]]
// ][timeFormat]
// ]
var status = this._statusFormat;
status = this._replaceInsert(status, 1, this.progress);
status = this._replaceInsert(status, 2, this._rateFormatted);
status = this._replaceInsert(status, 3, remainingTime);
status = this._replaceInsert(status, 4, this._remain);
status = this._replaceInsert(status, 1, progress);
status = this._replaceInsert(status, 2, remainingTime);
return status;
},
@ -709,33 +751,33 @@ DownloadStatusFormatter.prototype = {
* x, < 1MB y >= 1MB x KB of y MB
* x, >= 1MB y >= 1MB x of y MB
*/
_formatKBytes: function(currentKB, totalKB) {
_formatKBytes: function(currentKB, totalKB) {
var progressHasMB = parseInt(currentKB / 1024) > 0;
var totalHasMB = parseInt(totalKB / 1024) > 0;
var format = "";
if (!progressHasMB && !totalHasMB) {
if (!totalKB) {
format = this._statusFormatUnknownKB;
format = this._progressFormatUnknownKB;
format = this._replaceInsert(format, 1, currentKB);
} else {
format = this._statusFormatKBKB;
format = this._progressFormatKBKB;
format = this._replaceInsert(format, 1, currentKB);
format = this._replaceInsert(format, 2, totalKB);
}
}
else if (progressHasMB && totalHasMB) {
format = this._statusFormatMBMB;
format = this._progressFormatMBMB;
format = this._replaceInsert(format, 1, (currentKB / 1024).toFixed(1));
format = this._replaceInsert(format, 2, (totalKB / 1024).toFixed(1));
}
else if (totalHasMB && !progressHasMB) {
format = this._statusFormatKBMB;
format = this._progressFormatKBMB;
format = this._replaceInsert(format, 1, currentKB);
format = this._replaceInsert(format, 2, (totalKB / 1024).toFixed(1));
}
else if (progressHasMB && !totalHasMB) {
format = this._statusFormatUnknownMB;
format = this._progressFormatUnknownMB;
format = this._replaceInsert(format, 1, (currentKB / 1024).toFixed(1));
}
return format;
@ -772,6 +814,10 @@ DownloadStatusFormatter.prototype = {
}
};
/**
* The "Update is Downloading" page - provides feedback for the download
* process plus a pause/resume UI
*/
var gDownloadingPage = {
/**
* DOM Elements
@ -786,9 +832,14 @@ var gDownloadingPage = {
* An instance of the status formatter object
*/
_statusFormatter : null,
get statusFormatter() {
if (!this._statusFormatter)
this._statusFormatter = new DownloadStatusFormatter();
return this._statusFormatter;
},
/**
*
* Initialize
*/
onPageShow: function() {
gUpdates.wiz._wizardButtons.removeAttribute("type");
@ -843,7 +894,7 @@ var gDownloadingPage = {
},
/**
*
* Updates the text status message
*/
_setStatus: function(status) {
while (this._downloadStatus.hasChildNodes())
@ -851,9 +902,24 @@ var gDownloadingPage = {
this._downloadStatus.appendChild(document.createTextNode(status));
},
/**
* Whether or not we are currently paused
*/
_paused : false,
/**
* The status before we were paused
*/
_oldStatus : null,
/**
* The mode of the <progressmeter> before we were paused
*/
_oldMode : null,
/**
* The progress through the download before we were paused
*/
_oldProgress : 0,
/**
@ -887,7 +953,7 @@ var gDownloadingPage = {
},
/**
*
* When the user clicks the Pause/Resume button
*/
onPause: function() {
var updates =
@ -900,7 +966,7 @@ var gDownloadingPage = {
patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
patch.setProperty("status",
gUpdates.strings.getFormattedString("pausedStatus",
[this._statusFormatter.progress]));
[this.statusFormatter.progress]));
updates.pauseDownload();
}
this._paused = !this._paused;
@ -910,7 +976,7 @@ var gDownloadingPage = {
},
/**
*
* When the user closes the Wizard UI
*/
onWizardCancel: function() {
// Remove ourself as a download listener so that we don't continue to be
@ -950,19 +1016,29 @@ var gDownloadingPage = {
},
/**
*
* When the data transfer begins
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
*/
onStartRequest: function(request, context) {
request.QueryInterface(nsIIncrementalDownload);
LOG("UI:DownloadingPage", "onStartRequest: " + request.URI.spec);
this._statusFormatter = new DownloadStatusFormatter();
this._downloadThrobber.setAttribute("state", "loading");
},
/**
*
* 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
*/
onProgress: function(request, context, progress, maxProgress) {
request.QueryInterface(nsIIncrementalDownload);
@ -973,7 +1049,7 @@ var gDownloadingPage = {
p.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
p.setProperty("progress", Math.round(100 * (progress/maxProgress)));
p.setProperty("status",
this._statusFormatter.formatStatus(progress, maxProgress));
this.statusFormatter.formatStatus(progress, maxProgress));
this._downloadProgress.mode = "normal";
this._downloadProgress.value = parseInt(p.getProperty("progress"));
@ -984,16 +1060,31 @@ var gDownloadingPage = {
},
/**
*
* 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(request, context, status, statusText) {
request.QueryInterface(nsIIncrementalDownload);
LOG("UI:DownloadingPage", "onStatus: " + request.URI.spec + " status = " +
status + ", text = " + statusText);
this._setStatus(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.
*/
onStopRequest: function(request, context, status) {
request.QueryInterface(nsIIncrementalDownload);
@ -1002,14 +1093,16 @@ var gDownloadingPage = {
this._downloadThrobber.removeAttribute("state");
var u = gUpdates.update;
const NS_BINDING_ABORTED = 0x804b0002;
switch (status) {
case Components.results.NS_ERROR_UNEXPECTED:
LOG("UI:DownloadingPage", "STATE = " + gUpdates.update.selectedPatch.state);
if (gUpdates.update.selectedPatch.state == STATE_FAILED)
this.showVerificationError();
if (u.selectedPatch.state == STATE_FAILED && u.isCompleteUpdate) {
// Verification error of complete patch, informational text is held in
// the update object.
gUpdates.wiz.currentPage = document.getElementById("errors");
}
else {
LOG("UI:DownloadingPage", "TYPE = " + gUpdates.update.selectedPatch.type);
// Verification failed for a partial patch, complete patch is now
// downloading so return early and do NOT remove the download listener!
@ -1017,6 +1110,11 @@ var gDownloadingPage = {
// show old progress for the new download of the "complete" patch.
this._downloadProgress.mode = "undetermined";
this._pauseButton.disabled = true;
var verificationFailed = document.getElementById("verificationFailed");
verificationFailed.hidden = false;
this._statusFormatter = null;
return;
}
break;
@ -1032,7 +1130,7 @@ var gDownloadingPage = {
default:
LOG("UI:DownloadingPage", "onStopRequest: Transfer failed");
// Some kind of transfer error, die.
gUpdates.advanceToErrorPageWithCode(status, 2152398849);
gUpdates.wiz.currentPage = document.getElementById("errors");
break;
}
@ -1042,16 +1140,6 @@ var gDownloadingPage = {
updates.removeDownloadListener(this);
},
/**
* Advance the wizard to the "Verification Error" page
*/
showVerificationError: function() {
var verificationError = gUpdates.strings.getFormattedString(
"verificationError", [gUpdates.brandName]);
var downloadingPage = document.getElementById("downloading");
gUpdates.advanceToErrorPage(verificationError);
},
/**
* See nsISupports.idl
*/
@ -1064,14 +1152,35 @@ var gDownloadingPage = {
}
};
/**
* The "There was an error during the update" page.
*/
var gErrorsPage = {
/**
* Initialize
*/
onPageShow: function() {
gUpdates.wiz.getButton("back").disabled = true;
gUpdates.wiz.getButton("cancel").disabled = true;
gUpdates.wiz.getButton("finish").focus();
}
var errorReason = document.getElementById("errorReason");
errorReason.value = gUpdates.update.statusText;
var errorLink = document.getElementById("errorLink");
var pref = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch2);
var manualURL = pref.getComplexValue(PREF_UPDATE_MANUAL_URL,
Components.interfaces.nsIPrefLocalizedString);
errorLink.href = manualURL.data;
var errorLinkLabel = document.getElementById("errorLinkLabel");
errorLinkLabel.value = manualURL.data;
},
};
/**
* The "Update has been downloaded" page. Shows information about what
* was downloaded.
*/
var gFinishedPage = {
/**
* Called to initialize the Wizard Page.
@ -1177,6 +1286,18 @@ var gFinishedPage = {
},
};
/**
* The "Update was Installed Successfully" page.
*/
var gInstalledPage = {
/**
* Initialize
*/
onPageShow: function() {
},
};
/**
* Called as the application shuts down due to being quit from the File->Quit
* menu item.

Просмотреть файл

@ -321,5 +321,59 @@
</method>
</implementation>
</binding>
<binding id="update" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content>
<xul:hbox>
<xul:label class="update-name" xbl:inherits="value=name" flex="1" crop="right"/>
<xul:link xbl:inherits="href=detailsURL" class="update-details-link">
<xul:label>&update.details.label;</xul:label>
</xul:link>
</xul:hbox>
<xul:label class="update-type" xbl:inherits="value=type"/>
<xul:grid>
<xul:columns>
<xul:column class="update-label-column"/>
<xul:column flex="1"/>
</xul:columns>
<xul:rows>
<xul:row>
<xul:label class="update-installedOn-label">&update.installedOn.label;</xul:label>
<xul:label class="update-installedOn-value" xbl:inherits="value=installDate" flex="1" crop="right"/>
</xul:row>
<xul:row>
<xul:label class="update-status-label">&update.status.label;</xul:label>
<xul:description class="update-status-value" flex="1"/>
</xul:row>
</xul:rows>
</xul:grid>
</content>
<implementation>
<property name="name"
onget="return this.getAttribute('name');"
onset="this.setAttribute('name', val); return val;"/>
<property name="detailsURL"
onget="return this.getAttribute('detailsURL');"
onset="this.setAttribute('detailsURL', val); return val;"/>
<property name="installDate"
onget="return this.getAttribute('installDate');"
onset="this.setAttribute('installDate', val); return val;"/>
<property name="type"
onget="return this.getAttribute('type');"
onset="this.setAttribute('type', val); return val;"/>
<property name="status"
onget="return this.getAttribute('status');">
<setter><![CDATA[
this.setAttribute("status", val);
var field = document.getAnonymousElementByAttribute(this, "class", "update-status-value");
while (field.hasChildNodes())
field.removeChild(field.firstChild);
field.appendChild(document.createTextNode(val));
return val;
]]></setter>
</property>
onset="this.setAttribute('status', val); return val;"/>
</implementation>
</binding>
</bindings>

Просмотреть файл

@ -147,7 +147,12 @@
<image id="downloadThrobber"/>
<description id="downloadStatus" flex="1">&connecting.label;</description>
<button id="pauseButton" oncommand="gDownloadingPage.onPause();"
label="&pause.label;" accesskey="&pause.accesskey;"/>
disabled="true" label="&pause.label;" accesskey="&pause.accesskey;"/>
</hbox>
<separator/>
<hbox id="verificationFailed" align="start" hidden="true">
<image id="verificationFailedIcon"/>
<description flex="1">&verificationFailedText.label;</description>
</hbox>
</wizardpage>
@ -190,4 +195,10 @@
<label>&finishedBackground.instruction2;</label>
</wizardpage>
<wizardpage id="installed" pageid="installed"
label="&installed.title;" object="gInstalledPage"
onpageshow="gInstalledPage.onPageShow();">
<label>&installed.intro;</label>
</wizardpage>
</wizard>

Просмотреть файл

@ -39,6 +39,7 @@
interface nsIDOMDocument;
interface nsIDOMElement;
interface nsIDOMWindow;
interface nsIRequest;
interface nsIRequestObserver;
interface nsISimpleEnumerator;
@ -141,7 +142,12 @@ interface nsIUpdate : nsISupports
attribute long long installDate;
/**
*
* A message associated with this update, if any.
*/
attribute AString statusText;
/**
* The currently selected patch for this update.
*/
readonly attribute nsIUpdatePatch selectedPatch;
@ -198,8 +204,12 @@ interface nsIUpdateCheckListener : nsISupports
* An error occurred while loading the remote update service file.
* @param request
* The nsIXMLHttpRequest handling the update check.
* @param update
* A nsIUpdate object that contains details about the
* error in its |statusText| property.
*/
void onError(in nsIXMLHttpRequest request);
void onError(in nsIXMLHttpRequest request,
in nsIUpdate update);
};
[scriptable, uuid(22b00276-ec23-4034-a764-395da539b4be)]
@ -208,30 +218,55 @@ interface nsIUpdatePrompt : nsISupports
/**
* Shows a user interface that checks for and then displays the available
* updates.
* @param parent
* A parent window to anchor this window to. Can be null.
*/
void checkForUpdates();
void checkForUpdates(in nsIDOMWindow parent);
/**
* Show a message advising that an update is available for download and
* install.
* @param parent
* A parent window to anchor this window to. Can be null.
* @param update
* The update to be downloaded and installed
*/
void showUpdateAvailable(in nsIUpdate update);
void showUpdateAvailable(in nsIDOMWindow parent,
in nsIUpdate update);
/**
* Show a message advising that an update has now been downloaded and that
* the user should restart their software should be restarted so that the
* update can be installed.
* @param parent
* A parent window to anchor this window to. Can be null.
* @param update
* The update that was downloaded
*/
void showUpdateComplete(in nsIUpdate update);
void showUpdateDownloaded(in nsIDOMWindow parent,
in nsIUpdate update);
/**
* Shows a message detailing the update which was installed.
* @param update
* The nsIUpdate object which was just installed
*/
void showUpdateInstalled(in nsIUpdate update);
/**
* Shows an error message UI telling the user about some kind of update
* failure, e.g. failure to apply patch.
* @param update
* The nsIUpdate object which we could not install
*/
void showUpdateError(in nsIUpdate update);
/**
* Shows a list of all updates installed to date.
* @param parent
* A parent window to anchor this window to. Can be null.
*/
void showUpdateHistory();
void showUpdateHistory(in nsIDOMWindow parent);
};
[scriptable, uuid(877ace25-8bc5-452a-8586-9c1cf2871994)]

Просмотреть файл

@ -80,6 +80,7 @@ const MODE_TRUNCATE = 0x20;
const PERMS_FILE = 0644;
const PERMS_DIRECTORY = 0755;
const STATE_NONE = null;
const STATE_DOWNLOADING = "downloading";
const STATE_PENDING = "pending";
const STATE_APPLYING = "applying";
@ -240,6 +241,90 @@ function closeSafeOutputStream(fos) {
fos.close();
}
/**
* Returns human readable status text from the updates.propreties bundle
* based on an error code
* @param code
* The error code to look up human readable status text for
* @param defaultCode
* The default code to look up should human readable status text
* not exist for |code|
* @returns A human readable status text string
*/
function getStatusTextFromCode(code, defaultCode) {
var sbs =
Components.classes["@mozilla.org/intl/stringbundle;1"].
getService(Components.interfaces.nsIStringBundleService);
var updateBundle = sbs.createBundle(URI_UPDATES_PROPERTIES);
var reason = updateBundle.GetStringFromName("checker_error-" + defaultCode);
try {
reason = updateBundle.GetStringFromName("checker_error-" + code);
LOG("General", "Transfer Error: " + reason + ", code: " + code);
}
catch (e) {
// Use the default reason
LOG("General", "Transfer Error: " + reason + ", code: " + defaultCode);
}
return reason;
}
/**
* Get the Active Updates directory
* @returns The active updates directory, as a nsIFile object
*/
function getUpdatesDir() {
// Right now, we only support downloading one patch at a time, so we always
// use the same target directory.
var fileLocator =
Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties);
var appDir = fileLocator.get(KEY_APPDIR, Components.interfaces.nsIFile);
appDir.append(DIR_UPDATES);
appDir.append("0");
if (!appDir.exists())
appDir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
return appDir;
}
/**
* Reads the update state from the update.status file in the specified
* directory.
* @param dir
* The dir to look for an update.status file in
* @returns The status value of the update.
*/
function readStatusFile(dir) {
var statusFile = dir.clone();
statusFile.append(FILE_UPDATE_STATUS);
LOG("General", "Reading Status File: " + statusFile.path);
return readStringFromFile(statusFile);
}
/**
* Removes the Updates Directory
*/
function cleanUpUpdatesDir() {
var updateDir = getUpdatesDir();
var e = updateDir.directoryEntries;
while (e.hasMoreElements()) {
var f = e.getNext().QueryInterface(Components.interfaces.nsIFile);
try {
f.remove(false);
}
catch (e) {
LOG("General", "Failed to remove file: " + f.path);
}
}
try {
updateDir.remove(false);
} catch (e) {
LOG("General", "Failed to remove update directory: " + updateDir.path +
" - This is almost always bad. Exception = " + e);
throw e;
}
}
/**
* An enumeration of items in a JS array.
* @constructor
@ -321,7 +406,10 @@ function readStringFromFile(file) {
}
/**
*
* An object which can prompt the user with information about updates, request
* action, etc. Embedding clients can override this component with one that
* invokes a native front end.
* @constructor
*/
function UpdatePrompt() {
}
@ -329,31 +417,59 @@ UpdatePrompt.prototype = {
/**
* See nsIUpdateService.idl
*/
checkForUpdates: function() {
this._showUI(URI_UPDATE_PROMPT_DIALOG, "Update:Wizard", null);
checkForUpdates: function(parent) {
this._showUI(parent, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard", null);
},
/**
* See nsIUpdateService.idl
*/
showUpdateAvailable: function(update) {
this._showUI(URI_UPDATE_PROMPT_DIALOG, "Update:Wizard", update);
showUpdateAvailable: function(parent, update) {
this._showUI(parent, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard", update);
},
/**
* See nsIUpdateService.idl
*/
showUpdateComplete: function(update) {
this._showUI(URI_UPDATE_PROMPT_DIALOG, "Update:Wizard", update);
showUpdateDownloaded: function(parent, update) {
this._showUI(parent, URI_UPDATE_PROMPT_DIALOG, null, "Update:Wizard", update);
},
/**
* See nsIUpdateService.idl
*/
showUpdateInstalled: function(update) {
this._showUI(null, URI_UPDATE_PROMPT_DIALOG, "modal", "Update:Wizard", update);
},
/**
* See nsIUpdateService.idl
*/
showUpdateError: function(update) {
this._showUI(null, URI_UPDATE_PROMPT_DIALOG, "modal", "Update:Wizard", update);
},
/**
* See nsIUpdateService.idl
*/
showUpdateHistory: function(parent) {
this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal", "Update:History", null);
},
/**
* Show the Update Checking UI
* @param parent
* A parent window, can be null
* @param uri
* The URI string of the dialog to show
* @param name
* The Window Name of the dialog to show, in case it is already open
* and can merely be focused
* @param update
* An update to pass to the UI in the window arguments.
* Can be null.
* Can be null
*/
_showUI: function(uri, name, update) {
_showUI: function(parent, uri, features, name, update) {
var ary = null;
if (update) {
ary = Components.classes["@mozilla.org/supports-array;1"]
@ -367,20 +483,15 @@ UpdatePrompt.prototype = {
if (win)
win.focus();
else {
var openFeatures = "chrome,centerscreen,dialog";
if (features)
openFeatures += "," + features;
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
ww.openWindow(null, uri,
"", "chrome,centerscreen,dialog,titlebar,dependent", ary);
ww.openWindow(parent, uri, "", openFeatures, ary);
}
},
/**
* See nsIUpdateService.idl
*/
showUpdateHistory: function() {
this._showUI(URI_UPDATE_HISTORY_DIALOG, "Update:History", null);
},
/**
* See nsISupports.idl
*/
@ -414,6 +525,43 @@ function UpdateService() {
// Observe xpcom-shutdown to unhook pref branch observers above to avoid
// shutdown leaks.
gOS.addObserver(this, "xpcom-shutdown", false);
/*
// Detect installation failures and notify
var status = readStatusFile(getUpdatesDir());
if (status != null) {
// null status means the update.status file is not present, because either:
// 1) no update was performed, and so there's no UI to show
// 2) an update was attempted but failed during checking, transfer or
// verification, and was cleaned up at that point, and UI notifying of
// that error was shown at that stage.
var um =
Components.classes["@mozilla.org/updates/update-manager;1"].
getService(Components.interfaces.nsIUpdateManager);
var prompter =
Components.classes["@mozilla.org/updates/update-prompt;1"].
createInstance(Components.interfaces.nsIUpdatePrompt);
var update = um.activeUpdate;
if (!update)
update = new Update(null);
if (status == STATE_SUCCEEDED) {
update.statusText = "Install Succeeded";
//prompter.showUpdateInstalled(update);
}
else {
// Something went wrong with the patch application process.
update.statusText = "goats";
// XXXben todo: synthesize the best message
//prompter.showUpdateError(update);
}*/
// Move the update from the Active Update list into the Past Updates list.
um.activeUpdate = null;
um.saveUpdates();
// Now trash the updates directory, since we're done with it
cleanUpUpdatesDir();
}
this._initLoggingPrefs();
}
@ -450,7 +598,8 @@ UpdateService.prototype = {
},
/**
*
* Initialize Logging preferences, formatted like so:
* app.update.log.<moduleName> = <true|false>
*/
_initLoggingPrefs: function() {
try {
@ -514,8 +663,8 @@ UpdateService.prototype = {
/**
* See nsIUpdateService.idl
*/
onError: function(request) {
LOG("Checker", "Error during background update");
onError: function(request, update) {
LOG("Checker", "Error during background update: " + update.statusText);
},
}
this.backgroundChecker.checkForUpdates(listener, false);
@ -1014,6 +1163,13 @@ UpdatePatch.prototype = {
*/
function Update(update) {
this._patches = [];
this.installDate = 0;
this.isCompleteUpdate = false;
// Null <update>, assume this is a message container and do no
// further initialization
if (!update)
return;
for (var i = 0; i < update.childNodes.length; ++i) {
var patchElement = update.childNodes.item(i);
@ -1025,13 +1181,21 @@ function Update(update) {
this._patches.push(new UpdatePatch(patchElement));
}
this.type = update.getAttribute("type");
this.version = update.getAttribute("version");
this.extensionVersion = update.getAttribute("extensionVersion");
this.detailsURL = update.getAttribute("detailsURL");
this.licenseURL = update.getAttribute("licenseURL");
this.installDate = update.hasAttribute("installDate") ? parseInt(update.getAttribute("installDate")) : 0;
this.isCompleteUpdate = false;
for (var i = 0; i < update.attributes.length; ++i) {
var attr = update.attributes[i];
switch (attr.name) {
case "installDate":
if (attr.value)
this.installDate = parseInt(attr.value);
break;
case "isCompleteUpdate":
this.isCompleteUpdate = attr.value == "true";
break;
default:
this[attr.name] = attr.value;
break;
};
}
// The Update Name is either the string provided by the <update> element, or
// the string: "<App Name> <Update App Version>"
@ -1047,7 +1211,6 @@ function Update(update) {
name = updateBundle.formatStringFromName("updateName",
[appName, this.version], 2);
}
this.name = name;
}
Update.prototype = {
@ -1085,19 +1248,70 @@ Update.prototype = {
update.setAttribute("detailsURL", this.detailsURL);
update.setAttribute("licenseURL", this.licenseURL);
update.setAttribute("installDate", this.installDate);
update.setAttribute("statusText", this.statusText);
update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
updates.documentElement.appendChild(update);
for (var p in this._properties) {
if (this._properties[p].present)
patch.setAttribute(p, this._properties[p].data);
}
for (var i = 0; i < this.patchCount; ++i)
update.appendChild(this.getPatchAt(i).serialize(updates));
return update;
},
/**
* A hash of custom properties
*/
_properties: { },
/**
* See nsIWritablePropertyBag.idl
*/
setProperty: function(name, value) {
this._properties[name] = { data: value, present: true };
},
/**
* See nsIWritablePropertyBag.idl
*/
deleteProperty: function(name) {
if (name in this._properties)
this._properties[name].present = false;
else
throw Components.results.NS_ERROR_FAILURE;
},
/**
* See nsIPropertyBag.idl
*/
get enumerator() {
var properties = [];
for (var p in this._properties)
properties.push(this._properties[p].data);
return new ArrayEnumerator(properties);
},
/**
* See nsIPropertyBag.idl
*/
getProperty: function(name) {
if (name in this._properties &&
this._properties[name].present)
return this._properties[name].data;
throw Components.results.NS_ERROR_FAILURE;
},
/**
* See nsISupports.idl
*/
QueryInterface: function(iid) {
if (!iid.equals(Components.interfaces.nsIUpdate) &&
!iid.equals(Components.interfaces.nsIPropertyBag) &&
!iid.equals(Components.interfaces.nsIWritablePropertyBag) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
@ -1106,6 +1320,8 @@ Update.prototype = {
/**
* Checker
* Checks for new Updates
* @constructor
*/
function Checker() {
}
@ -1116,15 +1332,10 @@ Checker.prototype = {
_request : null,
/**
*
* The nsIUpdateCheckListener callback
*/
_callback : null,
/**
*
*/
observer : null,
/**
* The URL of the update service XML file to connect to that contains details
* about available updates.
@ -1190,7 +1401,9 @@ Checker.prototype = {
},
/**
*
* When progress associated with the XMLHttpRequest is received.
* @param event
* The nsIDOMLSProgressEvent for the load.
*/
onProgress: function(event) {
LOG("Checker", "onProgress: " + event.position + "/" + event.totalSize);
@ -1198,7 +1411,7 @@ Checker.prototype = {
},
/**
*
* Returns an array of nsIUpdate objects discovered by the update check.
*/
get _updates() {
var updatesElement = this._request.responseXML.documentElement;
@ -1227,15 +1440,13 @@ Checker.prototype = {
},
/**
*
* The XMLHttpRequest succeeded and the document was loaded.
* @param event
* The nsIDOMLSEvent for the load
*/
onLoad: function(event) {
LOG("Checker", "onLoad: request completed downloading document");
// Notify the front end that we're complete
if (this.observer)
this.observer.onLoad(event.target);
// Analyze the resulting DOM and determine the set of updates to install
try {
var updates = this._updates;
@ -1248,17 +1459,36 @@ Checker.prototype = {
catch (e) {
LOG("Checker", "There was a problem with the update service URL specified, " +
"either the XML file was malformed or it does not exist at the location " +
"specified");
this._callback.onError(event.target);
"specified. Exception: " + e);
var update = new Update(null);
update.statusText = getStatusTextFromCode(404, 404);
this._callback.onError(event.target, update);
}
},
/**
*
* There was an error of some kind during the XMLHttpRequest
* @param event
* The nsIDOMLSEvent for the load
*/
onError: function(event) {
LOG("Checker", "onError: error during load");
this._callback.onError(event.target);
var request = event.target;
try {
var status = request.status;
}
catch (e) {
var req = request.channel.QueryInterface(Components.interfaces.nsIRequest);
status = req.status;
}
// If we can't find an error string specific to this status code,
// just use the 200 message from above, which means everything
// "looks" fine but there was probably an XML error or a bogus file.
var update = new Update(null);
update.statusText = getStatusTextFromCode(status, 200);
this._callback.onError(request, update);
},
/**
@ -1350,23 +1580,6 @@ Downloader.prototype = {
}
},
/**
* Retrieves the active Updates directory, as a nsIFile object.
*/
get _updatesDir() {
// Right now, we only support downloading one patch at a time, so we always
// use the same target directory.
var fileLocator =
Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties);
var appDir = fileLocator.get(KEY_APPDIR, Components.interfaces.nsIFile);
appDir.append(DIR_UPDATES);
appDir.append("0");
if (!appDir.exists())
appDir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
return appDir;
},
/**
* Writes the current update operation/state to a file in the patch
* directory, indicating to the patching system that operations need
@ -1383,21 +1596,11 @@ Downloader.prototype = {
writeStringToFile(statusFile, state);
},
/**
* Reads the current update state...
*/
_readStatusFile: function(dir) {
var statusFile = dir.clone();
statusFile.append(FILE_UPDATE_STATUS);
LOG("Downloader", "Reading Status File: " + statusFile.path);
return readStringFromFile(statusFile);
},
/**
* Whether or not a patch has been downloaded and staged for installation.
*/
get patchIsStaged() {
return this._readStatusFile(this._updatesDir) == STATE_PENDING;
return readStatusFile(getUpdatesDir()) == STATE_PENDING;
},
/**
@ -1417,7 +1620,7 @@ Downloader.prototype = {
createInstance(nsICryptoHash);
var hashFunction = nsICryptoHash[this._patch.hashFunction.toUpperCase()];
if (hashFunction == undefined)
return false;
throw Components.results.NS_ERROR_UNEXPECTED;
hash.init(hashFunction);
hash.updateFromStream(fileStream, -1);
// NOTE: For now, we assume that the format of _patch.hashValue is hex
@ -1468,7 +1671,7 @@ Downloader.prototype = {
// to select ourselves.
var selectedPatch = update.selectedPatch;
var state = this._readStatusFile(updateDir)
var state = readStatusFile(updateDir)
// If this is a patch that we know about, then select it. If it is a patch
// that we do not know about, then remove it and use our default logic.
@ -1484,6 +1687,7 @@ Downloader.prototype = {
return null;
case STATE_FAILED:
case STATE_APPLYING:
case STATE_NONE:
// Something went wrong when we tried to apply the previous patch.
// Try the complete patch next time.
if (update && selectedPatch.type == "partial") {
@ -1509,31 +1713,8 @@ Downloader.prototype = {
selectedPatch = getPatchOfType("complete");
}
// The patch is no longer known to us, so we should assume that it is
// no longer valid. Remove all from the updateDir.
// XXX We may want to preserve an update.log file from a failed update.
// We expect this operation may fail since some files
// (e.g., updater.exe) may still be in use. That's ok.
var e = updateDir.directoryEntries;
while (e.hasMoreElements()) {
var f = e.getNext().QueryInterface(Components.interfaces.nsIFile);
try {
f.remove(false);
}
catch (e) {
LOG("Downloader", "Failed to remove file: " + f.path);
}
}
try {
updateDir.remove(false);
} catch (e) {
LOG("Downloader", "Failed to remove update directory: " + updateDir.path +
" - This is almost always bad. Exception = " + e);
throw e;
}
// Restore the updateDir since we may have deleted it.
updateDir = this._updatesDir;
updateDir = getUpdatesDir();
selectedPatch.selected = true;
update.isCompleteUpdate = useComplete;
@ -1563,7 +1744,7 @@ Downloader.prototype = {
if (!update)
throw Components.results.NS_ERROR_NULL_POINTER;
var updateDir = this._updatesDir;
var updateDir = getUpdatesDir();
this._update = update;
@ -1572,7 +1753,7 @@ Downloader.prototype = {
this._patch = this._selectPatch(update, updateDir);
if (!this._patch) {
LOG("Downloader", "no patch to download");
return this._readStatusFile(updateDir);
return readStatusFile(updateDir);
}
this.isCompleteUpdate = this._patch.type == "complete";
@ -1682,6 +1863,8 @@ Downloader.prototype = {
var state;
var shouldShowPrompt = false;
var deleteActiveUpdate = false;
const NS_BINDING_ABORTED = 0x804b0002;
if (Components.isSuccessCode(status)) {
if (this._verifyDownload()) {
state = STATE_PENDING;
@ -1691,21 +1874,64 @@ Downloader.prototype = {
// that UI will notify.
if (this.background)
shouldShowPrompt = true;
// Tell the updater.exe we're ready to apply.
this._writeStatusFile(getUpdatesDir(), state);
this._update.installDate = (new Date()).getTime();
this._update.statusText = "Install Pending";
} else {
LOG("Downloader", "onStopRequest: download verification failed");
state = STATE_FAILED;
var sbs =
Components.classes["@mozilla.org/intl/stringbundle;1"].
getService(Components.interfaces.nsIStringBundleService);
var updateStrings = sbs.createBundle(URI_UPDATES_PROPERTIES);
var brandStrings = sbs.createBundle(URI_BRAND_PROPERTIES);
var brandShortName = brandStrings.GetStringFromName("brandShortName");
this._update.statusText = updateStrings.
formatStringFromName("verificationError", [brandShortName], 1);
// TODO: use more informative error code here
status = Components.results.NS_ERROR_UNEXPECTED;
var message = getStatusTextFromCode("verification_failed",
"verification_failed");
this._update.statusText = message;
if (this._update.isCompleteUpdate)
deleteActiveUpdate = true;
// Destroy the updates directory, since we're done with it.
cleanUpUpdatesDir();
}
this._writeStatusFile(this._updatesDir, state);
this._patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
this._patch.state = state;
this._update.installDate = (new Date()).getTime();
var um = Components.classes["@mozilla.org/updates/update-manager;1"]
.getService(Components.interfaces.nsIUpdateManager);
um.activeUpdate = null;
um.saveUpdates();
}
else if (status != NS_BINDING_ABORTED) {
LOG("Downloader", "onStopRequest: Non-verification failure");
// Some sort of other failure, log this in the |statusText| property
state = STATE_FAILED;
// XXXben - if |request| (The Incremental Download) provided a means
// for accessing the http channel we could do more here.
const NS_BINDING_FAILED = 2152398849;
this._update.statusText = getStatusTextFromCode(status,
NS_BINDING_FAILED);
// Destroy the updates directory, since we're done with it.
cleanUpUpdatesDir();
deleteActiveUpdate = true;
}
this._patch.state = state;
var um =
Components.classes["@mozilla.org/updates/update-manager;1"].
getService(Components.interfaces.nsIUpdateManager);
if (deleteActiveUpdate) {
this._update.installDate = (new Date()).getTime();
um.activeUpdate = null;
}
um.saveUpdates();
var listenerCount = this._listeners.length;
for (var i = 0; i < listenerCount; ++i)
@ -1732,7 +1958,7 @@ Downloader.prototype = {
var prompter =
Components.classes["@mozilla.org/updates/update-prompt;1"].
createInstance(Components.interfaces.nsIUpdatePrompt);
prompter.showUpdateComplete(this._update);
prompter.showUpdateDownloaded(this._update);
}
},