dom/network/src/NetworkStatsDB.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 this.EXPORTED_SYMBOLS = ['NetworkStatsDB'];
michael@0 8
michael@0 9 const DEBUG = false;
michael@0 10 function debug(s) { dump("-*- NetworkStatsDB: " + s + "\n"); }
michael@0 11
michael@0 12 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
michael@0 13
michael@0 14 Cu.import("resource://gre/modules/Services.jsm");
michael@0 15 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
michael@0 16 Cu.importGlobalProperties(["indexedDB"]);
michael@0 17
michael@0 18 const DB_NAME = "net_stats";
michael@0 19 const DB_VERSION = 8;
michael@0 20 const DEPRECATED_STORE_NAME = "net_stats";
michael@0 21 const STATS_STORE_NAME = "net_stats_store";
michael@0 22 const ALARMS_STORE_NAME = "net_alarm";
michael@0 23
michael@0 24 // Constant defining the maximum values allowed per interface. If more, older
michael@0 25 // will be erased.
michael@0 26 const VALUES_MAX_LENGTH = 6 * 30;
michael@0 27
michael@0 28 // Constant defining the rate of the samples. Daily.
michael@0 29 const SAMPLE_RATE = 1000 * 60 * 60 * 24;
michael@0 30
michael@0 31 this.NetworkStatsDB = function NetworkStatsDB() {
michael@0 32 if (DEBUG) {
michael@0 33 debug("Constructor");
michael@0 34 }
michael@0 35 this.initDBHelper(DB_NAME, DB_VERSION, [STATS_STORE_NAME, ALARMS_STORE_NAME]);
michael@0 36 }
michael@0 37
michael@0 38 NetworkStatsDB.prototype = {
michael@0 39 __proto__: IndexedDBHelper.prototype,
michael@0 40
michael@0 41 dbNewTxn: function dbNewTxn(store_name, txn_type, callback, txnCb) {
michael@0 42 function successCb(result) {
michael@0 43 txnCb(null, result);
michael@0 44 }
michael@0 45 function errorCb(error) {
michael@0 46 txnCb(error, null);
michael@0 47 }
michael@0 48 return this.newTxn(txn_type, store_name, callback, successCb, errorCb);
michael@0 49 },
michael@0 50
michael@0 51 upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
michael@0 52 if (DEBUG) {
michael@0 53 debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!");
michael@0 54 }
michael@0 55 let db = aDb;
michael@0 56 let objectStore;
michael@0 57 for (let currVersion = aOldVersion; currVersion < aNewVersion; currVersion++) {
michael@0 58 if (currVersion == 0) {
michael@0 59 /**
michael@0 60 * Create the initial database schema.
michael@0 61 */
michael@0 62
michael@0 63 objectStore = db.createObjectStore(DEPRECATED_STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
michael@0 64 objectStore.createIndex("connectionType", "connectionType", { unique: false });
michael@0 65 objectStore.createIndex("timestamp", "timestamp", { unique: false });
michael@0 66 objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
michael@0 67 objectStore.createIndex("txBytes", "txBytes", { unique: false });
michael@0 68 objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
michael@0 69 objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
michael@0 70 if (DEBUG) {
michael@0 71 debug("Created object stores and indexes");
michael@0 72 }
michael@0 73 } else if (currVersion == 2) {
michael@0 74 // In order to support per-app traffic data storage, the original
michael@0 75 // objectStore needs to be replaced by a new objectStore with new
michael@0 76 // key path ("appId") and new index ("appId").
michael@0 77 // Also, since now networks are identified by their
michael@0 78 // [networkId, networkType] not just by their connectionType,
michael@0 79 // to modify the keyPath is mandatory to delete the object store
michael@0 80 // and create it again. Old data is going to be deleted because the
michael@0 81 // networkId for each sample can not be set.
michael@0 82
michael@0 83 // In version 1.2 objectStore name was 'net_stats_v2', to avoid errors when
michael@0 84 // upgrading from 1.2 to 1.3 objectStore name should be checked.
michael@0 85 let stores = db.objectStoreNames;
michael@0 86 if(stores.contains("net_stats_v2")) {
michael@0 87 db.deleteObjectStore("net_stats_v2");
michael@0 88 } else {
michael@0 89 db.deleteObjectStore(DEPRECATED_STORE_NAME);
michael@0 90 }
michael@0 91
michael@0 92 objectStore = db.createObjectStore(DEPRECATED_STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
michael@0 93 objectStore.createIndex("appId", "appId", { unique: false });
michael@0 94 objectStore.createIndex("network", "network", { unique: false });
michael@0 95 objectStore.createIndex("networkType", "networkType", { unique: false });
michael@0 96 objectStore.createIndex("timestamp", "timestamp", { unique: false });
michael@0 97 objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
michael@0 98 objectStore.createIndex("txBytes", "txBytes", { unique: false });
michael@0 99 objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
michael@0 100 objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
michael@0 101
michael@0 102 if (DEBUG) {
michael@0 103 debug("Created object stores and indexes for version 3");
michael@0 104 }
michael@0 105 } else if (currVersion == 3) {
michael@0 106 // Delete redundent indexes (leave "network" only).
michael@0 107 objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
michael@0 108 if (objectStore.indexNames.contains("appId")) {
michael@0 109 objectStore.deleteIndex("appId");
michael@0 110 }
michael@0 111 if (objectStore.indexNames.contains("networkType")) {
michael@0 112 objectStore.deleteIndex("networkType");
michael@0 113 }
michael@0 114 if (objectStore.indexNames.contains("timestamp")) {
michael@0 115 objectStore.deleteIndex("timestamp");
michael@0 116 }
michael@0 117 if (objectStore.indexNames.contains("rxBytes")) {
michael@0 118 objectStore.deleteIndex("rxBytes");
michael@0 119 }
michael@0 120 if (objectStore.indexNames.contains("txBytes")) {
michael@0 121 objectStore.deleteIndex("txBytes");
michael@0 122 }
michael@0 123 if (objectStore.indexNames.contains("rxTotalBytes")) {
michael@0 124 objectStore.deleteIndex("rxTotalBytes");
michael@0 125 }
michael@0 126 if (objectStore.indexNames.contains("txTotalBytes")) {
michael@0 127 objectStore.deleteIndex("txTotalBytes");
michael@0 128 }
michael@0 129
michael@0 130 if (DEBUG) {
michael@0 131 debug("Deleted redundent indexes for version 4");
michael@0 132 }
michael@0 133 } else if (currVersion == 4) {
michael@0 134 // In order to manage alarms, it is necessary to use a global counter
michael@0 135 // (totalBytes) that will increase regardless of the system reboot.
michael@0 136 objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
michael@0 137
michael@0 138 // Now, systemBytes will hold the old totalBytes and totalBytes will
michael@0 139 // keep the increasing counter. |counters| will keep the track of
michael@0 140 // accumulated values.
michael@0 141 let counters = {};
michael@0 142
michael@0 143 objectStore.openCursor().onsuccess = function(event) {
michael@0 144 let cursor = event.target.result;
michael@0 145 if (!cursor){
michael@0 146 return;
michael@0 147 }
michael@0 148
michael@0 149 cursor.value.rxSystemBytes = cursor.value.rxTotalBytes;
michael@0 150 cursor.value.txSystemBytes = cursor.value.txTotalBytes;
michael@0 151
michael@0 152 if (cursor.value.appId == 0) {
michael@0 153 let netId = cursor.value.network[0] + '' + cursor.value.network[1];
michael@0 154 if (!counters[netId]) {
michael@0 155 counters[netId] = {
michael@0 156 rxCounter: 0,
michael@0 157 txCounter: 0,
michael@0 158 lastRx: 0,
michael@0 159 lastTx: 0
michael@0 160 };
michael@0 161 }
michael@0 162
michael@0 163 let rxDiff = cursor.value.rxSystemBytes - counters[netId].lastRx;
michael@0 164 let txDiff = cursor.value.txSystemBytes - counters[netId].lastTx;
michael@0 165 if (rxDiff < 0 || txDiff < 0) {
michael@0 166 // System reboot between samples, so take the current one.
michael@0 167 rxDiff = cursor.value.rxSystemBytes;
michael@0 168 txDiff = cursor.value.txSystemBytes;
michael@0 169 }
michael@0 170
michael@0 171 counters[netId].rxCounter += rxDiff;
michael@0 172 counters[netId].txCounter += txDiff;
michael@0 173 cursor.value.rxTotalBytes = counters[netId].rxCounter;
michael@0 174 cursor.value.txTotalBytes = counters[netId].txCounter;
michael@0 175
michael@0 176 counters[netId].lastRx = cursor.value.rxSystemBytes;
michael@0 177 counters[netId].lastTx = cursor.value.txSystemBytes;
michael@0 178 } else {
michael@0 179 cursor.value.rxTotalBytes = cursor.value.rxSystemBytes;
michael@0 180 cursor.value.txTotalBytes = cursor.value.txSystemBytes;
michael@0 181 }
michael@0 182
michael@0 183 cursor.update(cursor.value);
michael@0 184 cursor.continue();
michael@0 185 };
michael@0 186
michael@0 187 // Create object store for alarms.
michael@0 188 objectStore = db.createObjectStore(ALARMS_STORE_NAME, { keyPath: "id", autoIncrement: true });
michael@0 189 objectStore.createIndex("alarm", ['networkId','threshold'], { unique: false });
michael@0 190 objectStore.createIndex("manifestURL", "manifestURL", { unique: false });
michael@0 191
michael@0 192 if (DEBUG) {
michael@0 193 debug("Created alarms store for version 5");
michael@0 194 }
michael@0 195 } else if (currVersion == 5) {
michael@0 196 // In contrast to "per-app" traffic data, "system-only" traffic data
michael@0 197 // refers to data which can not be identified by any applications.
michael@0 198 // To further support "system-only" data storage, the data can be
michael@0 199 // saved by service type (e.g., Tethering, OTA). Thus it's needed to
michael@0 200 // have a new key ("serviceType") for the ojectStore.
michael@0 201 let newObjectStore;
michael@0 202 newObjectStore = db.createObjectStore(STATS_STORE_NAME,
michael@0 203 { keyPath: ["appId", "serviceType", "network", "timestamp"] });
michael@0 204 newObjectStore.createIndex("network", "network", { unique: false });
michael@0 205
michael@0 206 // Copy the data from the original objectStore to the new objectStore.
michael@0 207 objectStore = aTransaction.objectStore(DEPRECATED_STORE_NAME);
michael@0 208 objectStore.openCursor().onsuccess = function(event) {
michael@0 209 let cursor = event.target.result;
michael@0 210 if (!cursor) {
michael@0 211 db.deleteObjectStore(DEPRECATED_STORE_NAME);
michael@0 212 return;
michael@0 213 }
michael@0 214
michael@0 215 let newStats = cursor.value;
michael@0 216 newStats.serviceType = "";
michael@0 217 newObjectStore.put(newStats);
michael@0 218 cursor.continue();
michael@0 219 };
michael@0 220
michael@0 221 if (DEBUG) {
michael@0 222 debug("Added new key 'serviceType' for version 6");
michael@0 223 }
michael@0 224 } else if (currVersion == 6) {
michael@0 225 // Replace threshold attribute of alarm index by relativeThreshold in alarms DB.
michael@0 226 // Now alarms are indexed by relativeThreshold, which is the threshold relative
michael@0 227 // to current system stats.
michael@0 228 let alarmsStore = aTransaction.objectStore(ALARMS_STORE_NAME);
michael@0 229
michael@0 230 // Delete "alarm" index.
michael@0 231 if (alarmsStore.indexNames.contains("alarm")) {
michael@0 232 alarmsStore.deleteIndex("alarm");
michael@0 233 }
michael@0 234
michael@0 235 // Create new "alarm" index.
michael@0 236 alarmsStore.createIndex("alarm", ['networkId','relativeThreshold'], { unique: false });
michael@0 237
michael@0 238 // Populate new "alarm" index attributes.
michael@0 239 alarmsStore.openCursor().onsuccess = function(event) {
michael@0 240 let cursor = event.target.result;
michael@0 241 if (!cursor) {
michael@0 242 return;
michael@0 243 }
michael@0 244
michael@0 245 cursor.value.relativeThreshold = cursor.value.threshold;
michael@0 246 cursor.value.absoluteThreshold = cursor.value.threshold;
michael@0 247 delete cursor.value.threshold;
michael@0 248
michael@0 249 cursor.update(cursor.value);
michael@0 250 cursor.continue();
michael@0 251 }
michael@0 252
michael@0 253 // Previous versions save accumulative totalBytes, increasing althought the system
michael@0 254 // reboots or resets stats. But is necessary to reset the total counters when reset
michael@0 255 // through 'clearInterfaceStats'.
michael@0 256 let statsStore = aTransaction.objectStore(STATS_STORE_NAME);
michael@0 257 let networks = [];
michael@0 258 // Find networks stored in the database.
michael@0 259 statsStore.index("network").openKeyCursor(null, "nextunique").onsuccess = function(event) {
michael@0 260 let cursor = event.target.result;
michael@0 261 if (cursor) {
michael@0 262 networks.push(cursor.key);
michael@0 263 cursor.continue();
michael@0 264 return;
michael@0 265 }
michael@0 266
michael@0 267 networks.forEach(function(network) {
michael@0 268 let lowerFilter = [0, "", network, 0];
michael@0 269 let upperFilter = [0, "", network, ""];
michael@0 270 let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
michael@0 271
michael@0 272 // Find number of samples for a given network.
michael@0 273 statsStore.count(range).onsuccess = function(event) {
michael@0 274 // If there are more samples than the max allowed, there is no way to know
michael@0 275 // when does reset take place.
michael@0 276 if (event.target.result >= VALUES_MAX_LENGTH) {
michael@0 277 return;
michael@0 278 }
michael@0 279
michael@0 280 let last = null;
michael@0 281 // Reset detected if the first sample totalCounters are different than bytes
michael@0 282 // counters. If so, the total counters should be recalculated.
michael@0 283 statsStore.openCursor(range).onsuccess = function(event) {
michael@0 284 let cursor = event.target.result;
michael@0 285 if (!cursor) {
michael@0 286 return;
michael@0 287 }
michael@0 288 if (!last) {
michael@0 289 if (cursor.value.rxTotalBytes == cursor.value.rxBytes &&
michael@0 290 cursor.value.txTotalBytes == cursor.value.txBytes) {
michael@0 291 return;
michael@0 292 }
michael@0 293
michael@0 294 cursor.value.rxTotalBytes = cursor.value.rxBytes;
michael@0 295 cursor.value.txTotalBytes = cursor.value.txBytes;
michael@0 296 cursor.update(cursor.value);
michael@0 297 last = cursor.value;
michael@0 298 cursor.continue();
michael@0 299 return;
michael@0 300 }
michael@0 301
michael@0 302 // Recalculate the total counter for last / current sample
michael@0 303 cursor.value.rxTotalBytes = last.rxTotalBytes + cursor.value.rxBytes;
michael@0 304 cursor.value.txTotalBytes = last.txTotalBytes + cursor.value.txBytes;
michael@0 305 cursor.update(cursor.value);
michael@0 306 last = cursor.value;
michael@0 307 cursor.continue();
michael@0 308 }
michael@0 309 }
michael@0 310 }, this);
michael@0 311 };
michael@0 312 } else if (currVersion == 7) {
michael@0 313 // Create index for 'ServiceType' in order to make it retrievable.
michael@0 314 let statsStore = aTransaction.objectStore(STATS_STORE_NAME);
michael@0 315 statsStore.createIndex("serviceType", "serviceType", { unique: false });
michael@0 316
michael@0 317 if (DEBUG) {
michael@0 318 debug("Create index of 'serviceType' for version 8");
michael@0 319 }
michael@0 320 }
michael@0 321 }
michael@0 322 },
michael@0 323
michael@0 324 importData: function importData(aStats) {
michael@0 325 let stats = { appId: aStats.appId,
michael@0 326 serviceType: aStats.serviceType,
michael@0 327 network: [aStats.networkId, aStats.networkType],
michael@0 328 timestamp: aStats.timestamp,
michael@0 329 rxBytes: aStats.rxBytes,
michael@0 330 txBytes: aStats.txBytes,
michael@0 331 rxSystemBytes: aStats.rxSystemBytes,
michael@0 332 txSystemBytes: aStats.txSystemBytes,
michael@0 333 rxTotalBytes: aStats.rxTotalBytes,
michael@0 334 txTotalBytes: aStats.txTotalBytes };
michael@0 335
michael@0 336 return stats;
michael@0 337 },
michael@0 338
michael@0 339 exportData: function exportData(aStats) {
michael@0 340 let stats = { appId: aStats.appId,
michael@0 341 serviceType: aStats.serviceType,
michael@0 342 networkId: aStats.network[0],
michael@0 343 networkType: aStats.network[1],
michael@0 344 timestamp: aStats.timestamp,
michael@0 345 rxBytes: aStats.rxBytes,
michael@0 346 txBytes: aStats.txBytes,
michael@0 347 rxTotalBytes: aStats.rxTotalBytes,
michael@0 348 txTotalBytes: aStats.txTotalBytes };
michael@0 349
michael@0 350 return stats;
michael@0 351 },
michael@0 352
michael@0 353 normalizeDate: function normalizeDate(aDate) {
michael@0 354 // Convert to UTC according to timezone and
michael@0 355 // filter timestamp to get SAMPLE_RATE precission
michael@0 356 let timestamp = aDate.getTime() - aDate.getTimezoneOffset() * 60 * 1000;
michael@0 357 timestamp = Math.floor(timestamp / SAMPLE_RATE) * SAMPLE_RATE;
michael@0 358 return timestamp;
michael@0 359 },
michael@0 360
michael@0 361 saveStats: function saveStats(aStats, aResultCb) {
michael@0 362 let isAccumulative = aStats.isAccumulative;
michael@0 363 let timestamp = this.normalizeDate(aStats.date);
michael@0 364
michael@0 365 let stats = { appId: aStats.appId,
michael@0 366 serviceType: aStats.serviceType,
michael@0 367 networkId: aStats.networkId,
michael@0 368 networkType: aStats.networkType,
michael@0 369 timestamp: timestamp,
michael@0 370 rxBytes: (isAccumulative) ? 0 : aStats.rxBytes,
michael@0 371 txBytes: (isAccumulative) ? 0 : aStats.txBytes,
michael@0 372 rxSystemBytes: (isAccumulative) ? aStats.rxBytes : 0,
michael@0 373 txSystemBytes: (isAccumulative) ? aStats.txBytes : 0,
michael@0 374 rxTotalBytes: (isAccumulative) ? aStats.rxBytes : 0,
michael@0 375 txTotalBytes: (isAccumulative) ? aStats.txBytes : 0 };
michael@0 376
michael@0 377 stats = this.importData(stats);
michael@0 378
michael@0 379 this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
michael@0 380 if (DEBUG) {
michael@0 381 debug("Filtered time: " + new Date(timestamp));
michael@0 382 debug("New stats: " + JSON.stringify(stats));
michael@0 383 }
michael@0 384
michael@0 385 let request = aStore.index("network").openCursor(stats.network, "prev");
michael@0 386 request.onsuccess = function onsuccess(event) {
michael@0 387 let cursor = event.target.result;
michael@0 388 if (!cursor) {
michael@0 389 // Empty, so save first element.
michael@0 390
michael@0 391 // There could be a time delay between the point when the network
michael@0 392 // interface comes up and the point when the database is initialized.
michael@0 393 // In this short interval some traffic data are generated but are not
michael@0 394 // registered by the first sample.
michael@0 395 if (isAccumulative) {
michael@0 396 stats.rxBytes = stats.rxTotalBytes;
michael@0 397 stats.txBytes = stats.txTotalBytes;
michael@0 398 }
michael@0 399
michael@0 400 this._saveStats(aTxn, aStore, stats);
michael@0 401 return;
michael@0 402 }
michael@0 403
michael@0 404 let value = cursor.value;
michael@0 405 if (stats.appId != value.appId ||
michael@0 406 (stats.appId == 0 && stats.serviceType != value.serviceType)) {
michael@0 407 cursor.continue();
michael@0 408 return;
michael@0 409 }
michael@0 410
michael@0 411 // There are old samples
michael@0 412 if (DEBUG) {
michael@0 413 debug("Last value " + JSON.stringify(value));
michael@0 414 }
michael@0 415
michael@0 416 // Remove stats previous to now - VALUE_MAX_LENGTH
michael@0 417 this._removeOldStats(aTxn, aStore, stats.appId, stats.serviceType,
michael@0 418 stats.network, stats.timestamp);
michael@0 419
michael@0 420 // Process stats before save
michael@0 421 this._processSamplesDiff(aTxn, aStore, cursor, stats, isAccumulative);
michael@0 422 }.bind(this);
michael@0 423 }.bind(this), aResultCb);
michael@0 424 },
michael@0 425
michael@0 426 /*
michael@0 427 * This function check that stats are saved in the database following the sample rate.
michael@0 428 * In this way is easier to find elements when stats are requested.
michael@0 429 */
michael@0 430 _processSamplesDiff: function _processSamplesDiff(aTxn,
michael@0 431 aStore,
michael@0 432 aLastSampleCursor,
michael@0 433 aNewSample,
michael@0 434 aIsAccumulative) {
michael@0 435 let lastSample = aLastSampleCursor.value;
michael@0 436
michael@0 437 // Get difference between last and new sample.
michael@0 438 let diff = (aNewSample.timestamp - lastSample.timestamp) / SAMPLE_RATE;
michael@0 439 if (diff % 1) {
michael@0 440 // diff is decimal, so some error happened because samples are stored as a multiple
michael@0 441 // of SAMPLE_RATE
michael@0 442 aTxn.abort();
michael@0 443 throw new Error("Error processing samples");
michael@0 444 }
michael@0 445
michael@0 446 if (DEBUG) {
michael@0 447 debug("New: " + aNewSample.timestamp + " - Last: " +
michael@0 448 lastSample.timestamp + " - diff: " + diff);
michael@0 449 }
michael@0 450
michael@0 451 // If the incoming data has a accumulation feature, the new
michael@0 452 // |txBytes|/|rxBytes| is assigend by differnces between the new
michael@0 453 // |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
michael@0 454 // Else, if incoming data is non-accumulative, the |txBytes|/|rxBytes|
michael@0 455 // is the new |txBytes|/|rxBytes|.
michael@0 456 let rxDiff = 0;
michael@0 457 let txDiff = 0;
michael@0 458 if (aIsAccumulative) {
michael@0 459 rxDiff = aNewSample.rxSystemBytes - lastSample.rxSystemBytes;
michael@0 460 txDiff = aNewSample.txSystemBytes - lastSample.txSystemBytes;
michael@0 461 if (rxDiff < 0 || txDiff < 0) {
michael@0 462 rxDiff = aNewSample.rxSystemBytes;
michael@0 463 txDiff = aNewSample.txSystemBytes;
michael@0 464 }
michael@0 465 aNewSample.rxBytes = rxDiff;
michael@0 466 aNewSample.txBytes = txDiff;
michael@0 467
michael@0 468 aNewSample.rxTotalBytes = lastSample.rxTotalBytes + rxDiff;
michael@0 469 aNewSample.txTotalBytes = lastSample.txTotalBytes + txDiff;
michael@0 470 } else {
michael@0 471 rxDiff = aNewSample.rxBytes;
michael@0 472 txDiff = aNewSample.txBytes;
michael@0 473 }
michael@0 474
michael@0 475 if (diff == 1) {
michael@0 476 // New element.
michael@0 477
michael@0 478 // If the incoming data is non-accumulative, the new
michael@0 479 // |rxTotalBytes|/|txTotalBytes| needs to be updated by adding new
michael@0 480 // |rxBytes|/|txBytes| to the last |rxTotalBytes|/|txTotalBytes|.
michael@0 481 if (!aIsAccumulative) {
michael@0 482 aNewSample.rxTotalBytes = aNewSample.rxBytes + lastSample.rxTotalBytes;
michael@0 483 aNewSample.txTotalBytes = aNewSample.txBytes + lastSample.txTotalBytes;
michael@0 484 }
michael@0 485
michael@0 486 this._saveStats(aTxn, aStore, aNewSample);
michael@0 487 return;
michael@0 488 }
michael@0 489 if (diff > 1) {
michael@0 490 // Some samples lost. Device off during one or more samplerate periods.
michael@0 491 // Time or timezone changed
michael@0 492 // Add lost samples with 0 bytes and the actual one.
michael@0 493 if (diff > VALUES_MAX_LENGTH) {
michael@0 494 diff = VALUES_MAX_LENGTH;
michael@0 495 }
michael@0 496
michael@0 497 let data = [];
michael@0 498 for (let i = diff - 2; i >= 0; i--) {
michael@0 499 let time = aNewSample.timestamp - SAMPLE_RATE * (i + 1);
michael@0 500 let sample = { appId: aNewSample.appId,
michael@0 501 serviceType: aNewSample.serviceType,
michael@0 502 network: aNewSample.network,
michael@0 503 timestamp: time,
michael@0 504 rxBytes: 0,
michael@0 505 txBytes: 0,
michael@0 506 rxSystemBytes: lastSample.rxSystemBytes,
michael@0 507 txSystemBytes: lastSample.txSystemBytes,
michael@0 508 rxTotalBytes: lastSample.rxTotalBytes,
michael@0 509 txTotalBytes: lastSample.txTotalBytes };
michael@0 510
michael@0 511 data.push(sample);
michael@0 512 }
michael@0 513
michael@0 514 data.push(aNewSample);
michael@0 515 this._saveStats(aTxn, aStore, data);
michael@0 516 return;
michael@0 517 }
michael@0 518 if (diff == 0 || diff < 0) {
michael@0 519 // New element received before samplerate period. It means that device has
michael@0 520 // been restarted (or clock / timezone change).
michael@0 521 // Update element. If diff < 0, clock or timezone changed back. Place data
michael@0 522 // in the last sample.
michael@0 523
michael@0 524 // Old |rxTotalBytes|/|txTotalBytes| needs to get updated by adding the
michael@0 525 // last |rxTotalBytes|/|txTotalBytes|.
michael@0 526 lastSample.rxBytes += rxDiff;
michael@0 527 lastSample.txBytes += txDiff;
michael@0 528 lastSample.rxSystemBytes = aNewSample.rxSystemBytes;
michael@0 529 lastSample.txSystemBytes = aNewSample.txSystemBytes;
michael@0 530 lastSample.rxTotalBytes += rxDiff;
michael@0 531 lastSample.txTotalBytes += txDiff;
michael@0 532
michael@0 533 if (DEBUG) {
michael@0 534 debug("Update: " + JSON.stringify(lastSample));
michael@0 535 }
michael@0 536 let req = aLastSampleCursor.update(lastSample);
michael@0 537 }
michael@0 538 },
michael@0 539
michael@0 540 _saveStats: function _saveStats(aTxn, aStore, aNetworkStats) {
michael@0 541 if (DEBUG) {
michael@0 542 debug("_saveStats: " + JSON.stringify(aNetworkStats));
michael@0 543 }
michael@0 544
michael@0 545 if (Array.isArray(aNetworkStats)) {
michael@0 546 let len = aNetworkStats.length - 1;
michael@0 547 for (let i = 0; i <= len; i++) {
michael@0 548 aStore.put(aNetworkStats[i]);
michael@0 549 }
michael@0 550 } else {
michael@0 551 aStore.put(aNetworkStats);
michael@0 552 }
michael@0 553 },
michael@0 554
michael@0 555 _removeOldStats: function _removeOldStats(aTxn, aStore, aAppId, aServiceType,
michael@0 556 aNetwork, aDate) {
michael@0 557 // Callback function to remove old items when new ones are added.
michael@0 558 let filterDate = aDate - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
michael@0 559 let lowerFilter = [aAppId, aServiceType, aNetwork, 0];
michael@0 560 let upperFilter = [aAppId, aServiceType, aNetwork, filterDate];
michael@0 561 let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
michael@0 562 let lastSample = null;
michael@0 563 let self = this;
michael@0 564
michael@0 565 aStore.openCursor(range).onsuccess = function(event) {
michael@0 566 var cursor = event.target.result;
michael@0 567 if (cursor) {
michael@0 568 lastSample = cursor.value;
michael@0 569 cursor.delete();
michael@0 570 cursor.continue();
michael@0 571 return;
michael@0 572 }
michael@0 573
michael@0 574 // If all samples for a network are removed, an empty sample
michael@0 575 // has to be saved to keep the totalBytes in order to compute
michael@0 576 // future samples because system counters are not set to 0.
michael@0 577 // Thus, if there are no samples left, the last sample removed
michael@0 578 // will be saved again after setting its bytes to 0.
michael@0 579 let request = aStore.index("network").openCursor(aNetwork);
michael@0 580 request.onsuccess = function onsuccess(event) {
michael@0 581 let cursor = event.target.result;
michael@0 582 if (!cursor && lastSample != null) {
michael@0 583 let timestamp = new Date();
michael@0 584 timestamp = self.normalizeDate(timestamp);
michael@0 585 lastSample.timestamp = timestamp;
michael@0 586 lastSample.rxBytes = 0;
michael@0 587 lastSample.txBytes = 0;
michael@0 588 self._saveStats(aTxn, aStore, lastSample);
michael@0 589 }
michael@0 590 };
michael@0 591 };
michael@0 592 },
michael@0 593
michael@0 594 clearInterfaceStats: function clearInterfaceStats(aNetwork, aResultCb) {
michael@0 595 let network = [aNetwork.network.id, aNetwork.network.type];
michael@0 596 let self = this;
michael@0 597
michael@0 598 // Clear and save an empty sample to keep sync with system counters
michael@0 599 this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
michael@0 600 let sample = null;
michael@0 601 let request = aStore.index("network").openCursor(network, "prev");
michael@0 602 request.onsuccess = function onsuccess(event) {
michael@0 603 let cursor = event.target.result;
michael@0 604 if (cursor) {
michael@0 605 if (!sample && cursor.value.appId == 0) {
michael@0 606 sample = cursor.value;
michael@0 607 }
michael@0 608
michael@0 609 cursor.delete();
michael@0 610 cursor.continue();
michael@0 611 return;
michael@0 612 }
michael@0 613
michael@0 614 if (sample) {
michael@0 615 let timestamp = new Date();
michael@0 616 timestamp = self.normalizeDate(timestamp);
michael@0 617 sample.timestamp = timestamp;
michael@0 618 sample.appId = 0;
michael@0 619 sample.serviceType = "";
michael@0 620 sample.rxBytes = 0;
michael@0 621 sample.txBytes = 0;
michael@0 622 sample.rxTotalBytes = 0;
michael@0 623 sample.txTotalBytes = 0;
michael@0 624
michael@0 625 self._saveStats(aTxn, aStore, sample);
michael@0 626 }
michael@0 627 };
michael@0 628 }, this._resetAlarms.bind(this, aNetwork.networkId, aResultCb));
michael@0 629 },
michael@0 630
michael@0 631 clearStats: function clearStats(aNetworks, aResultCb) {
michael@0 632 let index = 0;
michael@0 633 let stats = [];
michael@0 634 let self = this;
michael@0 635
michael@0 636 let callback = function(aError, aResult) {
michael@0 637 index++;
michael@0 638
michael@0 639 if (!aError && index < aNetworks.length) {
michael@0 640 self.clearInterfaceStats(aNetworks[index], callback);
michael@0 641 return;
michael@0 642 }
michael@0 643
michael@0 644 aResultCb(aError, aResult);
michael@0 645 };
michael@0 646
michael@0 647 if (!aNetworks[index]) {
michael@0 648 aResultCb(null, true);
michael@0 649 return;
michael@0 650 }
michael@0 651 this.clearInterfaceStats(aNetworks[index], callback);
michael@0 652 },
michael@0 653
michael@0 654 getCurrentStats: function getCurrentStats(aNetwork, aDate, aResultCb) {
michael@0 655 if (DEBUG) {
michael@0 656 debug("Get current stats for " + JSON.stringify(aNetwork) + " since " + aDate);
michael@0 657 }
michael@0 658
michael@0 659 let network = [aNetwork.id, aNetwork.type];
michael@0 660 if (aDate) {
michael@0 661 this._getCurrentStatsFromDate(network, aDate, aResultCb);
michael@0 662 return;
michael@0 663 }
michael@0 664
michael@0 665 this._getCurrentStats(network, aResultCb);
michael@0 666 },
michael@0 667
michael@0 668 _getCurrentStats: function _getCurrentStats(aNetwork, aResultCb) {
michael@0 669 this.dbNewTxn(STATS_STORE_NAME, "readonly", function(txn, store) {
michael@0 670 let request = null;
michael@0 671 let upperFilter = [0, "", aNetwork, Date.now()];
michael@0 672 let range = IDBKeyRange.upperBound(upperFilter, false);
michael@0 673 request = store.openCursor(range, "prev");
michael@0 674
michael@0 675 let result = { rxBytes: 0, txBytes: 0,
michael@0 676 rxTotalBytes: 0, txTotalBytes: 0 };
michael@0 677
michael@0 678 request.onsuccess = function onsuccess(event) {
michael@0 679 let cursor = event.target.result;
michael@0 680 if (cursor) {
michael@0 681 result.rxBytes = result.rxTotalBytes = cursor.value.rxTotalBytes;
michael@0 682 result.txBytes = result.txTotalBytes = cursor.value.txTotalBytes;
michael@0 683 }
michael@0 684
michael@0 685 txn.result = result;
michael@0 686 };
michael@0 687 }.bind(this), aResultCb);
michael@0 688 },
michael@0 689
michael@0 690 _getCurrentStatsFromDate: function _getCurrentStatsFromDate(aNetwork, aDate, aResultCb) {
michael@0 691 aDate = new Date(aDate);
michael@0 692 this.dbNewTxn(STATS_STORE_NAME, "readonly", function(txn, store) {
michael@0 693 let request = null;
michael@0 694 let start = this.normalizeDate(aDate);
michael@0 695 let lowerFilter = [0, "", aNetwork, start];
michael@0 696 let upperFilter = [0, "", aNetwork, Date.now()];
michael@0 697
michael@0 698 let range = IDBKeyRange.upperBound(upperFilter, false);
michael@0 699
michael@0 700 let result = { rxBytes: 0, txBytes: 0,
michael@0 701 rxTotalBytes: 0, txTotalBytes: 0 };
michael@0 702
michael@0 703 request = store.openCursor(range, "prev");
michael@0 704
michael@0 705 request.onsuccess = function onsuccess(event) {
michael@0 706 let cursor = event.target.result;
michael@0 707 if (cursor) {
michael@0 708 result.rxBytes = result.rxTotalBytes = cursor.value.rxTotalBytes;
michael@0 709 result.txBytes = result.txTotalBytes = cursor.value.txTotalBytes;
michael@0 710 }
michael@0 711
michael@0 712 let timestamp = cursor.value.timestamp;
michael@0 713 let range = IDBKeyRange.lowerBound(lowerFilter, false);
michael@0 714 request = store.openCursor(range);
michael@0 715
michael@0 716 request.onsuccess = function onsuccess(event) {
michael@0 717 let cursor = event.target.result;
michael@0 718 if (cursor) {
michael@0 719 if (cursor.value.timestamp == timestamp) {
michael@0 720 // There is one sample only.
michael@0 721 result.rxBytes = cursor.value.rxBytes;
michael@0 722 result.txBytes = cursor.value.txBytes;
michael@0 723 } else {
michael@0 724 result.rxBytes -= cursor.value.rxTotalBytes;
michael@0 725 result.txBytes -= cursor.value.txTotalBytes;
michael@0 726 }
michael@0 727 }
michael@0 728
michael@0 729 txn.result = result;
michael@0 730 };
michael@0 731 };
michael@0 732 }.bind(this), aResultCb);
michael@0 733 },
michael@0 734
michael@0 735 find: function find(aResultCb, aAppId, aServiceType, aNetwork,
michael@0 736 aStart, aEnd, aAppManifestURL) {
michael@0 737 let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
michael@0 738 let start = this.normalizeDate(aStart);
michael@0 739 let end = this.normalizeDate(aEnd);
michael@0 740
michael@0 741 if (DEBUG) {
michael@0 742 debug("Find samples for appId: " + aAppId + " serviceType: " +
michael@0 743 aServiceType + " network: " + JSON.stringify(aNetwork) + " from " +
michael@0 744 start + " until " + end);
michael@0 745 debug("Start time: " + new Date(start));
michael@0 746 debug("End time: " + new Date(end));
michael@0 747 }
michael@0 748
michael@0 749 this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
michael@0 750 let network = [aNetwork.id, aNetwork.type];
michael@0 751 let lowerFilter = [aAppId, aServiceType, network, start];
michael@0 752 let upperFilter = [aAppId, aServiceType, network, end];
michael@0 753 let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
michael@0 754
michael@0 755 let data = [];
michael@0 756
michael@0 757 if (!aTxn.result) {
michael@0 758 aTxn.result = {};
michael@0 759 }
michael@0 760
michael@0 761 let request = aStore.openCursor(range).onsuccess = function(event) {
michael@0 762 var cursor = event.target.result;
michael@0 763 if (cursor){
michael@0 764 data.push({ rxBytes: cursor.value.rxBytes,
michael@0 765 txBytes: cursor.value.txBytes,
michael@0 766 date: new Date(cursor.value.timestamp + offset) });
michael@0 767 cursor.continue();
michael@0 768 return;
michael@0 769 }
michael@0 770
michael@0 771 // When requested samples (start / end) are not in the range of now and
michael@0 772 // now - VALUES_MAX_LENGTH, fill with empty samples.
michael@0 773 this.fillResultSamples(start + offset, end + offset, data);
michael@0 774
michael@0 775 aTxn.result.appManifestURL = aAppManifestURL;
michael@0 776 aTxn.result.serviceType = aServiceType;
michael@0 777 aTxn.result.network = aNetwork;
michael@0 778 aTxn.result.start = aStart;
michael@0 779 aTxn.result.end = aEnd;
michael@0 780 aTxn.result.data = data;
michael@0 781 }.bind(this);
michael@0 782 }.bind(this), aResultCb);
michael@0 783 },
michael@0 784
michael@0 785 /*
michael@0 786 * Fill data array (samples from database) with empty samples to match
michael@0 787 * requested start / end dates.
michael@0 788 */
michael@0 789 fillResultSamples: function fillResultSamples(aStart, aEnd, aData) {
michael@0 790 if (aData.length == 0) {
michael@0 791 aData.push({ rxBytes: undefined,
michael@0 792 txBytes: undefined,
michael@0 793 date: new Date(aStart) });
michael@0 794 }
michael@0 795
michael@0 796 while (aStart < aData[0].date.getTime()) {
michael@0 797 aData.unshift({ rxBytes: undefined,
michael@0 798 txBytes: undefined,
michael@0 799 date: new Date(aData[0].date.getTime() - SAMPLE_RATE) });
michael@0 800 }
michael@0 801
michael@0 802 while (aEnd > aData[aData.length - 1].date.getTime()) {
michael@0 803 aData.push({ rxBytes: undefined,
michael@0 804 txBytes: undefined,
michael@0 805 date: new Date(aData[aData.length - 1].date.getTime() + SAMPLE_RATE) });
michael@0 806 }
michael@0 807 },
michael@0 808
michael@0 809 getAvailableNetworks: function getAvailableNetworks(aResultCb) {
michael@0 810 this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
michael@0 811 if (!aTxn.result) {
michael@0 812 aTxn.result = [];
michael@0 813 }
michael@0 814
michael@0 815 let request = aStore.index("network").openKeyCursor(null, "nextunique");
michael@0 816 request.onsuccess = function onsuccess(event) {
michael@0 817 let cursor = event.target.result;
michael@0 818 if (cursor) {
michael@0 819 aTxn.result.push({ id: cursor.key[0],
michael@0 820 type: cursor.key[1] });
michael@0 821 cursor.continue();
michael@0 822 return;
michael@0 823 }
michael@0 824 };
michael@0 825 }, aResultCb);
michael@0 826 },
michael@0 827
michael@0 828 isNetworkAvailable: function isNetworkAvailable(aNetwork, aResultCb) {
michael@0 829 this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
michael@0 830 if (!aTxn.result) {
michael@0 831 aTxn.result = false;
michael@0 832 }
michael@0 833
michael@0 834 let network = [aNetwork.id, aNetwork.type];
michael@0 835 let request = aStore.index("network").openKeyCursor(IDBKeyRange.only(network));
michael@0 836 request.onsuccess = function onsuccess(event) {
michael@0 837 if (event.target.result) {
michael@0 838 aTxn.result = true;
michael@0 839 }
michael@0 840 };
michael@0 841 }, aResultCb);
michael@0 842 },
michael@0 843
michael@0 844 getAvailableServiceTypes: function getAvailableServiceTypes(aResultCb) {
michael@0 845 this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
michael@0 846 if (!aTxn.result) {
michael@0 847 aTxn.result = [];
michael@0 848 }
michael@0 849
michael@0 850 let request = aStore.index("serviceType").openKeyCursor(null, "nextunique");
michael@0 851 request.onsuccess = function onsuccess(event) {
michael@0 852 let cursor = event.target.result;
michael@0 853 if (cursor && cursor.key != "") {
michael@0 854 aTxn.result.push({ serviceType: cursor.key });
michael@0 855 cursor.continue();
michael@0 856 return;
michael@0 857 }
michael@0 858 };
michael@0 859 }, aResultCb);
michael@0 860 },
michael@0 861
michael@0 862 get sampleRate () {
michael@0 863 return SAMPLE_RATE;
michael@0 864 },
michael@0 865
michael@0 866 get maxStorageSamples () {
michael@0 867 return VALUES_MAX_LENGTH;
michael@0 868 },
michael@0 869
michael@0 870 logAllRecords: function logAllRecords(aResultCb) {
michael@0 871 this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
michael@0 872 aStore.mozGetAll().onsuccess = function onsuccess(event) {
michael@0 873 aTxn.result = event.target.result;
michael@0 874 };
michael@0 875 }, aResultCb);
michael@0 876 },
michael@0 877
michael@0 878 alarmToRecord: function alarmToRecord(aAlarm) {
michael@0 879 let record = { networkId: aAlarm.networkId,
michael@0 880 absoluteThreshold: aAlarm.absoluteThreshold,
michael@0 881 relativeThreshold: aAlarm.relativeThreshold,
michael@0 882 startTime: aAlarm.startTime,
michael@0 883 data: aAlarm.data,
michael@0 884 manifestURL: aAlarm.manifestURL,
michael@0 885 pageURL: aAlarm.pageURL };
michael@0 886
michael@0 887 if (aAlarm.id) {
michael@0 888 record.id = aAlarm.id;
michael@0 889 }
michael@0 890
michael@0 891 return record;
michael@0 892 },
michael@0 893
michael@0 894 recordToAlarm: function recordToalarm(aRecord) {
michael@0 895 let alarm = { networkId: aRecord.networkId,
michael@0 896 absoluteThreshold: aRecord.absoluteThreshold,
michael@0 897 relativeThreshold: aRecord.relativeThreshold,
michael@0 898 startTime: aRecord.startTime,
michael@0 899 data: aRecord.data,
michael@0 900 manifestURL: aRecord.manifestURL,
michael@0 901 pageURL: aRecord.pageURL };
michael@0 902
michael@0 903 if (aRecord.id) {
michael@0 904 alarm.id = aRecord.id;
michael@0 905 }
michael@0 906
michael@0 907 return alarm;
michael@0 908 },
michael@0 909
michael@0 910 addAlarm: function addAlarm(aAlarm, aResultCb) {
michael@0 911 this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
michael@0 912 if (DEBUG) {
michael@0 913 debug("Going to add " + JSON.stringify(aAlarm));
michael@0 914 }
michael@0 915
michael@0 916 let record = this.alarmToRecord(aAlarm);
michael@0 917 store.put(record).onsuccess = function setResult(aEvent) {
michael@0 918 txn.result = aEvent.target.result;
michael@0 919 if (DEBUG) {
michael@0 920 debug("Request successful. New record ID: " + txn.result);
michael@0 921 }
michael@0 922 };
michael@0 923 }.bind(this), aResultCb);
michael@0 924 },
michael@0 925
michael@0 926 getFirstAlarm: function getFirstAlarm(aNetworkId, aResultCb) {
michael@0 927 let self = this;
michael@0 928
michael@0 929 this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
michael@0 930 if (DEBUG) {
michael@0 931 debug("Get first alarm for network " + aNetworkId);
michael@0 932 }
michael@0 933
michael@0 934 let lowerFilter = [aNetworkId, 0];
michael@0 935 let upperFilter = [aNetworkId, ""];
michael@0 936 let range = IDBKeyRange.bound(lowerFilter, upperFilter);
michael@0 937
michael@0 938 store.index("alarm").openCursor(range).onsuccess = function onsuccess(event) {
michael@0 939 let cursor = event.target.result;
michael@0 940 txn.result = null;
michael@0 941 if (cursor) {
michael@0 942 txn.result = self.recordToAlarm(cursor.value);
michael@0 943 }
michael@0 944 };
michael@0 945 }, aResultCb);
michael@0 946 },
michael@0 947
michael@0 948 removeAlarm: function removeAlarm(aAlarmId, aManifestURL, aResultCb) {
michael@0 949 this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
michael@0 950 if (DEBUG) {
michael@0 951 debug("Remove alarm " + aAlarmId);
michael@0 952 }
michael@0 953
michael@0 954 store.get(aAlarmId).onsuccess = function onsuccess(event) {
michael@0 955 let record = event.target.result;
michael@0 956 txn.result = false;
michael@0 957 if (!record || (aManifestURL && record.manifestURL != aManifestURL)) {
michael@0 958 return;
michael@0 959 }
michael@0 960
michael@0 961 store.delete(aAlarmId);
michael@0 962 txn.result = true;
michael@0 963 }
michael@0 964 }, aResultCb);
michael@0 965 },
michael@0 966
michael@0 967 removeAlarms: function removeAlarms(aManifestURL, aResultCb) {
michael@0 968 this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
michael@0 969 if (DEBUG) {
michael@0 970 debug("Remove alarms of " + aManifestURL);
michael@0 971 }
michael@0 972
michael@0 973 store.index("manifestURL").openCursor(aManifestURL)
michael@0 974 .onsuccess = function onsuccess(event) {
michael@0 975 let cursor = event.target.result;
michael@0 976 if (cursor) {
michael@0 977 cursor.delete();
michael@0 978 cursor.continue();
michael@0 979 }
michael@0 980 }
michael@0 981 }, aResultCb);
michael@0 982 },
michael@0 983
michael@0 984 updateAlarm: function updateAlarm(aAlarm, aResultCb) {
michael@0 985 let self = this;
michael@0 986 this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
michael@0 987 if (DEBUG) {
michael@0 988 debug("Update alarm " + aAlarm.id);
michael@0 989 }
michael@0 990
michael@0 991 let record = self.alarmToRecord(aAlarm);
michael@0 992 store.openCursor(record.id).onsuccess = function onsuccess(event) {
michael@0 993 let cursor = event.target.result;
michael@0 994 txn.result = false;
michael@0 995 if (cursor) {
michael@0 996 cursor.update(record);
michael@0 997 txn.result = true;
michael@0 998 }
michael@0 999 }
michael@0 1000 }, aResultCb);
michael@0 1001 },
michael@0 1002
michael@0 1003 getAlarms: function getAlarms(aNetworkId, aManifestURL, aResultCb) {
michael@0 1004 let self = this;
michael@0 1005 this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
michael@0 1006 if (DEBUG) {
michael@0 1007 debug("Get alarms for " + aManifestURL);
michael@0 1008 }
michael@0 1009
michael@0 1010 txn.result = [];
michael@0 1011 store.index("manifestURL").openCursor(aManifestURL)
michael@0 1012 .onsuccess = function onsuccess(event) {
michael@0 1013 let cursor = event.target.result;
michael@0 1014 if (!cursor) {
michael@0 1015 return;
michael@0 1016 }
michael@0 1017
michael@0 1018 if (!aNetworkId || cursor.value.networkId == aNetworkId) {
michael@0 1019 txn.result.push(self.recordToAlarm(cursor.value));
michael@0 1020 }
michael@0 1021
michael@0 1022 cursor.continue();
michael@0 1023 }
michael@0 1024 }, aResultCb);
michael@0 1025 },
michael@0 1026
michael@0 1027 _resetAlarms: function _resetAlarms(aNetworkId, aResultCb) {
michael@0 1028 this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
michael@0 1029 if (DEBUG) {
michael@0 1030 debug("Reset alarms for network " + aNetworkId);
michael@0 1031 }
michael@0 1032
michael@0 1033 let lowerFilter = [aNetworkId, 0];
michael@0 1034 let upperFilter = [aNetworkId, ""];
michael@0 1035 let range = IDBKeyRange.bound(lowerFilter, upperFilter);
michael@0 1036
michael@0 1037 store.index("alarm").openCursor(range).onsuccess = function onsuccess(event) {
michael@0 1038 let cursor = event.target.result;
michael@0 1039 if (cursor) {
michael@0 1040 if (cursor.value.startTime) {
michael@0 1041 cursor.value.relativeThreshold = cursor.value.threshold;
michael@0 1042 cursor.update(cursor.value);
michael@0 1043 }
michael@0 1044 cursor.continue();
michael@0 1045 return;
michael@0 1046 }
michael@0 1047 };
michael@0 1048 }, aResultCb);
michael@0 1049 }
michael@0 1050 };

mercurial