PageRenderTime 35ms CodeModel.GetById 18ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/toolkit/mozapps/update/nsUpdateTimerManager.js

http://github.com/zpao/v8monkey
JavaScript | 337 lines | 198 code | 27 blank | 112 comment | 30 complexity | b8e4df8dd8431306929875cf2c471bd5 MD5 | raw file
  1/*
  2# ***** BEGIN LICENSE BLOCK *****
  3# Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4#
  5# The contents of this file are subject to the Mozilla Public License Version
  6# 1.1 (the "License"); you may not use this file except in compliance with
  7# the License. You may obtain a copy of the License at
  8# http://www.mozilla.org/MPL/
  9#
 10# Software distributed under the License is distributed on an "AS IS" basis,
 11# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 12# for the specific language governing rights and limitations under the
 13# License.
 14#
 15# The Original Code is the Update timer Manager.
 16#
 17# The Initial Developer of the Original Code is Ben Goodger.
 18# Portions created by the Initial Developer are Copyright (C) 2004
 19# the Initial Developer. All Rights Reserved.
 20#
 21# Contributor(s):
 22#  Ben Goodger <ben@mozilla.org> (Original Author)
 23#  Robert Strong <robert.bugzilla@gmail.com>
 24#
 25# Alternatively, the contents of this file may be used under the terms of
 26# either the GNU General Public License Version 2 or later (the "GPL"), or
 27# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 28# in which case the provisions of the GPL or the LGPL are applicable instead
 29# of those above. If you wish to allow use of your version of this file only
 30# under the terms of either the GPL or the LGPL, and not to allow others to
 31# use your version of this file under the terms of the MPL, indicate your
 32# decision by deleting the provisions above and replace them with the notice
 33# and other provisions required by the GPL or the LGPL. If you do not delete
 34# the provisions above, a recipient may use your version of this file under
 35# the terms of any one of the MPL, the GPL or the LGPL.
 36#
 37# ***** END LICENSE BLOCK *****
 38*/
 39Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 40Components.utils.import("resource://gre/modules/Services.jsm");
 41
 42const Cc = Components.classes;
 43const Ci = Components.interfaces;
 44
 45const PREF_APP_UPDATE_LASTUPDATETIME_FMT  = "app.update.lastUpdateTime.%ID%";
 46const PREF_APP_UPDATE_TIMERMINIMUMDELAY   = "app.update.timerMinimumDelay";
 47const PREF_APP_UPDATE_TIMERFIRSTINTERVAL  = "app.update.timerFirstInterval";
 48const PREF_APP_UPDATE_LOG                 = "app.update.log";
 49
 50const CATEGORY_UPDATE_TIMER               = "update-timer";
 51
 52XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function tm_gLogEnabled() {
 53  return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
 54});
 55
 56/**
 57#  Gets a preference value, handling the case where there is no default.
 58#  @param   func
 59#           The name of the preference function to call, on nsIPrefBranch
 60#  @param   preference
 61#           The name of the preference
 62#  @param   defaultValue
 63#           The default value to return in the event the preference has
 64#           no setting
 65#  @returns The value of the preference, or undefined if there was no
 66#           user or default value.
 67 */
 68function getPref(func, preference, defaultValue) {
 69  try {
 70    return Services.prefs[func](preference);
 71  }
 72  catch (e) {
 73  }
 74  return defaultValue;
 75}
 76
 77/**
 78#  Logs a string to the error console.
 79#  @param   string
 80#           The string to write to the error console.
 81 */
 82function LOG(string) {
 83  if (gLogEnabled) {
 84    dump("*** UTM:SVC " + string + "\n");
 85    Services.console.logStringMessage("UTM:SVC " + string);
 86  }
 87}
 88
 89/**
 90#  A manager for timers. Manages timers that fire over long periods of time
 91#  (e.g. days, weeks, months).
 92#  @constructor
 93 */
 94function TimerManager() {
 95  Services.obs.addObserver(this, "xpcom-shutdown", false);
 96}
 97TimerManager.prototype = {
 98  /**
 99   * The Checker Timer
100   */
101  _timer: null,
102
103  /**
104#    The Checker Timer minimum delay interval as specified by the
105#    app.update.timerMinimumDelay pref. If the app.update.timerMinimumDelay
106#    pref doesn't exist this will default to 120000.
107   */
108   _timerMinimumDelay: null,
109
110  /**
111   * The set of registered timers.
112   */
113  _timers: { },
114
115  /**
116   * See nsIObserver.idl
117   */
118  observe: function TM_observe(aSubject, aTopic, aData) {
119    // Prevent setting the timer interval to a value of less than 60 seconds.
120    var minInterval = 60000;
121    // Prevent setting the first timer interval to a value of less than 10
122    // seconds.
123    var minFirstInterval = 10000;
124    switch (aTopic) {
125    case "utm-test-init":
126      // Enforce a minimum timer interval of 500 ms for tests and fall through
127      // to profile-after-change to initialize the timer.
128      minInterval = 500;
129      minFirstInterval = 500;
130    case "profile-after-change":
131      // Cancel the timer if it has already been initialized. This is primarily
132      // for tests.
133      this._timerMinimumDelay = Math.max(1000 * getPref("getIntPref", PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
134                                         minInterval);
135      let firstInterval = Math.max(getPref("getIntPref", PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
136                                           this._timerMinimumDelay), minFirstInterval);
137      this._canEnsureTimer = true;
138      this._ensureTimer(firstInterval);
139      break;
140    case "xpcom-shutdown":
141      Services.obs.removeObserver(this, "xpcom-shutdown");
142
143      // Release everything we hold onto.
144      this._cancelTimer();
145      for (var timerID in this._timers)
146        delete this._timers[timerID];
147      this._timers = null;
148      break;
149    }
150  },
151
152  /**
153#    Called when the checking timer fires.
154#
155#    We only fire one notification each time, so that the operations are
156#    staggered. We don't want too many to happen at once, which could
157#    negatively impact responsiveness.
158#
159#    @param   timer
160#             The checking timer that fired.
161   */
162  notify: function TM_notify(timer) {
163    var nextDelay = null;
164    function updateNextDelay(delay) {
165      if (nextDelay === null || delay < nextDelay)
166        nextDelay = delay;
167    }
168
169    // Each timer calls tryFire(), which figures out which is the the one that
170    // wanted to be called earliest. That one will be fired; the others are
171    // skipped and will be done later.
172    var now = Math.round(Date.now() / 1000);
173
174    var callbackToFire = null;
175    var earliestIntendedTime = null;
176    var skippedFirings = false;
177    function tryFire(callback, intendedTime) {
178      var selected = false;
179      if (intendedTime <= now) {
180        if (intendedTime < earliestIntendedTime ||
181            earliestIntendedTime === null) {
182          callbackToFire = callback;
183          earliestIntendedTime = intendedTime;
184          selected = true;
185        }
186        else if (earliestIntendedTime !== null)
187          skippedFirings = true;
188      }
189      // We do not need to updateNextDelay for the timer that actually fires;
190      // we'll update right after it fires, with the proper intended time.
191      // Note that we might select one, then select another later (with an
192      // earlier intended time); it is still ok that we did not update for
193      // the first one, since if we have skipped firings, the next delay
194      // will be the minimum delay anyhow.
195      if (!selected)
196        updateNextDelay(intendedTime - now);
197    }
198
199    var catMan = Cc["@mozilla.org/categorymanager;1"].
200                 getService(Ci.nsICategoryManager);
201    var entries = catMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
202    while (entries.hasMoreElements()) {
203      let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
204      let value = catMan.getCategoryEntry(CATEGORY_UPDATE_TIMER, entry);
205      let [cid, method, timerID, prefInterval, defaultInterval] = value.split(",");
206      let lastUpdateTime;
207
208      defaultInterval = parseInt(defaultInterval);
209      // cid and method are validated below when calling notify.
210      if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
211        LOG("TimerManager:notify - update-timer category registered" +
212            (cid ? " for " + cid : "") + " without required parameters - " +
213             "skipping");
214        continue;
215      }
216
217      let interval = getPref("getIntPref", prefInterval, defaultInterval);
218      let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
219                                                                  timerID);
220      if (Services.prefs.prefHasUserValue(prefLastUpdate)) {
221        lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate);
222      }
223      else {
224        lastUpdateTime = now;
225        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
226      }
227
228      tryFire(function() {
229        try {
230          Components.classes[cid][method](Ci.nsITimerCallback).notify(timer);
231          LOG("TimerManager:notify - notified " + cid);
232        }
233        catch (e) {
234          LOG("TimerManager:notify - error notifying component id: " +
235              cid + " ,error: " + e);
236        }
237        lastUpdateTime = now;
238        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
239        updateNextDelay(lastUpdateTime + interval - now);
240      }, lastUpdateTime + interval);
241    }
242
243    for (let _timerID in this._timers) {
244      let timerID = _timerID; // necessary for the closure to work properly
245      let timerData = this._timers[timerID];
246      tryFire(function() {
247        if (timerData.callback instanceof Ci.nsITimerCallback) {
248          try {
249            timerData.callback.notify(timer);
250            LOG("TimerManager:notify - notified timerID: " + timerID);
251          }
252          catch (e) {
253            LOG("TimerManager:notify - error notifying timerID: " + timerID +
254                ", error: " + e);
255          }
256        }
257        else {
258          LOG("TimerManager:notify - timerID: " + timerID + " doesn't " +
259              "implement nsITimerCallback - skipping");
260        }
261        lastUpdateTime = now;
262        timerData.lastUpdateTime = lastUpdateTime;
263        var prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
264        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
265        updateNextDelay(timerData.lastUpdateTime + timerData.interval - now);
266      }, timerData.lastUpdateTime + timerData.interval);
267    }
268
269    if (callbackToFire)
270      callbackToFire();
271
272    if (nextDelay !== null) {
273      if (skippedFirings)
274        timer.delay = this._timerMinimumDelay;
275      else
276        timer.delay = Math.max(nextDelay * 1000, this._timerMinimumDelay);  
277      this.lastTimerReset = Date.now();
278    } else {
279      this._cancelTimer();
280    }
281  },
282
283  /**
284   * Starts the timer, if necessary, and ensures that it will fire soon enough
285   * to happen after time |interval| (in milliseconds).
286   */
287  _ensureTimer: function(interval) {
288    if (!this._canEnsureTimer)
289      return;
290    if (!this._timer) {
291      this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
292      this._timer.initWithCallback(this, interval,
293                                   Ci.nsITimer.TYPE_REPEATING_SLACK);
294      this.lastTimerReset = Date.now();
295    } else {
296      if (Date.now() + interval < this.lastTimerReset + this._timer.delay) 
297        this._timer.delay = this.lastTimerReset + interval - Date.now();
298    }
299  },
300
301  /**
302   * Stops the timer, if it is running.
303   */
304  _cancelTimer: function() {
305    if (this._timer) {
306      this._timer.cancel();
307      this._timer = null;
308    }
309  },
310
311  /**
312   * See nsIUpdateTimerManager.idl
313   */
314  registerTimer: function TM_registerTimer(id, callback, interval) {
315    LOG("TimerManager:registerTimer - id: " + id);
316    var prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
317    var lastUpdateTime;
318    if (Services.prefs.prefHasUserValue(prefLastUpdate)) {
319      lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate);
320    } else {
321      lastUpdateTime = Math.round(Date.now() / 1000);
322      Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
323    }
324    this._timers[id] = { callback       : callback,
325                         interval       : interval,
326                         lastUpdateTime : lastUpdateTime };
327
328    this._ensureTimer(interval * 1000);
329  },
330
331  classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
332  QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateTimerManager,
333                                         Ci.nsITimerCallback,
334                                         Ci.nsIObserver])
335};
336
337var NSGetFactory = XPCOMUtils.generateNSGetFactory([TimerManager]);