Many update improvements: making background update work properly. User nagging after user opts to do something later. Rework timer manager so it's less retarded. etc.

This commit is contained in:
ben%bengoodger.com 2005-06-22 00:59:50 +00:00
Родитель c63fc078e3
Коммит 54f6e93d4a
6 изменённых файлов: 455 добавлений и 163 удалений

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

@ -47,7 +47,7 @@ static const nsXREAppData kAppData = {
sizeof(nsXREAppData),
nsnull,
"Mozilla",
"Firefox",
"Firefox Debug",
NS_STRINGIFY(APP_VERSION),
NS_STRINGIFY(BUILD_ID),
"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",

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

@ -42,6 +42,8 @@
# SYNTAX HINTS: dashes are delimiters. Use underscores instead.
# The first character after a period must be alphabetic.
pref("app.update.logEnabled", true);
// pref("startup.homepage_override_url","chrome://browser-region/locale/region.properties");
pref("general.startup.browser", true);
@ -80,15 +82,27 @@ pref("app.update.autoInstallMode", 0);
// XXX these prefs and others like them are distribution specific and should move
// into chrome://browser
// Default service URL for testing.
pref("app.update.url", "chrome://mozapps/locale/update/updates.properties");
// URL user can browse to manually if for some reason all update installation
// attempts fail.
pref("app.update.url.manual", "chrome://mozapps/locale/update/updates.properties");
// User-settable update preference that overrides app.update.url for testing
// purposes.
pref("app.update.url.override", "chrome://mozapps/locale/update/updates.properties");
pref("app.update.updatesAvailable", false);
// Check for updates to Firefox every day
pref("app.update.interval", 86400000);
// Interval: Time between checks for a new version (in seconds)
// default=1 day
pref("app.update.interval", 86400);
// Interval: Time before prompting the user to download a new version that
// is available (in seconds) default=1 day
pref("app.update.nagTimer.download", 86400);
// Interval: Time before prompting the user to restart to install the latest
// download (in seconds) default=30 minutes
pref("app.update.nagTimer.restart", 1800);
// Interval: When all registered timers should be checked (in milliseconds)
// default=5 seconds
pref("app.update.timer", 5000);
// UTC offset when last App update was performed.
pref("app.update.lastUpdateDate", 0);
// Symmetric (can be overridden by individual extensions) update preferences.
// e.g.

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

@ -6,6 +6,7 @@ introType_major=A new version of %S is available:
verificationError=%S could not confirm the integrity of the update package.
errorsPageHeader=Update Failed
IAgreeLabel=I Agree
IDoNotAgreeLabel=I Disagree
license404Error=The license file could not be found. Please contact the distributor.
downloadingLicense=Downloading license text...
statusSucceededFormat=Installed on: %S

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

@ -40,7 +40,9 @@ const nsIIncrementalDownload = Components.interfaces.nsIIncrementalDownload;
const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_UPDATE_MANUAL_URL = "app.update.url.manual";
const PREF_UPDATE_MANUAL_URL = "app.update.url.manual";
const PREF_UPDATE_NAGTIMER_DL = "app.update.nagTimer.download";
const PREF_UPDATE_NAGTIMER_RESTART = "app.update.nagTimer.restart";
const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
@ -53,6 +55,8 @@ const STATE_FAILED = "failed";
const SRCEVT_FOREGROUND = 1;
const SRCEVT_BACKGROUND = 2;
var gPref = null;
/**
* Logs a string to the error console.
* @param string
@ -62,6 +66,28 @@ function LOG(string) {
dump("*** " + string + "\n");
}
/**
* Gets a preference value, handling the case where there is no default.
* @param func
* The name of the preference function to call, on nsIPrefBranch
* @param preference
* The name of the preference
* @param defaultValue
* The default value to return in the event the preference has
* no setting
* @returns The value of the preference, or undefined if there was no
* user or default value.
*/
function getPref(func, preference, defaultValue) {
try {
return gPref[func](preference);
}
catch (e) {
LOG("FAIL = " + e);
}
return defaultValue;
}
var gUpdates = {
update : null,
strings : null,
@ -111,6 +137,9 @@ var gUpdates = {
* Called when the wizard UI is loaded.
*/
onLoad: function() {
gPref = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch2);
this.strings = document.getElementById("updateStrings");
var brandStrings = document.getElementById("brandStrings");
this.brandName = brandStrings.getString("brandShortName");
@ -121,9 +150,17 @@ var gUpdates = {
if (page.localName == "wizardpage")
this._pages[page.pageid] = eval(page.getAttribute("object"));
}
var de = document.documentElement;
// Cache the standard button labels in case we need to restore them
this.buttonLabel_back = de.getButton("back").label;
this.buttonLabel_next = de.getButton("next").label;
this.buttonLabel_finish = de.getButton("finish").label;
this.buttonLabel_cancel = de.getButton("cancel").label;
// Advance to the Start page.
document.documentElement.currentPage = this.startPage;
de.currentPage = this.startPage;
},
/**
@ -145,7 +182,8 @@ var gUpdates = {
// their permission to install, and it's ready for download.
this.update = arg0;
this.sourceEvent = SRCEVT_BACKGROUND;
if (this.update.selectedPatch.state == STATE_PENDING)
if (this.update.selectedPatch &&
this.update.selectedPatch.state == STATE_PENDING)
return document.getElementById("finishedBackground");
return document.getElementById("updatesfound");
}
@ -185,7 +223,69 @@ var gUpdates = {
errorPage.setAttribute("label", pageTitle);
document.documentElement.currentPage = document.getElementById("errors");
document.documentElement.setAttribute("label", pageTitle);
}
},
/**
* Registers a timer to nag the user about something relating to update
* @param timerID
* The ID of the timer to register, used for persistence
* @param timerInterval
* The interval of the timer
* @param methodName
* The method to call on the Update Prompter when the timer fires
*/
registerNagTimer: function(timerID, timerInterval, methodName) {
// Remind the user to restart their browser in a little bit.
var tm =
Components.classes["@mozilla.org/updates/timer-manager;1"].
getService(Components.interfaces.nsIUpdateTimerManager);
/**
* An object implementing nsITimerCallback that uses the Update Prompt
* component to notify the user about some event relating to app update
* that they should take action on.
* @param update
* The nsIUpdate object in question
* @param methodName
* The name of the method on the Update Prompter that should be
* called
* @constructor
*/
function Callback(update, methodName) {
this._update = update;
this._methodName = methodName;
this._prompter =
Components.classes["@mozilla.org/updates/update-prompt;1"].
createInstance(Components.interfaces.nsIUpdatePrompt);
}
Callback.prototype = {
/**
* The Update we should nag about downloading
*/
_update: null,
/**
* The Update prompter we can use to notify the user
*/
_prompter: null,
/**
* The method on the update prompt that should be called
*/
_methodName: "",
/**
* Called when the timer fires. Notifies the user about whichever event
* they need to be nagged about (e.g. update available, please restart,
* etc).
*/
notify: function(timerCallback) {
this._prompter[methodName](this._update);
}
}
tm.registerTimer(timerID, (new Callback(gUpdates.update, methodName)),
timerInterval);
},
}
var gCheckingPage = {
@ -202,10 +302,10 @@ var gCheckingPage = {
var wiz = document.documentElement;
wiz.getButton("next").disabled = true;
var aus =
Components.classes["@mozilla.org/updates/update-service;1"].
getService(Components.interfaces.nsIApplicationUpdateService);
this._checker = aus.checkForUpdates(this.updateListener);
this._checker =
Components.classes["@mozilla.org/updates/update-checker;1"].
createInstance(Components.interfaces.nsIUpdateChecker);
this._checker.checkForUpdates(this.updateListener);
},
/**
@ -213,8 +313,10 @@ var gCheckingPage = {
* Manager control, so stop checking for updates.
*/
onWizardCancel: function() {
if (this._checker)
this._checker.stopChecking();
if (this._checker) {
const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
this._checker.stopChecking(nsIUpdateChecker.CURRENT_CHECK);
}
},
updateListener: {
@ -271,8 +373,14 @@ var gNoUpdatesPage = {
};
var gUpdatesAvailablePage = {
/**
* An array of installed addons incompatible with this update.
*/
_incompatibleItems: null,
/**
* Initialize.
*/
onPageShow: function() {
var updateName = gUpdates.strings.getFormattedString("updateName",
[gUpdates.brandName, gUpdates.update.version]);
@ -307,15 +415,38 @@ var gUpdatesAvailablePage = {
downloadNow.focus();
},
/**
* User said they wanted to install now, so advance to the License or
* Downloading page, whichever is appropriate.
*/
onInstallNow: function() {
var nextPageID = gUpdates.update.licenseURL ? "license" : "downloading";
document.documentElement.currentPage = document.getElementById(nextPageID);
},
/**
* User said that they would install later. Register a timer to remind them
* after a day or so.
*/
onInstallLater: function() {
var interval = getPref("getIntPref", PREF_UPDATE_NAGTIMER_DL, 86400000);
gUpdates.registerNagTimer("download-nag-timer", interval,
"showUpdateAvailable");
// The user said "Later", so stop all update checks for this session
// so that we don't bother them again.
const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
var aus =
Components.classes["@mozilla.org/updates/update-service;1"].
getService(Components.interfaces.nsIApplicationUpdateService);
aus.backgroundChecker.stopChecking(nsIUpdateChecker.CURRENT_SESSION);
close();
},
/**
* Show a list of extensions made incompatible by this update.
*/
showIncompatibleItems: function() {
openDialog("chrome://mozapps/content/update/incompatible.xul", "",
"dialog,centerscreen,modal,resizable,titlebar", this._incompatibleItems);
@ -327,11 +458,15 @@ var gLicensePage = {
onPageShow: function() {
this._licenseContent = document.getElementById("licenseContent");
var nextButton = document.documentElement.getButton("next");
var de = document.documentElement;
var nextButton = de.getButton("next");
nextButton.disabled = true;
nextButton.label = gUpdates.strings.getString("IAgreeLabel");
document.documentElement.getButton("back").disabled = true;
document.documentElement.getButton("next").focus();
de.getButton("back").disabled = true;
de.getButton("next").focus();
var cancelButton = de.getButton("cancel");
cancelButton.label = gUpdates.strings.getString("IDoNotAgreeLabel");
this._licenseContent.addEventListener("load", this.onLicenseLoad, false);
this._licenseContent.url = gUpdates.update.licenseURL;
@ -345,6 +480,14 @@ var gLicensePage = {
onWizardCancel: function() {
this._licenseContent.stopDownloading();
// The user said "Do Not Agree", so stop all update checks for this session
// so that we don't bother them again.
const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
var aus =
Components.classes["@mozilla.org/updates/update-service;1"].
getService(Components.interfaces.nsIApplicationUpdateService);
aus.backgroundChecker.stopChecking(nsIUpdateChecker.CURRENT_SESSION);
}
};
@ -598,11 +741,14 @@ var gDownloadingPage = {
updates.addDownloadListener(this);
}
document.documentElement.getButton("back").disabled = true;
document.documentElement.getButton("next").disabled = true;
var cancelButton = document.documentElement.getButton("cancel");
var de = document.documentElement;
de.getButton("back").disabled = true;
var cancelButton = de.getButton("cancel");
cancelButton.label = gUpdates.strings.getString("closeButtonLabel");
cancelButton.focus();
var nextButton = de.getButton("next");
nextButton.disabled = true;
nextButton.label = gUpdates.buttonLabel_next;
},
/**
@ -626,13 +772,14 @@ var gDownloadingPage = {
*/
_togglePausedState: function(paused) {
var u = gUpdates.update;
var p = u.selectedPatch.QueryInterface(Components.interfaces.nsIPropertyBag);
if (paused) {
this._oldStatus = this._downloadStatus.textContent;
this._oldMode = this._downloadProgress.mode;
this._oldProgress = parseInt(this._downloadProgress.progress);
this._downloadName.value = gUpdates.strings.getFormattedString(
"pausedName", [u.name]);
this._setStatus(u.selectedPatch.status);
this._setStatus(p.getProperty("status"));
this._downloadProgress.mode = "normal";
this._pauseButton.label = gUpdates.strings.getString("pauseButtonResume");
@ -640,8 +787,9 @@ var gDownloadingPage = {
else {
this._downloadName.value = gUpdates.strings.getFormattedString(
"downloadingPrefix", [u.name]);
this._setStatus(this._oldStatus || u.selectedPatch.status);
this._downloadProgress.value = this._oldProgress || u.selectedPatch.progress;
this._setStatus(this._oldStatus || p.getProperty("status"));
this._downloadProgress.value =
this._oldProgress || parseInt(p.getProperty("progress"));
this._downloadProgress.mode = this._oldMode || "normal";
this._pauseButton.label = gUpdates.strings.getString("pauseButtonPause");
}
@ -657,9 +805,11 @@ var gDownloadingPage = {
if (this._paused)
updates.downloadUpdate(gUpdates.update, false);
else {
gUpdates.update.selectedPatch.status =
var patch = gUpdates.update.selectedPatch;
patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
patch.setProperty("status",
gUpdates.strings.getFormattedString("pausedStatus",
[this._statusFormatter.progress]);
[this._statusFormatter.progress]));
updates.pauseDownload();
}
this._paused = !this._paused;
@ -728,15 +878,18 @@ var gDownloadingPage = {
request.QueryInterface(nsIIncrementalDownload);
// LOG("gDownloadingPage.onProgress: " + request.URI.spec + ", " + progress + "/" + maxProgress);
gUpdates.update.selectedPatch.status =
this._statusFormatter.formatStatus(progress, maxProgress);
var p = gUpdates.update.selectedPatch;
p.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
p.setProperty("progress", Math.round(100 * (progress/maxProgress)));
p.setProperty("status",
this._statusFormatter.formatStatus(progress, maxProgress));
this._downloadProgress.mode = "normal";
this._downloadProgress.value = gUpdates.update.selectedPatch.progress;
this._downloadProgress.value = parseInt(p.getProperty("progress"));
this._pauseButton.disabled = false;
var name = gUpdates.strings.getFormattedString("downloadingPrefix", [gUpdates.update.name]);
this._downloadName.value = name;
this._setStatus(gUpdates.update.selectedPatch.status);
this._setStatus(p.getProperty("status"));
},
/**
@ -909,6 +1062,11 @@ var gFinishedPage = {
[gUpdates.brandName]);
ps.alert(window, gUpdates.strings.getString("restartLaterTitle"),
message);
var interval = getPref("getIntPref", PREF_UPDATE_NAGTIMER_RESTART,
18000000);
gUpdates.registerNagTimer("restart-nag-timer", interval,
"showUpdateComplete");
},
};

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

@ -69,16 +69,6 @@ interface nsIUpdatePatch : nsISupports
*/
attribute unsigned long size;
/**
*
*/
attribute double progress;
/**
*
*/
attribute AString status;
/**
* The state of this update
*/
@ -231,18 +221,56 @@ interface nsIUpdatePrompt : nsISupports
interface nsIUpdateChecker : nsISupports
{
/**
* Ends any pending update check.
* Checks for available updates, notifying a listener of the results.
* @param listener
* An object implementing nsIUpdateCheckListener which is notified
* of the results of an update check.
*/
void stopChecking();
void checkForUpdates(in nsIUpdateCheckListener listener);
/**
* Constants for the |stopChecking| function that tell the Checker how long
* to stop checking:
*
* CURRENT_CHECK: Stops the current (active) check only
* CURRENT_SESSION: Stops all checking for the current session
* ANY_CHECKS: Stops all checking, any session from now on
* (disables update checking preferences)
*/
const unsigned short CURRENT_CHECK = 1;
const unsigned short CURRENT_SESSION = 2;
const unsigned short ANY_CHECKS = 3;
/**
* Ends any pending update check.
* @param duration
* A value representing the set of checks to stop doing.
*/
void stopChecking(in unsigned short duration);
/**
* Whether or not this Checker can perform update checking.
*/
readonly attribute boolean enabled;
};
[scriptable, uuid(9849c4bf-5197-4d22-baa8-e3b44a1703d2)]
interface nsIApplicationUpdateService : nsISupports
{
/**
*
* The Update Checker used for background update checking.
*/
nsIUpdateChecker checkForUpdates(in nsIUpdateCheckListener listener);
readonly attribute nsIUpdateChecker backgroundChecker;
/**
* Selects the best update to install from a list of available updates.
* @param updates
* An array of updates that are available
* @param updateCount
* The length of the |updates| array
*/
nsIUpdate selectUpdate([array, size_is(updateCount)] in nsIUpdate updates,
in unsigned long updateCount);
/**
* Adds a listener that receives progress and state information about the
@ -263,16 +291,6 @@ interface nsIApplicationUpdateService : nsISupports
*/
void removeDownloadListener(in nsIRequestObserver listener);
/**
* Selects the best update to install from a list of available updates.
* @param updates
* An array of updates that are available
* @param updateCount
* The length of the |updates| array
*/
nsIUpdate selectUpdate([array, size_is(updateCount)] in nsIUpdate updates,
in unsigned long updateCount);
/**
*
*/
@ -322,22 +340,45 @@ interface nsIUpdateManager : nsISupports
interface nsIUpdateTimerManager : nsISupports
{
/**
*
* Register an interval with the timer manager. The timer manager
* periodically checks to see if the interval has expired and if it has
* calls the specified callback. This is persistent across application
* restarts and can handle intervals of long durations.
* @param id
* An id that identifies the interval, used for persistence
* @param callback
* A nsITimerCallback object that is notified when the interval
* expires
* @param interval
* The length of time, in milliseconds, of the interval
*/
void registerTimer(in AString id,
in nsITimerCallback callback,
in unsigned long interval,
in unsigned long type);
in unsigned long interval);
};
[scriptable, uuid(22d35700-5765-42e1-914b-a0da7c911a8c)]
interface nsIVersionChecker : nsISupports
{
// -ve if B is newer
// equal if A == B
// +ve if A is newer
long compare(in AString aVersionA, in AString aVersionB);
/**
* Compare two FVF versions
* @param versionA
* The first version
* @param versionB
* The second version
* @returns < 0 if A < B
* = 0 if A == B
* > 0 if B > A
* XXXben - change the return value here to return a constant
*/
long compare(in AString versionA, in AString versionB);
boolean isValidVersion(in AString aVersion);
/**
* Determines if a string is a valid FVF version
* @param string
* The string to validate
* @returns true if the string is a valid FVF version
*/
boolean isValidVersion(in AString version);
};

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

@ -429,7 +429,6 @@ UpdateService.prototype = {
var um = Components.classes["@mozilla.org/updates/update-manager;1"]
.getService(Components.interfaces.nsIUpdateManager);
var activeUpdate = um.activeUpdate;
LOG("ACTIVEUPDATE = " + activeUpdate);
if (activeUpdate)
this.downloadUpdate(activeUpdate, true);
break;
@ -441,8 +440,7 @@ UpdateService.prototype = {
Components.classes["@mozilla.org/updates/timer-manager;1"]
.getService(Components.interfaces.nsIUpdateTimerManager);
var interval = getPref("getIntPref", PREF_APP_UPDATE_INTERVAL, 86400000);
tm.registerTimer("background-update-timer", this, interval,
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
tm.registerTimer("background-update-timer", this, interval);
break;
case "xpcom-shutdown":
gOS.removeObserver(this, "xpcom-shutdown");
@ -472,7 +470,7 @@ UpdateService.prototype = {
notify: function(timer) {
// If a download is in progress, then do nothing.
if (this.isDownloading)
if (this.isDownloading || this._downloader && this._downloader.patchIsStaged)
return;
var self = this;
@ -484,7 +482,7 @@ UpdateService.prototype = {
onError: function() { },
}
this.checkForUpdates(listener);
this.backgroundChecker.checkForUpdates(listener);
},
/**
@ -514,12 +512,18 @@ UpdateService.prototype = {
// Minor 1 Yes Notify and Confirm
// Minor 2 Yes or No Notify and Confirm
//
// In addition, if there is a license associated with an update, regardless
// of type it must be agreed to.
//
// If app.update.enabled is set to false, an update check is not performed
// at all, and so none of the decision making above is entered into.
//
if (update.licenseURL)
return true;
var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
if (!updateEnabled)
return;
return false;
var mode = getPref("getIntPref", PREF_APP_UPDATE_AUTOINSTALL_MODE, 0);
var compatible = isCompatible(update);
@ -580,12 +584,17 @@ UpdateService.prototype = {
},
/**
*
* The Checker used for background update checks.
*/
checkForUpdates: function(listener) {
var checker = new Checker();
checker.findUpdates(listener);
return checker;
_backgroundChecker: null,
/**
* See nsIUpdateService.idl
*/
get backgroundChecker() {
if (!this._backgroundChecker)
this._backgroundChecker = new Checker();
return this._backgroundChecker;
},
/**
@ -871,15 +880,17 @@ function getPref(func, preference, defaultValue) {
* @constructor
*/
function UpdatePatch(patch) {
this.type = patch.getAttribute("type");
this.URL = patch.getAttribute("URL");
this.hashFunction = patch.getAttribute("hashFunction");
this.hashValue = patch.getAttribute("hashValue");
this.size = parseInt(patch.getAttribute("size"));
this.percentage = parseFloat(patch.getAttribute("percentage"));
this.state = patch.getAttribute("state");
this.status = patch.getAttribute("status");
this.selected = patch.getAttribute("selected") == "true";
for (var i = 0; i < patch.attributes.length; ++i) {
var attr = patch.attributes[i];
switch (attr.name) {
case "selected":
this.selected = attr.value == "true";
break;
default:
this[attr.name] = attr.value;
break;
};
}
}
UpdatePatch.prototype = {
/**
@ -892,18 +903,65 @@ UpdatePatch.prototype = {
patch.setAttribute("hashFunction", this.hashFunction);
patch.setAttribute("hashValue", this.hashValue);
patch.setAttribute("size", this.size);
patch.setAttribute("percentage", this.percentage);
patch.setAttribute("state", this.state);
patch.setAttribute("status", this.status);
patch.setAttribute("selected", this.selected);
for (var p in this._properties) {
if (this._properties[p].present)
patch.setAttribute(p, this._properties[p].data);
}
return patch;
},
/**
* 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.nsIUpdatePatch) &&
!iid.equals(Components.interfaces.nsIPropertyBag) &&
!iid.equals(Components.interfaces.nsIWritablePropertyBag) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
@ -1067,13 +1125,13 @@ Checker.prototype = {
},
/**
*
* See nsIUpdateService.idl
*/
findUpdates: function(callback) {
if (!callback)
checkForUpdates: function(listener) {
if (!listener)
throw Components.results.NS_ERROR_NULL_POINTER;
if (!this._updateURL)
if (!this._updateURL || !this.enabled)
return;
this._request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
@ -1087,10 +1145,10 @@ Checker.prototype = {
this._request.onload = function(event) { self.onLoad(event); };
this._request.onprogress = function(event) { self.onProgress(event); };
LOG("Checker.findUpdates: sending request to " + this._updateURL);
LOG("Checker.checkForUpdates: sending request to " + this._updateURL);
this._request.send(null);
this._callback = callback;
this._callback = listener;
},
/**
@ -1157,11 +1215,36 @@ Checker.prototype = {
this._callback.onError(event.target);
},
/**
* Whether or not we are allowed to do update checking.
*/
_enabled: true,
/**
* See nsIUpdateService.idl
*/
stopChecking: function() {
get enabled() {
return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) ||
this._enabled;
},
/**
* See nsIUpdateService.idl
*/
stopChecking: function(duration) {
// Always stop the current check
this._request.abort();
const nsIUpdateChecker = Components.interfaces.nsIUpdateChecker;
switch (duration) {
case nsIUpdateChecker.CURRENT_SESSION:
this._enabled = false;
break;
case nsIUpdateChecker.ANY_CHECKS:
this._enabled = false;
gPref.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
break;
}
},
/**
@ -1209,7 +1292,7 @@ Downloader.prototype = {
isCompleteUpdate: null,
/**
*
* Cancels the active download.
*/
cancel: function() {
if (this._request &&
@ -1220,9 +1303,9 @@ Downloader.prototype = {
},
/**
*
* Retrieves the active Updates directory, as a nsIFile object.
*/
_getUpdatesDir: function() {
get _updatesDir() {
// Right now, we only support downloading one patch at a time, so we always
// use the same target directory.
var fileLocator =
@ -1261,6 +1344,13 @@ Downloader.prototype = {
LOG(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;
},
/**
* Verify the downloaded file. We assume that the download is complete at
@ -1365,7 +1455,7 @@ Downloader.prototype = {
updateDir.remove(true);
} catch (e) {}
// Restore the updateDir since we may have deleted it.
updateDir = this._getUpdatesDir();
updateDir = this._updatesDir;
selectedPatch = null;
}
@ -1408,13 +1498,14 @@ Downloader.prototype = {
if (!update)
throw Components.results.NS_ERROR_NULL_POINTER;
var updateDir = this._getUpdatesDir();
var updateDir = this._updatesDir;
this._update = update;
// This function may return null, which indicates that there are no patches
// to download.
this._patch = this._selectPatch(update, updateDir);
LOG("PATCH = " + this._patch);
if (!this._patch) {
LOG("no patch to download");
return this._readStatusFile(updateDir);
@ -1441,6 +1532,7 @@ Downloader.prototype = {
this._request.start(this, null);
this._writeStatusFile(updateDir, STATE_DOWNLOADING);
this._patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
this._patch.state = STATE_DOWNLOADING;
var um = Components.classes["@mozilla.org/updates/update-manager;1"]
.getService(Components.interfaces.nsIUpdateManager);
@ -1495,8 +1587,6 @@ Downloader.prototype = {
request.QueryInterface(nsIIncrementalDownload);
LOG("Downloader.onProgress: " + request.URI.spec + ", " + progress + "/" + maxProgress);
this._patch.progress = Math.round(100 * (progress/maxProgress));
var listenerCount = this._listeners.length;
for (var i = 0; i < listenerCount; ++i) {
var listener = this._listeners[i];
@ -1553,7 +1643,8 @@ Downloader.prototype = {
// download initiates.
}
}
this._writeStatusFile(this._getUpdatesDir(), state);
this._writeStatusFile(this._updatesDir, state);
this._patch.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
this._patch.state = state;
var um = Components.classes["@mozilla.org/updates/update-manager;1"]
.getService(Components.interfaces.nsIUpdateManager);
@ -1598,82 +1689,61 @@ Downloader.prototype = {
* @constructor
*/
function TimerManager() {
const nsITimer = Components.interfaces.nsITimer;
this._timer = Components.classes["@mozilla.org/timer;1"]
.createInstance(nsITimer);
var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 5000);
this._timer.initWithCallback(this, timerInterval,
nsITimer.TYPE_REPEATING_SLACK);
}
TimerManager.prototype = {
/**
* The Checker Timer
*/
_timer: null,
/**
* The set of registered timers.
*/
_timers: { },
/**
*
* Called when the checking timer fires.
* @param timer
* The checking timer that fired.
*/
registerTimer: function(id, callback, interval, type) {
const nsITimer = Components.interfaces.nsITimer;
var timer = Components.classes["@mozilla.org/timer;1"]
.createInstance(nsITimer);
var timerInterval = getPref("getIntPref", PREF_APP_UPDATE_TIMER, 5000);
notify: function(timer) {
for (var timerID in this._timers) {
var timerData = this._timers[timerID];
var lastUpdateTime = timerData.lastUpdateTime;
var now = Math.round(Date.now() / 1000);
var self = this;
/**
* A callback object implementing nsITimerCallback that determines if the
* user-registered callback should be invoked yet.
* @param id
* The id of the timer that fired
* @param callback
* The nsITimerCallback object supplied by the user that should be
* notified if the user's interval has expired.
* @param interval
* The user's interval
* @constructor
*/
function TimerCallback(id, callback, interval) {
this.id = id;
this.callback = callback;
this.interval = interval;
}
TimerCallback.prototype = {
/**
*
*/
notify: function(timer) {
// LOG("self._timers = " + self._timers.toSource());
var lastUpdateTime = self._timers[this.id].lastUpdateTime;
var now = Math.round(Date.now() / 1000);
// LOG("notify = " + (now - lastUpdateTime) + " > " + this.interval);
// Fudge the lastUpdateTime by some random increment of the update
// check interval (e.g. some random slice of 10 minutes) so that when
// the time comes to check, we offset each client request by a random
// amount so they don't all hit at once.
lastUpdateTime += Math.round(Math.random() * this.interval);
if ((now - lastUpdateTime) > this.interval &&
this.callback instanceof Components.interfaces.nsITimerCallback) {
this.callback.notify(timer);
self._timers[this.id].lastUpdateTime = now;
var preference = PREF_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, this.id);
gPref.setIntPref(preference, now);
}
},
// Fudge the lastUpdateTime by some random increment of the update
// check interval (e.g. some random slice of 10 minutes) so that when
// the time comes to check, we offset each client request by a random
// amount so they don't all hit at once.
lastUpdateTime += Math.round(Math.random() * timerData.interval);
/**
* See nsISupports.idl
*/
QueryInterface: function(iid) {
if (!iid.equals(Components.interfaces.nsITimerCallback) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
if ((now - lastUpdateTime) > timerData.interval &&
timerData.callback instanceof Components.interfaces.nsITimerCallback) {
timerData.callback.notify(timer);
timerData.lastUpdateTime = now;
var preference = PREF_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
gPref.setIntPref(preference, now);
}
};
var tc = new TimerCallback(id, callback, interval);
timer.initWithCallback(tc, timerInterval, type);
}
},
/**
* See nsIUpdateService.idl
*/
registerTimer: function(id, callback, interval) {
var preference = PREF_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
var lastUpdateTime = getPref("getIntPref", preference,
Math.round(Date.now() / 1000));
this._timers[id] = { timer: timer, lastUpdateTime: lastUpdateTime };
this._timers[id] = { callback : callback,
interval : interval,
lastUpdateTime : lastUpdateTime };
},
/**
@ -1681,6 +1751,7 @@ TimerManager.prototype = {
*/
QueryInterface: function(iid) {
if (!iid.equals(Components.interfaces.nsIUpdateTimerManager) &&
!iid.equals(Components.interfaces.nsITimerCallback) &&
!iid.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
@ -1820,6 +1891,11 @@ var gModule = {
className : "Update Service",
factory : #1#(UpdateService)
},
checker: { CID : Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
contractID : "@mozilla.org/updates/update-checker;1",
className : "Update Checker",
factory : #1#(Checker)
},
manager: { CID : Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
contractID : "@mozilla.org/updates/update-manager;1",
className : "Update Manager",
@ -1860,12 +1936,14 @@ function NSGetModule(compMgr, fileSpec) {
* the specified update, false otherwise.
*/
function isCompatible(update) {
#ifdef MOZ_XULAPP
#ifdef MOZ_XUL_APP
var em = Components.classes["@mozilla.org/extensions/manager;1"]
.getService(Components.interfaces.nsIExtensionManager);
var items = em.getIncompatibleItemList("", update.extensionVersion,
nsIUpdateItem.TYPE_ADDON, { });
return items.length == 0;
#else
return true;
#endif
}