dom/network/src/NetworkStatsService.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/network/src/NetworkStatsService.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1155 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +const DEBUG = false;
    1.11 +function debug(s) {
    1.12 +  if (DEBUG) {
    1.13 +    dump("-*- NetworkStatsService: " + s + "\n");
    1.14 +  }
    1.15 +}
    1.16 +
    1.17 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    1.18 +
    1.19 +this.EXPORTED_SYMBOLS = ["NetworkStatsService"];
    1.20 +
    1.21 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.22 +Cu.import("resource://gre/modules/Services.jsm");
    1.23 +Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
    1.24 +
    1.25 +const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
    1.26 +const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
    1.27 +
    1.28 +const TOPIC_BANDWIDTH_CONTROL = "netd-bandwidth-control"
    1.29 +
    1.30 +const TOPIC_INTERFACE_REGISTERED   = "network-interface-registered";
    1.31 +const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
    1.32 +const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
    1.33 +const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
    1.34 +
    1.35 +// Networks have different status that NetworkStats API needs to be aware of.
    1.36 +// Network is present and ready, so NetworkManager provides the whole info.
    1.37 +const NETWORK_STATUS_READY   = 0;
    1.38 +// Network is present but hasn't established a connection yet (e.g. SIM that has not
    1.39 +// enabled 3G since boot).
    1.40 +const NETWORK_STATUS_STANDBY = 1;
    1.41 +// Network is not present, but stored in database by the previous connections.
    1.42 +const NETWORK_STATUS_AWAY    = 2;
    1.43 +
    1.44 +// The maximum traffic amount can be saved in the |cachedStats|.
    1.45 +const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
    1.46 +
    1.47 +const QUEUE_TYPE_UPDATE_STATS = 0;
    1.48 +const QUEUE_TYPE_UPDATE_CACHE = 1;
    1.49 +const QUEUE_TYPE_WRITE_CACHE = 2;
    1.50 +
    1.51 +XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
    1.52 +                                   "@mozilla.org/parentprocessmessagemanager;1",
    1.53 +                                   "nsIMessageListenerManager");
    1.54 +
    1.55 +XPCOMUtils.defineLazyServiceGetter(this, "gRil",
    1.56 +                                   "@mozilla.org/ril;1",
    1.57 +                                   "nsIRadioInterfaceLayer");
    1.58 +
    1.59 +XPCOMUtils.defineLazyServiceGetter(this, "networkService",
    1.60 +                                   "@mozilla.org/network/service;1",
    1.61 +                                   "nsINetworkService");
    1.62 +
    1.63 +XPCOMUtils.defineLazyServiceGetter(this, "appsService",
    1.64 +                                   "@mozilla.org/AppsService;1",
    1.65 +                                   "nsIAppsService");
    1.66 +
    1.67 +XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
    1.68 +                                   "@mozilla.org/settingsService;1",
    1.69 +                                   "nsISettingsService");
    1.70 +
    1.71 +XPCOMUtils.defineLazyServiceGetter(this, "messenger",
    1.72 +                                   "@mozilla.org/system-message-internal;1",
    1.73 +                                   "nsISystemMessagesInternal");
    1.74 +
    1.75 +this.NetworkStatsService = {
    1.76 +  init: function() {
    1.77 +    debug("Service started");
    1.78 +
    1.79 +    Services.obs.addObserver(this, "xpcom-shutdown", false);
    1.80 +    Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false);
    1.81 +    Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false);
    1.82 +    Services.obs.addObserver(this, TOPIC_BANDWIDTH_CONTROL, false);
    1.83 +    Services.obs.addObserver(this, "profile-after-change", false);
    1.84 +
    1.85 +    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    1.86 +
    1.87 +    // Object to store network interfaces, each network interface is composed
    1.88 +    // by a network object (network type and network Id) and a interfaceName
    1.89 +    // that contains the name of the physical interface (wlan0, rmnet0, etc.).
    1.90 +    // The network type can be 0 for wifi or 1 for mobile. On the other hand,
    1.91 +    // the network id is '0' for wifi or the iccid for mobile (SIM).
    1.92 +    // Each networkInterface is placed in the _networks object by the index of
    1.93 +    // 'networkId + networkType'.
    1.94 +    //
    1.95 +    // _networks object allows to map available network interfaces at low level
    1.96 +    // (wlan0, rmnet0, etc.) to a network. It's not mandatory to have a
    1.97 +    // networkInterface per network but can't exist a networkInterface not
    1.98 +    // being mapped to a network.
    1.99 +
   1.100 +    this._networks = Object.create(null);
   1.101 +
   1.102 +    // There is no way to know a priori if wifi connection is available,
   1.103 +    // just when the wifi driver is loaded, but it is unloaded when
   1.104 +    // wifi is switched off. So wifi connection is hardcoded
   1.105 +    let netId = this.getNetworkId('0', NET_TYPE_WIFI);
   1.106 +    this._networks[netId] = { network:       { id: '0',
   1.107 +                                               type: NET_TYPE_WIFI },
   1.108 +                              interfaceName: null,
   1.109 +                              status:        NETWORK_STATUS_STANDBY };
   1.110 +
   1.111 +    this.messages = ["NetworkStats:Get",
   1.112 +                     "NetworkStats:Clear",
   1.113 +                     "NetworkStats:ClearAll",
   1.114 +                     "NetworkStats:SetAlarm",
   1.115 +                     "NetworkStats:GetAlarms",
   1.116 +                     "NetworkStats:RemoveAlarms",
   1.117 +                     "NetworkStats:GetAvailableNetworks",
   1.118 +                     "NetworkStats:GetAvailableServiceTypes",
   1.119 +                     "NetworkStats:SampleRate",
   1.120 +                     "NetworkStats:MaxStorageAge"];
   1.121 +
   1.122 +    this.messages.forEach(function(aMsgName) {
   1.123 +      ppmm.addMessageListener(aMsgName, this);
   1.124 +    }, this);
   1.125 +
   1.126 +    this._db = new NetworkStatsDB();
   1.127 +
   1.128 +    // Stats for all interfaces are updated periodically
   1.129 +    this.timer.initWithCallback(this, this._db.sampleRate,
   1.130 +                                Ci.nsITimer.TYPE_REPEATING_PRECISE);
   1.131 +
   1.132 +    // Stats not from netd are firstly stored in the cached.
   1.133 +    this.cachedStats = Object.create(null);
   1.134 +    this.cachedStatsDate = new Date();
   1.135 +
   1.136 +    this.updateQueue = [];
   1.137 +    this.isQueueRunning = false;
   1.138 +
   1.139 +    this._currentAlarms = {};
   1.140 +    this.initAlarms();
   1.141 +  },
   1.142 +
   1.143 +  receiveMessage: function(aMessage) {
   1.144 +    if (!aMessage.target.assertPermission("networkstats-manage")) {
   1.145 +      return;
   1.146 +    }
   1.147 +
   1.148 +    debug("receiveMessage " + aMessage.name);
   1.149 +
   1.150 +    let mm = aMessage.target;
   1.151 +    let msg = aMessage.json;
   1.152 +
   1.153 +    switch (aMessage.name) {
   1.154 +      case "NetworkStats:Get":
   1.155 +        this.getSamples(mm, msg);
   1.156 +        break;
   1.157 +      case "NetworkStats:Clear":
   1.158 +        this.clearInterfaceStats(mm, msg);
   1.159 +        break;
   1.160 +      case "NetworkStats:ClearAll":
   1.161 +        this.clearDB(mm, msg);
   1.162 +        break;
   1.163 +      case "NetworkStats:SetAlarm":
   1.164 +        this.setAlarm(mm, msg);
   1.165 +        break;
   1.166 +      case "NetworkStats:GetAlarms":
   1.167 +        this.getAlarms(mm, msg);
   1.168 +        break;
   1.169 +      case "NetworkStats:RemoveAlarms":
   1.170 +        this.removeAlarms(mm, msg);
   1.171 +        break;
   1.172 +      case "NetworkStats:GetAvailableNetworks":
   1.173 +        this.getAvailableNetworks(mm, msg);
   1.174 +        break;
   1.175 +      case "NetworkStats:GetAvailableServiceTypes":
   1.176 +        this.getAvailableServiceTypes(mm, msg);
   1.177 +        break;
   1.178 +      case "NetworkStats:SampleRate":
   1.179 +        // This message is sync.
   1.180 +        return this._db.sampleRate;
   1.181 +      case "NetworkStats:MaxStorageAge":
   1.182 +        // This message is sync.
   1.183 +        return this._db.maxStorageSamples * this._db.sampleRate;
   1.184 +    }
   1.185 +  },
   1.186 +
   1.187 +  observe: function observe(aSubject, aTopic, aData) {
   1.188 +    switch (aTopic) {
   1.189 +      case TOPIC_INTERFACE_REGISTERED:
   1.190 +      case TOPIC_INTERFACE_UNREGISTERED:
   1.191 +
   1.192 +        // If new interface is registered (notified from NetworkService),
   1.193 +        // the stats are updated for the new interface without waiting to
   1.194 +        // complete the updating period.
   1.195 +
   1.196 +        let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
   1.197 +        debug("Network " + network.name + " of type " + network.type + " status change");
   1.198 +
   1.199 +        let netId = this.convertNetworkInterface(network);
   1.200 +        if (!netId) {
   1.201 +          break;
   1.202 +        }
   1.203 +
   1.204 +        this._updateCurrentAlarm(netId);
   1.205 +
   1.206 +        debug("NetId: " + netId);
   1.207 +        this.updateStats(netId);
   1.208 +        break;
   1.209 +
   1.210 +      case TOPIC_BANDWIDTH_CONTROL:
   1.211 +        debug("Bandwidth message from netd: " + JSON.stringify(aData));
   1.212 +
   1.213 +        let interfaceName = aData.substring(aData.lastIndexOf(" ") + 1);
   1.214 +        for (let networkId in this._networks) {
   1.215 +          if (interfaceName == this._networks[networkId].interfaceName) {
   1.216 +            let currentAlarm = this._currentAlarms[networkId];
   1.217 +            if (Object.getOwnPropertyNames(currentAlarm).length !== 0) {
   1.218 +              this._fireAlarm(currentAlarm.alarm);
   1.219 +            }
   1.220 +            break;
   1.221 +          }
   1.222 +        }
   1.223 +        break;
   1.224 +
   1.225 +      case "xpcom-shutdown":
   1.226 +        debug("Service shutdown");
   1.227 +
   1.228 +        this.messages.forEach(function(aMsgName) {
   1.229 +          ppmm.removeMessageListener(aMsgName, this);
   1.230 +        }, this);
   1.231 +
   1.232 +        Services.obs.removeObserver(this, "xpcom-shutdown");
   1.233 +        Services.obs.removeObserver(this, "profile-after-change");
   1.234 +        Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED);
   1.235 +        Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED);
   1.236 +        Services.obs.removeObserver(this, TOPIC_BANDWIDTH_CONTROL);
   1.237 +
   1.238 +        this.timer.cancel();
   1.239 +        this.timer = null;
   1.240 +
   1.241 +        // Update stats before shutdown
   1.242 +        this.updateAllStats();
   1.243 +        break;
   1.244 +    }
   1.245 +  },
   1.246 +
   1.247 +  /*
   1.248 +   * nsITimerCallback
   1.249 +   * Timer triggers the update of all stats
   1.250 +   */
   1.251 +  notify: function(aTimer) {
   1.252 +    this.updateAllStats();
   1.253 +  },
   1.254 +
   1.255 +  /*
   1.256 +   * nsINetworkStatsService
   1.257 +   */
   1.258 +  getRilNetworks: function() {
   1.259 +    let networks = {};
   1.260 +    let numRadioInterfaces = gRil.numRadioInterfaces;
   1.261 +    for (let i = 0; i < numRadioInterfaces; i++) {
   1.262 +      let radioInterface = gRil.getRadioInterface(i);
   1.263 +      if (radioInterface.rilContext.iccInfo) {
   1.264 +        let netId = this.getNetworkId(radioInterface.rilContext.iccInfo.iccid,
   1.265 +                                      NET_TYPE_MOBILE);
   1.266 +        networks[netId] = { id : radioInterface.rilContext.iccInfo.iccid,
   1.267 +                            type: NET_TYPE_MOBILE };
   1.268 +      }
   1.269 +    }
   1.270 +    return networks;
   1.271 +  },
   1.272 +
   1.273 +  convertNetworkInterface: function(aNetwork) {
   1.274 +    if (aNetwork.type != NET_TYPE_MOBILE &&
   1.275 +        aNetwork.type != NET_TYPE_WIFI) {
   1.276 +      return null;
   1.277 +    }
   1.278 +
   1.279 +    let id = '0';
   1.280 +    if (aNetwork.type == NET_TYPE_MOBILE) {
   1.281 +      if (!(aNetwork instanceof Ci.nsIRilNetworkInterface)) {
   1.282 +        debug("Error! Mobile network should be an nsIRilNetworkInterface!");
   1.283 +        return null;
   1.284 +      }
   1.285 +
   1.286 +      let rilNetwork = aNetwork.QueryInterface(Ci.nsIRilNetworkInterface);
   1.287 +      id = rilNetwork.iccId;
   1.288 +    }
   1.289 +
   1.290 +    let netId = this.getNetworkId(id, aNetwork.type);
   1.291 +
   1.292 +    if (!this._networks[netId]) {
   1.293 +      this._networks[netId] = Object.create(null);
   1.294 +      this._networks[netId].network = { id: id,
   1.295 +                                        type: aNetwork.type };
   1.296 +    }
   1.297 +
   1.298 +    this._networks[netId].status = NETWORK_STATUS_READY;
   1.299 +    this._networks[netId].interfaceName = aNetwork.name;
   1.300 +    return netId;
   1.301 +  },
   1.302 +
   1.303 +  getNetworkId: function getNetworkId(aIccId, aNetworkType) {
   1.304 +    return aIccId + '' + aNetworkType;
   1.305 +  },
   1.306 +
   1.307 +  /* Function to ensure that one network is valid. The network is valid if its status is
   1.308 +   * NETWORK_STATUS_READY, NETWORK_STATUS_STANDBY or NETWORK_STATUS_AWAY.
   1.309 +   *
   1.310 +   * The result is |netId| or null in case of a non-valid network
   1.311 +   * aCallback is signatured as |function(netId)|.
   1.312 +   */
   1.313 +  validateNetwork: function validateNetwork(aNetwork, aCallback) {
   1.314 +    let netId = this.getNetworkId(aNetwork.id, aNetwork.type);
   1.315 +
   1.316 +    if (this._networks[netId]) {
   1.317 +      aCallback(netId);
   1.318 +      return;
   1.319 +    }
   1.320 +
   1.321 +    // Check if network is valid (RIL entry) but has not established a connection yet.
   1.322 +    // If so add to networks list with empty interfaceName.
   1.323 +    let rilNetworks = this.getRilNetworks();
   1.324 +    if (rilNetworks[netId]) {
   1.325 +      this._networks[netId] = Object.create(null);
   1.326 +      this._networks[netId].network = rilNetworks[netId];
   1.327 +      this._networks[netId].status = NETWORK_STATUS_STANDBY;
   1.328 +      this._currentAlarms[netId] = Object.create(null);
   1.329 +      aCallback(netId);
   1.330 +      return;
   1.331 +    }
   1.332 +
   1.333 +    // Check if network is available in the DB.
   1.334 +    this._db.isNetworkAvailable(aNetwork, function(aError, aResult) {
   1.335 +      if (aResult) {
   1.336 +        this._networks[netId] = Object.create(null);
   1.337 +        this._networks[netId].network = aNetwork;
   1.338 +        this._networks[netId].status = NETWORK_STATUS_AWAY;
   1.339 +        this._currentAlarms[netId] = Object.create(null);
   1.340 +        aCallback(netId);
   1.341 +        return;
   1.342 +      }
   1.343 +
   1.344 +      aCallback(null);
   1.345 +    }.bind(this));
   1.346 +  },
   1.347 +
   1.348 +  getAvailableNetworks: function getAvailableNetworks(mm, msg) {
   1.349 +    let self = this;
   1.350 +    let rilNetworks = this.getRilNetworks();
   1.351 +    this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
   1.352 +
   1.353 +      // Also return the networks that are valid but have not
   1.354 +      // established connections yet.
   1.355 +      for (let netId in rilNetworks) {
   1.356 +        let found = false;
   1.357 +        for (let i = 0; i < aResult.length; i++) {
   1.358 +          if (netId == self.getNetworkId(aResult[i].id, aResult[i].type)) {
   1.359 +            found = true;
   1.360 +            break;
   1.361 +          }
   1.362 +        }
   1.363 +        if (!found) {
   1.364 +          aResult.push(rilNetworks[netId]);
   1.365 +        }
   1.366 +      }
   1.367 +
   1.368 +      mm.sendAsyncMessage("NetworkStats:GetAvailableNetworks:Return",
   1.369 +                          { id: msg.id, error: aError, result: aResult });
   1.370 +    });
   1.371 +  },
   1.372 +
   1.373 +  getAvailableServiceTypes: function getAvailableServiceTypes(mm, msg) {
   1.374 +    this._db.getAvailableServiceTypes(function onGetServiceTypes(aError, aResult) {
   1.375 +      mm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes:Return",
   1.376 +                          { id: msg.id, error: aError, result: aResult });
   1.377 +    });
   1.378 +  },
   1.379 +
   1.380 +  initAlarms: function initAlarms() {
   1.381 +    debug("Init usage alarms");
   1.382 +    let self = this;
   1.383 +
   1.384 +    for (let netId in this._networks) {
   1.385 +      this._currentAlarms[netId] = Object.create(null);
   1.386 +
   1.387 +      this._db.getFirstAlarm(netId, function getResult(error, result) {
   1.388 +        if (!error && result) {
   1.389 +          self._setAlarm(result, function onSet(error, success) {
   1.390 +            if (error == "InvalidStateError") {
   1.391 +              self._fireAlarm(result);
   1.392 +            }
   1.393 +          });
   1.394 +        }
   1.395 +      });
   1.396 +    }
   1.397 +  },
   1.398 +
   1.399 +  /*
   1.400 +   * Function called from manager to get stats from database.
   1.401 +   * In order to return updated stats, first is performed a call to
   1.402 +   * updateAllStats function, which will get last stats from netd
   1.403 +   * and update the database.
   1.404 +   * Then, depending on the request (stats per appId or total stats)
   1.405 +   * it retrieve them from database and return to the manager.
   1.406 +   */
   1.407 +  getSamples: function getSamples(mm, msg) {
   1.408 +    let network = msg.network;
   1.409 +    let netId = this.getNetworkId(network.id, network.type);
   1.410 +
   1.411 +    let appId = 0;
   1.412 +    let appManifestURL = msg.appManifestURL;
   1.413 +    if (appManifestURL) {
   1.414 +      appId = appsService.getAppLocalIdByManifestURL(appManifestURL);
   1.415 +
   1.416 +      if (!appId) {
   1.417 +        mm.sendAsyncMessage("NetworkStats:Get:Return",
   1.418 +                            { id: msg.id,
   1.419 +                              error: "Invalid appManifestURL", result: null });
   1.420 +        return;
   1.421 +      }
   1.422 +    }
   1.423 +
   1.424 +    let serviceType = msg.serviceType || "";
   1.425 +
   1.426 +    let start = new Date(msg.start);
   1.427 +    let end = new Date(msg.end);
   1.428 +
   1.429 +    let callback = (function (aError, aResult) {
   1.430 +      this._db.find(function onStatsFound(aError, aResult) {
   1.431 +        mm.sendAsyncMessage("NetworkStats:Get:Return",
   1.432 +                            { id: msg.id, error: aError, result: aResult });
   1.433 +      }, appId, serviceType, network, start, end, appManifestURL);
   1.434 +    }).bind(this);
   1.435 +
   1.436 +    this.validateNetwork(network, function onValidateNetwork(aNetId) {
   1.437 +      if (!aNetId) {
   1.438 +        mm.sendAsyncMessage("NetworkStats:Get:Return",
   1.439 +                            { id: msg.id, error: "Invalid connectionType", result: null });
   1.440 +        return;
   1.441 +      }
   1.442 +
   1.443 +      // If network is currently active we need to update the cached stats first before
   1.444 +      // retrieving stats from the DB.
   1.445 +      if (this._networks[aNetId].status == NETWORK_STATUS_READY) {
   1.446 +        debug("getstats for network " + network.id + " of type " + network.type);
   1.447 +        debug("appId: " + appId + " from appManifestURL: " + appManifestURL);
   1.448 +        debug("serviceType: " + serviceType);
   1.449 +
   1.450 +        if (appId || serviceType) {
   1.451 +          this.updateCachedStats(callback);
   1.452 +          return;
   1.453 +        }
   1.454 +
   1.455 +        this.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) {
   1.456 +          this.updateCachedStats(callback);
   1.457 +        }.bind(this));
   1.458 +        return;
   1.459 +      }
   1.460 +
   1.461 +      // Network not active, so no need to update
   1.462 +      this._db.find(function onStatsFound(aError, aResult) {
   1.463 +        mm.sendAsyncMessage("NetworkStats:Get:Return",
   1.464 +                            { id: msg.id, error: aError, result: aResult });
   1.465 +      }, appId, serviceType, network, start, end, appManifestURL);
   1.466 +    }.bind(this));
   1.467 +  },
   1.468 +
   1.469 +  clearInterfaceStats: function clearInterfaceStats(mm, msg) {
   1.470 +    let self = this;
   1.471 +    let network = msg.network;
   1.472 +
   1.473 +    debug("clear stats for network " + network.id + " of type " + network.type);
   1.474 +
   1.475 +    this.validateNetwork(network, function onValidateNetwork(aNetId) {
   1.476 +      if (!aNetId) {
   1.477 +        mm.sendAsyncMessage("NetworkStats:Clear:Return",
   1.478 +                            { id: msg.id, error: "Invalid connectionType", result: null });
   1.479 +        return;
   1.480 +      }
   1.481 +
   1.482 +      network = {network: network, networkId: aNetId};
   1.483 +      self.updateStats(aNetId, function onUpdate(aResult, aMessage) {
   1.484 +        if (!aResult) {
   1.485 +          mm.sendAsyncMessage("NetworkStats:Clear:Return",
   1.486 +                              { id: msg.id, error: aMessage, result: null });
   1.487 +          return;
   1.488 +        }
   1.489 +
   1.490 +        self._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
   1.491 +          self._updateCurrentAlarm(aNetId);
   1.492 +          mm.sendAsyncMessage("NetworkStats:Clear:Return",
   1.493 +                              { id: msg.id, error: aError, result: aResult });
   1.494 +        });
   1.495 +      });
   1.496 +    });
   1.497 +  },
   1.498 +
   1.499 +  clearDB: function clearDB(mm, msg) {
   1.500 +    let self = this;
   1.501 +    this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
   1.502 +      if (aError) {
   1.503 +        mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
   1.504 +                            { id: msg.id, error: aError, result: aResult });
   1.505 +        return;
   1.506 +      }
   1.507 +
   1.508 +      let networks = aResult;
   1.509 +      networks.forEach(function(network, index) {
   1.510 +        networks[index] = {network: network, networkId: self.getNetworkId(network.id, network.type)};
   1.511 +      }, self);
   1.512 +
   1.513 +      self.updateAllStats(function onUpdate(aResult, aMessage){
   1.514 +        if (!aResult) {
   1.515 +          mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
   1.516 +                              { id: msg.id, error: aMessage, result: null });
   1.517 +          return;
   1.518 +        }
   1.519 +
   1.520 +        self._db.clearStats(networks, function onDBCleared(aError, aResult) {
   1.521 +          networks.forEach(function(network, index) {
   1.522 +            self._updateCurrentAlarm(network.networkId);
   1.523 +          }, self);
   1.524 +          mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
   1.525 +                              { id: msg.id, error: aError, result: aResult });
   1.526 +        });
   1.527 +      });
   1.528 +    });
   1.529 +  },
   1.530 +
   1.531 +  updateAllStats: function updateAllStats(aCallback) {
   1.532 +    let elements = [];
   1.533 +    let lastElement = null;
   1.534 +    let callback = (function (success, message) {
   1.535 +      this.updateCachedStats(aCallback);
   1.536 +    }).bind(this);
   1.537 +
   1.538 +    // For each connectionType create an object containning the type
   1.539 +    // and the 'queueIndex', the 'queueIndex' is an integer representing
   1.540 +    // the index of a connection type in the global queue array. So, if
   1.541 +    // the connection type is already in the queue it is not appended again,
   1.542 +    // else it is pushed in 'elements' array, which later will be pushed to
   1.543 +    // the queue array.
   1.544 +    for (let netId in this._networks) {
   1.545 +      if (this._networks[netId].status != NETWORK_STATUS_READY) {
   1.546 +        continue;
   1.547 +      }
   1.548 +
   1.549 +      lastElement = { netId: netId,
   1.550 +                      queueIndex: this.updateQueueIndex(netId) };
   1.551 +
   1.552 +      if (lastElement.queueIndex == -1) {
   1.553 +        elements.push({ netId:     lastElement.netId,
   1.554 +                        callbacks: [],
   1.555 +                        queueType: QUEUE_TYPE_UPDATE_STATS });
   1.556 +      }
   1.557 +    }
   1.558 +
   1.559 +    if (!lastElement) {
   1.560 +      // No elements need to be updated, probably because status is different than
   1.561 +      // NETWORK_STATUS_READY.
   1.562 +      if (aCallback) {
   1.563 +        aCallback(true, "OK");
   1.564 +      }
   1.565 +      return;
   1.566 +    }
   1.567 +
   1.568 +    if (elements.length > 0) {
   1.569 +      // If length of elements is greater than 0, callback is set to
   1.570 +      // the last element.
   1.571 +      elements[elements.length - 1].callbacks.push(callback);
   1.572 +      this.updateQueue = this.updateQueue.concat(elements);
   1.573 +    } else {
   1.574 +      // Else, it means that all connection types are already in the queue to
   1.575 +      // be updated, so callback for this request is added to
   1.576 +      // the element in the main queue with the index of the last 'lastElement'.
   1.577 +      // But before is checked that element is still in the queue because it can
   1.578 +      // be processed while generating 'elements' array.
   1.579 +      let element = this.updateQueue[lastElement.queueIndex];
   1.580 +      if (aCallback &&
   1.581 +         (!element || element.netId != lastElement.netId)) {
   1.582 +        aCallback();
   1.583 +        return;
   1.584 +      }
   1.585 +
   1.586 +      this.updateQueue[lastElement.queueIndex].callbacks.push(callback);
   1.587 +    }
   1.588 +
   1.589 +    // Call the function that process the elements of the queue.
   1.590 +    this.processQueue();
   1.591 +
   1.592 +    if (DEBUG) {
   1.593 +      this.logAllRecords();
   1.594 +    }
   1.595 +  },
   1.596 +
   1.597 +  updateStats: function updateStats(aNetId, aCallback) {
   1.598 +    // Check if the connection is in the main queue, push a new element
   1.599 +    // if it is not being processed or add a callback if it is.
   1.600 +    let index = this.updateQueueIndex(aNetId);
   1.601 +    if (index == -1) {
   1.602 +      this.updateQueue.push({ netId: aNetId,
   1.603 +                              callbacks: [aCallback],
   1.604 +                              queueType: QUEUE_TYPE_UPDATE_STATS });
   1.605 +    } else {
   1.606 +      this.updateQueue[index].callbacks.push(aCallback);
   1.607 +      return;
   1.608 +    }
   1.609 +
   1.610 +    // Call the function that process the elements of the queue.
   1.611 +    this.processQueue();
   1.612 +  },
   1.613 +
   1.614 +  /*
   1.615 +   * Find if a connection is in the main queue array and return its
   1.616 +   * index, if it is not in the array return -1.
   1.617 +   */
   1.618 +  updateQueueIndex: function updateQueueIndex(aNetId) {
   1.619 +    return this.updateQueue.map(function(e) { return e.netId; }).indexOf(aNetId);
   1.620 +  },
   1.621 +
   1.622 +  /*
   1.623 +   * Function responsible of process all requests in the queue.
   1.624 +   */
   1.625 +  processQueue: function processQueue(aResult, aMessage) {
   1.626 +    // If aResult is not undefined, the caller of the function is the result
   1.627 +    // of processing an element, so remove that element and call the callbacks
   1.628 +    // it has.
   1.629 +    if (aResult != undefined) {
   1.630 +      let item = this.updateQueue.shift();
   1.631 +      for (let callback of item.callbacks) {
   1.632 +        if (callback) {
   1.633 +          callback(aResult, aMessage);
   1.634 +        }
   1.635 +      }
   1.636 +    } else {
   1.637 +      // The caller is a function that has pushed new elements to the queue,
   1.638 +      // if isQueueRunning is false it means there is no processing currently
   1.639 +      // being done, so start.
   1.640 +      if (this.isQueueRunning) {
   1.641 +        return;
   1.642 +      } else {
   1.643 +        this.isQueueRunning = true;
   1.644 +      }
   1.645 +    }
   1.646 +
   1.647 +    // Check length to determine if queue is empty and stop processing.
   1.648 +    if (this.updateQueue.length < 1) {
   1.649 +      this.isQueueRunning = false;
   1.650 +      return;
   1.651 +    }
   1.652 +
   1.653 +    // Call the update function for the next element.
   1.654 +    switch (this.updateQueue[0].queueType) {
   1.655 +      case QUEUE_TYPE_UPDATE_STATS:
   1.656 +        this.update(this.updateQueue[0].netId, this.processQueue.bind(this));
   1.657 +        break;
   1.658 +      case QUEUE_TYPE_UPDATE_CACHE:
   1.659 +        this.updateCache(this.processQueue.bind(this));
   1.660 +        break;
   1.661 +      case QUEUE_TYPE_WRITE_CACHE:
   1.662 +        this.writeCache(this.updateQueue[0].stats, this.processQueue.bind(this));
   1.663 +        break;
   1.664 +    }
   1.665 +  },
   1.666 +
   1.667 +  update: function update(aNetId, aCallback) {
   1.668 +    // Check if connection type is valid.
   1.669 +    if (!this._networks[aNetId]) {
   1.670 +      if (aCallback) {
   1.671 +        aCallback(false, "Invalid network " + aNetId);
   1.672 +      }
   1.673 +      return;
   1.674 +    }
   1.675 +
   1.676 +    let interfaceName = this._networks[aNetId].interfaceName;
   1.677 +    debug("Update stats for " + interfaceName);
   1.678 +
   1.679 +    // Request stats to NetworkService, which will get stats from netd, passing
   1.680 +    // 'networkStatsAvailable' as a callback.
   1.681 +    if (interfaceName) {
   1.682 +      networkService.getNetworkInterfaceStats(interfaceName,
   1.683 +                this.networkStatsAvailable.bind(this, aCallback, aNetId));
   1.684 +      return;
   1.685 +    }
   1.686 +
   1.687 +    if (aCallback) {
   1.688 +      aCallback(true, "ok");
   1.689 +    }
   1.690 +  },
   1.691 +
   1.692 +  /*
   1.693 +   * Callback of request stats. Store stats in database.
   1.694 +   */
   1.695 +  networkStatsAvailable: function networkStatsAvailable(aCallback, aNetId,
   1.696 +                                                        aResult, aRxBytes,
   1.697 +                                                        aTxBytes, aDate) {
   1.698 +    if (!aResult) {
   1.699 +      if (aCallback) {
   1.700 +        aCallback(false, "Netd IPC error");
   1.701 +      }
   1.702 +      return;
   1.703 +    }
   1.704 +
   1.705 +    let stats = { appId:          0,
   1.706 +                  serviceType:    "",
   1.707 +                  networkId:      this._networks[aNetId].network.id,
   1.708 +                  networkType:    this._networks[aNetId].network.type,
   1.709 +                  date:           aDate,
   1.710 +                  rxBytes:        aTxBytes,
   1.711 +                  txBytes:        aRxBytes,
   1.712 +                  isAccumulative: true };
   1.713 +
   1.714 +    debug("Update stats for: " + JSON.stringify(stats));
   1.715 +
   1.716 +    this._db.saveStats(stats, function onSavedStats(aError, aResult) {
   1.717 +      if (aCallback) {
   1.718 +        if (aError) {
   1.719 +          aCallback(false, aError);
   1.720 +          return;
   1.721 +        }
   1.722 +
   1.723 +        aCallback(true, "OK");
   1.724 +      }
   1.725 +    });
   1.726 +  },
   1.727 +
   1.728 +  /*
   1.729 +   * Function responsible for receiving stats which are not from netd.
   1.730 +   */
   1.731 +  saveStats: function saveStats(aAppId, aServiceType, aNetwork, aTimeStamp,
   1.732 +                                aRxBytes, aTxBytes, aIsAccumulative,
   1.733 +                                aCallback) {
   1.734 +    let netId = this.convertNetworkInterface(aNetwork);
   1.735 +    if (!netId) {
   1.736 +      if (aCallback) {
   1.737 +        aCallback(false, "Invalid network type");
   1.738 +      }
   1.739 +      return;
   1.740 +    }
   1.741 +
   1.742 +    // Check if |aConnectionType|, |aAppId| and |aServiceType| are valid.
   1.743 +    // There are two invalid cases for the combination of |aAppId| and
   1.744 +    // |aServiceType|:
   1.745 +    // a. Both |aAppId| is non-zero and |aServiceType| is non-empty.
   1.746 +    // b. Both |aAppId| is zero and |aServiceType| is empty.
   1.747 +    if (!this._networks[netId] || (aAppId && aServiceType) ||
   1.748 +        (!aAppId && !aServiceType)) {
   1.749 +      debug("Invalid network interface, appId or serviceType");
   1.750 +      return;
   1.751 +    }
   1.752 +
   1.753 +    let stats = { appId:          aAppId,
   1.754 +                  serviceType:    aServiceType,
   1.755 +                  networkId:      this._networks[netId].network.id,
   1.756 +                  networkType:    this._networks[netId].network.type,
   1.757 +                  date:           new Date(aTimeStamp),
   1.758 +                  rxBytes:        aRxBytes,
   1.759 +                  txBytes:        aTxBytes,
   1.760 +                  isAccumulative: aIsAccumulative };
   1.761 +
   1.762 +    this.updateQueue.push({ stats: stats,
   1.763 +                            callbacks: [aCallback],
   1.764 +                            queueType: QUEUE_TYPE_WRITE_CACHE });
   1.765 +
   1.766 +    this.processQueue();
   1.767 +  },
   1.768 +
   1.769 +  /*
   1.770 +   *
   1.771 +   */
   1.772 +  writeCache: function writeCache(aStats, aCallback) {
   1.773 +    debug("saveStats: " + aStats.appId + " " + aStats.serviceType + " " +
   1.774 +          aStats.networkId + " " + aStats.networkType + " " + aStats.date + " "
   1.775 +          + aStats.date + " " + aStats.rxBytes + " " + aStats.txBytes);
   1.776 +
   1.777 +    // Generate an unique key from |appId|, |serviceType| and |netId|,
   1.778 +    // which is used to retrieve data in |cachedStats|.
   1.779 +    let netId = this.getNetworkId(aStats.networkId, aStats.networkType);
   1.780 +    let key = aStats.appId + "" + aStats.serviceType + "" + netId;
   1.781 +
   1.782 +    // |cachedStats| only keeps the data with the same date.
   1.783 +    // If the incoming date is different from |cachedStatsDate|,
   1.784 +    // both |cachedStats| and |cachedStatsDate| will get updated.
   1.785 +    let diff = (this._db.normalizeDate(aStats.date) -
   1.786 +                this._db.normalizeDate(this.cachedStatsDate)) /
   1.787 +               this._db.sampleRate;
   1.788 +    if (diff != 0) {
   1.789 +      this.updateCache(function onUpdated(success, message) {
   1.790 +        this.cachedStatsDate = aStats.date;
   1.791 +        this.cachedStats[key] = aStats;
   1.792 +
   1.793 +        if (aCallback) {
   1.794 +          aCallback(true, "ok");
   1.795 +        }
   1.796 +      }.bind(this));
   1.797 +      return;
   1.798 +    }
   1.799 +
   1.800 +    // Try to find the matched row in the cached by |appId| and |connectionType|.
   1.801 +    // If not found, save the incoming data into the cached.
   1.802 +    let cachedStats = this.cachedStats[key];
   1.803 +    if (!cachedStats) {
   1.804 +      this.cachedStats[key] = aStats;
   1.805 +      if (aCallback) {
   1.806 +        aCallback(true, "ok");
   1.807 +      }
   1.808 +      return;
   1.809 +    }
   1.810 +
   1.811 +    // Find matched row, accumulate the traffic amount.
   1.812 +    cachedStats.rxBytes += aStats.rxBytes;
   1.813 +    cachedStats.txBytes += aStats.txBytes;
   1.814 +
   1.815 +    // If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC
   1.816 +    // the corresponding row will be saved to indexedDB.
   1.817 +    // Then, the row will be removed from the cached.
   1.818 +    if (cachedStats.rxBytes > MAX_CACHED_TRAFFIC ||
   1.819 +        cachedStats.txBytes > MAX_CACHED_TRAFFIC) {
   1.820 +      this._db.saveStats(cachedStats, function (error, result) {
   1.821 +        debug("Application stats inserted in indexedDB");
   1.822 +        if (aCallback) {
   1.823 +          aCallback(true, "ok");
   1.824 +        }
   1.825 +      });
   1.826 +      delete this.cachedStats[key];
   1.827 +      return;
   1.828 +    }
   1.829 +
   1.830 +    if (aCallback) {
   1.831 +      aCallback(true, "ok");
   1.832 +    }
   1.833 +  },
   1.834 +
   1.835 +  updateCachedStats: function updateCachedStats(aCallback) {
   1.836 +    this.updateQueue.push({ callbacks: [aCallback],
   1.837 +                            queueType: QUEUE_TYPE_UPDATE_CACHE });
   1.838 +
   1.839 +    this.processQueue();
   1.840 +  },
   1.841 +
   1.842 +  updateCache: function updateCache(aCallback) {
   1.843 +    debug("updateCache: " + this.cachedStatsDate);
   1.844 +
   1.845 +    let stats = Object.keys(this.cachedStats);
   1.846 +    if (stats.length == 0) {
   1.847 +      // |cachedStats| is empty, no need to update.
   1.848 +      if (aCallback) {
   1.849 +        aCallback(true, "no need to update");
   1.850 +      }
   1.851 +      return;
   1.852 +    }
   1.853 +
   1.854 +    let index = 0;
   1.855 +    this._db.saveStats(this.cachedStats[stats[index]],
   1.856 +                       function onSavedStats(error, result) {
   1.857 +      debug("Application stats inserted in indexedDB");
   1.858 +
   1.859 +      // Clean up the |cachedStats| after updating.
   1.860 +      if (index == stats.length - 1) {
   1.861 +        this.cachedStats = Object.create(null);
   1.862 +
   1.863 +        if (aCallback) {
   1.864 +          aCallback(true, "ok");
   1.865 +        }
   1.866 +        return;
   1.867 +      }
   1.868 +
   1.869 +      // Update is not finished, keep updating.
   1.870 +      index += 1;
   1.871 +      this._db.saveStats(this.cachedStats[stats[index]],
   1.872 +                         onSavedStats.bind(this, error, result));
   1.873 +    }.bind(this));
   1.874 +  },
   1.875 +
   1.876 +  get maxCachedTraffic () {
   1.877 +    return MAX_CACHED_TRAFFIC;
   1.878 +  },
   1.879 +
   1.880 +  logAllRecords: function logAllRecords() {
   1.881 +    this._db.logAllRecords(function onResult(aError, aResult) {
   1.882 +      if (aError) {
   1.883 +        debug("Error: " + aError);
   1.884 +        return;
   1.885 +      }
   1.886 +
   1.887 +      debug("===== LOG =====");
   1.888 +      debug("There are " + aResult.length + " items");
   1.889 +      debug(JSON.stringify(aResult));
   1.890 +    });
   1.891 +  },
   1.892 +
   1.893 +  getAlarms: function getAlarms(mm, msg) {
   1.894 +    let self = this;
   1.895 +    let network = msg.data.network;
   1.896 +    let manifestURL = msg.data.manifestURL;
   1.897 +
   1.898 +    if (network) {
   1.899 +      this.validateNetwork(network, function onValidateNetwork(aNetId) {
   1.900 +        if (!aNetId) {
   1.901 +          mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
   1.902 +                              { id: msg.id, error: "InvalidInterface", result: null });
   1.903 +          return;
   1.904 +        }
   1.905 +
   1.906 +        self._getAlarms(mm, msg, aNetId, manifestURL);
   1.907 +      });
   1.908 +      return;
   1.909 +    }
   1.910 +
   1.911 +    this._getAlarms(mm, msg, null, manifestURL);
   1.912 +  },
   1.913 +
   1.914 +  _getAlarms: function _getAlarms(mm, msg, aNetId, aManifestURL) {
   1.915 +    let self = this;
   1.916 +    this._db.getAlarms(aNetId, aManifestURL, function onCompleted(error, result) {
   1.917 +      if (error) {
   1.918 +        mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
   1.919 +                            { id: msg.id, error: error, result: result });
   1.920 +        return;
   1.921 +      }
   1.922 +
   1.923 +      let alarms = []
   1.924 +      // NetworkStatsManager must return the network instead of the networkId.
   1.925 +      for (let i = 0; i < result.length; i++) {
   1.926 +        let alarm = result[i];
   1.927 +        alarms.push({ id: alarm.id,
   1.928 +                      network: self._networks[alarm.networkId].network,
   1.929 +                      threshold: alarm.absoluteThreshold,
   1.930 +                      data: alarm.data });
   1.931 +      }
   1.932 +
   1.933 +      mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
   1.934 +                          { id: msg.id, error: null, result: alarms });
   1.935 +    });
   1.936 +  },
   1.937 +
   1.938 +  removeAlarms: function removeAlarms(mm, msg) {
   1.939 +    let alarmId = msg.data.alarmId;
   1.940 +    let manifestURL = msg.data.manifestURL;
   1.941 +
   1.942 +    let self = this;
   1.943 +    let callback = function onRemove(error, result) {
   1.944 +      if (error) {
   1.945 +        mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
   1.946 +                            { id: msg.id, error: error, result: result });
   1.947 +        return;
   1.948 +      }
   1.949 +
   1.950 +      for (let i in self._currentAlarms) {
   1.951 +        let currentAlarm = self._currentAlarms[i].alarm;
   1.952 +        if (currentAlarm && ((alarmId == currentAlarm.id) ||
   1.953 +            (alarmId == -1 && currentAlarm.manifestURL == manifestURL))) {
   1.954 +
   1.955 +          self._updateCurrentAlarm(currentAlarm.networkId);
   1.956 +        }
   1.957 +      }
   1.958 +
   1.959 +      mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
   1.960 +                          { id: msg.id, error: error, result: true });
   1.961 +    };
   1.962 +
   1.963 +    if (alarmId == -1) {
   1.964 +      this._db.removeAlarms(manifestURL, callback);
   1.965 +    } else {
   1.966 +      this._db.removeAlarm(alarmId, manifestURL, callback);
   1.967 +    }
   1.968 +  },
   1.969 +
   1.970 +  /*
   1.971 +   * Function called from manager to set an alarm.
   1.972 +   */
   1.973 +  setAlarm: function setAlarm(mm, msg) {
   1.974 +    let options = msg.data;
   1.975 +    let network = options.network;
   1.976 +    let threshold = options.threshold;
   1.977 +
   1.978 +    debug("Set alarm at " + threshold + " for " + JSON.stringify(network));
   1.979 +
   1.980 +    if (threshold < 0) {
   1.981 +      mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
   1.982 +                          { id: msg.id, error: "InvalidThresholdValue", result: null });
   1.983 +      return;
   1.984 +    }
   1.985 +
   1.986 +    let self = this;
   1.987 +    this.validateNetwork(network, function onValidateNetwork(aNetId) {
   1.988 +      if (!aNetId) {
   1.989 +        mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
   1.990 +                            { id: msg.id, error: "InvalidiConnectionType", result: null });
   1.991 +        return;
   1.992 +      }
   1.993 +
   1.994 +      let newAlarm = {
   1.995 +        id: null,
   1.996 +        networkId: aNetId,
   1.997 +        absoluteThreshold: threshold,
   1.998 +        relativeThreshold: null,
   1.999 +        startTime: options.startTime,
  1.1000 +        data: options.data,
  1.1001 +        pageURL: options.pageURL,
  1.1002 +        manifestURL: options.manifestURL
  1.1003 +      };
  1.1004 +
  1.1005 +      self._getAlarmQuota(newAlarm, function onUpdate(error, quota) {
  1.1006 +        if (error) {
  1.1007 +          mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
  1.1008 +                              { id: msg.id, error: error, result: null });
  1.1009 +          return;
  1.1010 +        }
  1.1011 +
  1.1012 +        self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
  1.1013 +          if (error) {
  1.1014 +            mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
  1.1015 +                                { id: msg.id, error: error, result: null });
  1.1016 +            return;
  1.1017 +          }
  1.1018 +
  1.1019 +          newAlarm.id = newId;
  1.1020 +          self._setAlarm(newAlarm, function onSet(error, success) {
  1.1021 +            mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
  1.1022 +                                { id: msg.id, error: error, result: newId });
  1.1023 +
  1.1024 +            if (error == "InvalidStateError") {
  1.1025 +              self._fireAlarm(newAlarm);
  1.1026 +            }
  1.1027 +          });
  1.1028 +        });
  1.1029 +      });
  1.1030 +    });
  1.1031 +  },
  1.1032 +
  1.1033 +  _setAlarm: function _setAlarm(aAlarm, aCallback) {
  1.1034 +    let currentAlarm = this._currentAlarms[aAlarm.networkId];
  1.1035 +    if ((Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
  1.1036 +         aAlarm.relativeThreshold > currentAlarm.alarm.relativeThreshold) ||
  1.1037 +        this._networks[aAlarm.networkId].status != NETWORK_STATUS_READY) {
  1.1038 +      aCallback(null, true);
  1.1039 +      return;
  1.1040 +    }
  1.1041 +
  1.1042 +    let self = this;
  1.1043 +
  1.1044 +    this._getAlarmQuota(aAlarm, function onUpdate(aError, aQuota) {
  1.1045 +      if (aError) {
  1.1046 +        aCallback(aError, null);
  1.1047 +        return;
  1.1048 +      }
  1.1049 +
  1.1050 +      let callback = function onAlarmSet(aError) {
  1.1051 +        if (aError) {
  1.1052 +          debug("Set alarm error: " + aError);
  1.1053 +          aCallback("netdError", null);
  1.1054 +          return;
  1.1055 +        }
  1.1056 +
  1.1057 +        self._currentAlarms[aAlarm.networkId].alarm = aAlarm;
  1.1058 +
  1.1059 +        aCallback(null, true);
  1.1060 +      };
  1.1061 +
  1.1062 +      debug("Set alarm " + JSON.stringify(aAlarm));
  1.1063 +      let interfaceName = self._networks[aAlarm.networkId].interfaceName;
  1.1064 +      if (interfaceName) {
  1.1065 +        networkService.setNetworkInterfaceAlarm(interfaceName,
  1.1066 +                                                aQuota,
  1.1067 +                                                callback);
  1.1068 +        return;
  1.1069 +      }
  1.1070 +
  1.1071 +      aCallback(null, true);
  1.1072 +    });
  1.1073 +  },
  1.1074 +
  1.1075 +  _getAlarmQuota: function _getAlarmQuota(aAlarm, aCallback) {
  1.1076 +    let self = this;
  1.1077 +    this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) {
  1.1078 +      self._db.getCurrentStats(self._networks[aAlarm.networkId].network,
  1.1079 +                               aAlarm.startTime,
  1.1080 +                               function onStatsFound(error, result) {
  1.1081 +        if (error) {
  1.1082 +          debug("Error getting stats for " +
  1.1083 +                JSON.stringify(self._networks[aAlarm.networkId]) + ": " + error);
  1.1084 +          aCallback(error, result);
  1.1085 +          return;
  1.1086 +        }
  1.1087 +
  1.1088 +        let quota = aAlarm.absoluteThreshold - result.rxBytes - result.txBytes;
  1.1089 +
  1.1090 +        // Alarm set to a threshold lower than current rx/tx bytes.
  1.1091 +        if (quota <= 0) {
  1.1092 +          aCallback("InvalidStateError", null);
  1.1093 +          return;
  1.1094 +        }
  1.1095 +
  1.1096 +        aAlarm.relativeThreshold = aAlarm.startTime
  1.1097 +                                 ? result.rxTotalBytes + result.txTotalBytes + quota
  1.1098 +                                 : aAlarm.absoluteThreshold;
  1.1099 +
  1.1100 +        aCallback(null, quota);
  1.1101 +      });
  1.1102 +    });
  1.1103 +  },
  1.1104 +
  1.1105 +  _fireAlarm: function _fireAlarm(aAlarm) {
  1.1106 +    debug("Fire alarm");
  1.1107 +
  1.1108 +    let self = this;
  1.1109 +    this._db.removeAlarm(aAlarm.id, null, function onRemove(aError, aResult){
  1.1110 +      if (!aError && !aResult) {
  1.1111 +        return;
  1.1112 +      }
  1.1113 +
  1.1114 +      self._fireSystemMessage(aAlarm);
  1.1115 +      self._updateCurrentAlarm(aAlarm.networkId);
  1.1116 +    });
  1.1117 +  },
  1.1118 +
  1.1119 +  _updateCurrentAlarm: function _updateCurrentAlarm(aNetworkId) {
  1.1120 +    this._currentAlarms[aNetworkId] = Object.create(null);
  1.1121 +
  1.1122 +    let self = this;
  1.1123 +    this._db.getFirstAlarm(aNetworkId, function onGet(error, result){
  1.1124 +      if (error) {
  1.1125 +        debug("Error getting the first alarm");
  1.1126 +        return;
  1.1127 +      }
  1.1128 +
  1.1129 +      if (!result) {
  1.1130 +        let interfaceName = self._networks[aNetworkId].interfaceName;
  1.1131 +        networkService.setNetworkInterfaceAlarm(interfaceName, -1,
  1.1132 +                                                function onComplete(){});
  1.1133 +        return;
  1.1134 +      }
  1.1135 +
  1.1136 +      self._setAlarm(result, function onSet(error, success){
  1.1137 +        if (error == "InvalidStateError") {
  1.1138 +          self._fireAlarm(result);
  1.1139 +          return;
  1.1140 +        }
  1.1141 +      });
  1.1142 +    });
  1.1143 +  },
  1.1144 +
  1.1145 +  _fireSystemMessage: function _fireSystemMessage(aAlarm) {
  1.1146 +    debug("Fire system message: " + JSON.stringify(aAlarm));
  1.1147 +
  1.1148 +    let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
  1.1149 +    let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
  1.1150 +
  1.1151 +    let alarm = { "id":        aAlarm.id,
  1.1152 +                  "threshold": aAlarm.absoluteThreshold,
  1.1153 +                  "data":      aAlarm.data };
  1.1154 +    messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI);
  1.1155 +  }
  1.1156 +};
  1.1157 +
  1.1158 +NetworkStatsService.init();

mercurial