dom/datastore/DataStoreCursorImpl.jsm

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

michael@0 1 /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 'use strict'
michael@0 8
michael@0 9 this.EXPORTED_SYMBOLS = ['DataStoreCursor'];
michael@0 10
michael@0 11 function debug(s) {
michael@0 12 //dump('DEBUG DataStoreCursor: ' + s + '\n');
michael@0 13 }
michael@0 14
michael@0 15 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
michael@0 16
michael@0 17 const STATE_INIT = 0;
michael@0 18 const STATE_REVISION_INIT = 1;
michael@0 19 const STATE_REVISION_CHECK = 2;
michael@0 20 const STATE_SEND_ALL = 3;
michael@0 21 const STATE_REVISION_SEND = 4;
michael@0 22 const STATE_DONE = 5;
michael@0 23
michael@0 24 const REVISION_ADDED = 'added';
michael@0 25 const REVISION_UPDATED = 'updated';
michael@0 26 const REVISION_REMOVED = 'removed';
michael@0 27 const REVISION_VOID = 'void';
michael@0 28 const REVISION_SKIP = 'skip'
michael@0 29
michael@0 30 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
michael@0 31
michael@0 32 /**
michael@0 33 * legend:
michael@0 34 * - RID = revision ID
michael@0 35 * - R = revision object (with the internalRevisionId that is a number)
michael@0 36 * - X = current object ID.
michael@0 37 * - L = the list of revisions that we have to send
michael@0 38 *
michael@0 39 * State: init: do you have RID ?
michael@0 40 * YES: state->initRevision; loop
michael@0 41 * NO: get R; X=0; state->sendAll; send a 'clear'
michael@0 42 *
michael@0 43 * State: initRevision. Get R from RID. Done?
michael@0 44 * YES: state->revisionCheck; loop
michael@0 45 * NO: RID = null; state->init; loop
michael@0 46 *
michael@0 47 * State: revisionCheck: get all the revisions between R and NOW. Done?
michael@0 48 * YES and R == NOW: state->done; loop
michael@0 49 * YES and R != NOW: Store this revisions in L; state->revisionSend; loop
michael@0 50 * NO: R = NOW; X=0; state->sendAll; send a 'clear'
michael@0 51 *
michael@0 52 * State: sendAll: is R still the last revision?
michael@0 53 * YES get the first object with id > X. Done?
michael@0 54 * YES: X = object.id; send 'add'
michael@0 55 * NO: state->revisionCheck; loop
michael@0 56 * NO: R = NOW; X=0; send a 'clear'
michael@0 57 *
michael@0 58 * State: revisionSend: do you have something from L to send?
michael@0 59 * YES and L[0] == 'removed': R=L[0]; send 'remove' with ID
michael@0 60 * YES and L[0] == 'added': R=L[0]; get the object; found?
michael@0 61 * NO: loop
michael@0 62 * YES: send 'add' with ID and object
michael@0 63 * YES and L[0] == 'updated': R=L[0]; get the object; found?
michael@0 64 * NO: loop
michael@0 65 * YES and object.R > R: continue
michael@0 66 * YES and object.R <= R: send 'update' with ID and object
michael@0 67 * YES L[0] == 'void': R=L[0]; state->init; loop
michael@0 68 * NO: state->revisionCheck; loop
michael@0 69 *
michael@0 70 * State: done: send a 'done' with R
michael@0 71 */
michael@0 72
michael@0 73 /* Helper functions */
michael@0 74 function createDOMError(aWindow, aEvent) {
michael@0 75 return new aWindow.DOMError(aEvent.target.error.name);
michael@0 76 }
michael@0 77
michael@0 78 /* DataStoreCursor object */
michael@0 79 this.DataStoreCursor = function(aWindow, aDataStore, aRevisionId) {
michael@0 80 debug("DataStoreCursor created");
michael@0 81 this.init(aWindow, aDataStore, aRevisionId);
michael@0 82 }
michael@0 83
michael@0 84 this.DataStoreCursor.prototype = {
michael@0 85 classDescription: 'DataStoreCursor XPCOM Component',
michael@0 86 classID: Components.ID('{b6d14349-1eab-46b8-8513-584a7328a26b}'),
michael@0 87 contractID: '@mozilla.org/dom/datastore-cursor-impl;1',
michael@0 88 QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports]),
michael@0 89
michael@0 90 _window: null,
michael@0 91 _dataStore: null,
michael@0 92 _revisionId: null,
michael@0 93 _revision: null,
michael@0 94 _revisionsList: null,
michael@0 95 _objectId: 0,
michael@0 96
michael@0 97 _state: STATE_INIT,
michael@0 98
michael@0 99 init: function(aWindow, aDataStore, aRevisionId) {
michael@0 100 debug('DataStoreCursor init');
michael@0 101
michael@0 102 this._window = aWindow;
michael@0 103 this._dataStore = aDataStore;
michael@0 104 this._revisionId = aRevisionId;
michael@0 105 },
michael@0 106
michael@0 107 // This is the implementation of the state machine.
michael@0 108 // Read the comments at the top of this file in order to follow what it does.
michael@0 109 stateMachine: function(aStore, aRevisionStore, aResolve, aReject) {
michael@0 110 debug('StateMachine: ' + this._state);
michael@0 111
michael@0 112 switch (this._state) {
michael@0 113 case STATE_INIT:
michael@0 114 this.stateMachineInit(aStore, aRevisionStore, aResolve, aReject);
michael@0 115 break;
michael@0 116
michael@0 117 case STATE_REVISION_INIT:
michael@0 118 this.stateMachineRevisionInit(aStore, aRevisionStore, aResolve, aReject);
michael@0 119 break;
michael@0 120
michael@0 121 case STATE_REVISION_CHECK:
michael@0 122 this.stateMachineRevisionCheck(aStore, aRevisionStore, aResolve, aReject);
michael@0 123 break;
michael@0 124
michael@0 125 case STATE_SEND_ALL:
michael@0 126 this.stateMachineSendAll(aStore, aRevisionStore, aResolve, aReject);
michael@0 127 break;
michael@0 128
michael@0 129 case STATE_REVISION_SEND:
michael@0 130 this.stateMachineRevisionSend(aStore, aRevisionStore, aResolve, aReject);
michael@0 131 break;
michael@0 132
michael@0 133 case STATE_DONE:
michael@0 134 this.stateMachineDone(aStore, aRevisionStore, aResolve, aReject);
michael@0 135 break;
michael@0 136 }
michael@0 137 },
michael@0 138
michael@0 139 stateMachineInit: function(aStore, aRevisionStore, aResolve, aReject) {
michael@0 140 debug('StateMachineInit');
michael@0 141
michael@0 142 if (this._revisionId) {
michael@0 143 this._state = STATE_REVISION_INIT;
michael@0 144 this.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 145 return;
michael@0 146 }
michael@0 147
michael@0 148 let self = this;
michael@0 149 let request = aRevisionStore.openCursor(null, 'prev');
michael@0 150 request.onsuccess = function(aEvent) {
michael@0 151 self._revision = aEvent.target.result.value;
michael@0 152 self._objectId = 0;
michael@0 153 self._state = STATE_SEND_ALL;
michael@0 154 aResolve(Cu.cloneInto({ operation: 'clear' }, self._window));
michael@0 155 }
michael@0 156 },
michael@0 157
michael@0 158 stateMachineRevisionInit: function(aStore, aRevisionStore, aResolve, aReject) {
michael@0 159 debug('StateMachineRevisionInit');
michael@0 160
michael@0 161 let self = this;
michael@0 162 let request = this._dataStore._db.getInternalRevisionId(
michael@0 163 self._revisionId,
michael@0 164 aRevisionStore,
michael@0 165 function(aInternalRevisionId) {
michael@0 166 // This revision doesn't exist.
michael@0 167 if (aInternalRevisionId == undefined) {
michael@0 168 self._revisionId = null;
michael@0 169 self._objectId = 0;
michael@0 170 self._state = STATE_INIT;
michael@0 171 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 172 return;
michael@0 173 }
michael@0 174
michael@0 175 self._revision = { revisionId: self._revisionId,
michael@0 176 internalRevisionId: aInternalRevisionId };
michael@0 177 self._state = STATE_REVISION_CHECK;
michael@0 178 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 179 }
michael@0 180 );
michael@0 181 },
michael@0 182
michael@0 183 stateMachineRevisionCheck: function(aStore, aRevisionStore, aResolve, aReject) {
michael@0 184 debug('StateMachineRevisionCheck');
michael@0 185
michael@0 186 let changes = {
michael@0 187 addedIds: {},
michael@0 188 updatedIds: {},
michael@0 189 removedIds: {}
michael@0 190 };
michael@0 191
michael@0 192 let self = this;
michael@0 193 let request = aRevisionStore.mozGetAll(
michael@0 194 self._window.IDBKeyRange.lowerBound(this._revision.internalRevisionId, true));
michael@0 195 request.onsuccess = function(aEvent) {
michael@0 196
michael@0 197 // Optimize the operations.
michael@0 198 for (let i = 0; i < aEvent.target.result.length; ++i) {
michael@0 199 let data = aEvent.target.result[i];
michael@0 200
michael@0 201 switch (data.operation) {
michael@0 202 case REVISION_ADDED:
michael@0 203 changes.addedIds[data.objectId] = data.internalRevisionId;
michael@0 204 break;
michael@0 205
michael@0 206 case REVISION_UPDATED:
michael@0 207 // We don't consider an update if this object has been added
michael@0 208 // or if it has been already modified by a previous
michael@0 209 // operation.
michael@0 210 if (!(data.objectId in changes.addedIds) &&
michael@0 211 !(data.objectId in changes.updatedIds)) {
michael@0 212 changes.updatedIds[data.objectId] = data.internalRevisionId;
michael@0 213 }
michael@0 214 break;
michael@0 215
michael@0 216 case REVISION_REMOVED:
michael@0 217 let id = data.objectId;
michael@0 218
michael@0 219 // If the object has been added in this range of revisions
michael@0 220 // we can ignore it and remove it from the list.
michael@0 221 if (id in changes.addedIds) {
michael@0 222 delete changes.addedIds[id];
michael@0 223 } else {
michael@0 224 changes.removedIds[id] = data.internalRevisionId;
michael@0 225 }
michael@0 226
michael@0 227 if (id in changes.updatedIds) {
michael@0 228 delete changes.updatedIds[id];
michael@0 229 }
michael@0 230 break;
michael@0 231
michael@0 232 case REVISION_VOID:
michael@0 233 if (i != 0) {
michael@0 234 dump('Internal error: Revision "' + REVISION_VOID + '" should not be found!!!\n');
michael@0 235 return;
michael@0 236 }
michael@0 237
michael@0 238 self._revisionId = null;
michael@0 239 self._objectId = 0;
michael@0 240 self._state = STATE_INIT;
michael@0 241 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 242 return;
michael@0 243 }
michael@0 244 }
michael@0 245
michael@0 246 // From changes to a map of internalRevisionId.
michael@0 247 let revisions = {};
michael@0 248 function addRevisions(obj) {
michael@0 249 for (let key in obj) {
michael@0 250 revisions[obj[key]] = true;
michael@0 251 }
michael@0 252 }
michael@0 253
michael@0 254 addRevisions(changes.addedIds);
michael@0 255 addRevisions(changes.updatedIds);
michael@0 256 addRevisions(changes.removedIds);
michael@0 257
michael@0 258 // Create the list of revisions.
michael@0 259 let list = [];
michael@0 260 for (let i = 0; i < aEvent.target.result.length; ++i) {
michael@0 261 let data = aEvent.target.result[i];
michael@0 262
michael@0 263 // If this revision doesn't contain useful data, we still need to keep
michael@0 264 // it in the list because we need to update the internal revision ID.
michael@0 265 if (!(data.internalRevisionId in revisions)) {
michael@0 266 data.operation = REVISION_SKIP;
michael@0 267 }
michael@0 268
michael@0 269 list.push(data);
michael@0 270 }
michael@0 271
michael@0 272 if (list.length == 0) {
michael@0 273 self._state = STATE_DONE;
michael@0 274 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 275 return;
michael@0 276 }
michael@0 277
michael@0 278 // Some revision has to be sent.
michael@0 279 self._revisionsList = list;
michael@0 280 self._state = STATE_REVISION_SEND;
michael@0 281 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 282 };
michael@0 283 },
michael@0 284
michael@0 285 stateMachineSendAll: function(aStore, aRevisionStore, aResolve, aReject) {
michael@0 286 debug('StateMachineSendAll');
michael@0 287
michael@0 288 let self = this;
michael@0 289 let request = aRevisionStore.openCursor(null, 'prev');
michael@0 290 request.onsuccess = function(aEvent) {
michael@0 291 if (self._revision.revisionId != aEvent.target.result.value.revisionId) {
michael@0 292 self._revision = aEvent.target.result.value;
michael@0 293 self._objectId = 0;
michael@0 294 aResolve(Cu.cloneInto({ operation: 'clear' }, self._window));
michael@0 295 return;
michael@0 296 }
michael@0 297
michael@0 298 let request = aStore.openCursor(self._window.IDBKeyRange.lowerBound(self._objectId, true));
michael@0 299 request.onsuccess = function(aEvent) {
michael@0 300 let cursor = aEvent.target.result;
michael@0 301 if (!cursor) {
michael@0 302 self._state = STATE_REVISION_CHECK;
michael@0 303 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 304 return;
michael@0 305 }
michael@0 306
michael@0 307 self._objectId = cursor.key;
michael@0 308 aResolve(Cu.cloneInto({ operation: 'add', id: self._objectId,
michael@0 309 data: cursor.value }, self._window));
michael@0 310 };
michael@0 311 };
michael@0 312 },
michael@0 313
michael@0 314 stateMachineRevisionSend: function(aStore, aRevisionStore, aResolve, aReject) {
michael@0 315 debug('StateMachineRevisionSend');
michael@0 316
michael@0 317 if (!this._revisionsList.length) {
michael@0 318 this._state = STATE_REVISION_CHECK;
michael@0 319 this.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 320 return;
michael@0 321 }
michael@0 322
michael@0 323 this._revision = this._revisionsList.shift();
michael@0 324
michael@0 325 switch (this._revision.operation) {
michael@0 326 case REVISION_REMOVED:
michael@0 327 aResolve(Cu.cloneInto({ operation: 'remove', id: this._revision.objectId },
michael@0 328 this._window));
michael@0 329 break;
michael@0 330
michael@0 331 case REVISION_ADDED: {
michael@0 332 let request = aStore.get(this._revision.objectId);
michael@0 333 let self = this;
michael@0 334 request.onsuccess = function(aEvent) {
michael@0 335 if (aEvent.target.result == undefined) {
michael@0 336 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 337 return;
michael@0 338 }
michael@0 339
michael@0 340 aResolve(Cu.cloneInto({ operation: 'add', id: self._revision.objectId,
michael@0 341 data: aEvent.target.result }, self._window));
michael@0 342 }
michael@0 343 break;
michael@0 344 }
michael@0 345
michael@0 346 case REVISION_UPDATED: {
michael@0 347 let request = aStore.get(this._revision.objectId);
michael@0 348 let self = this;
michael@0 349 request.onsuccess = function(aEvent) {
michael@0 350 if (aEvent.target.result == undefined) {
michael@0 351 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 352 return;
michael@0 353 }
michael@0 354
michael@0 355 if (aEvent.target.result.revisionId > self._revision.internalRevisionId) {
michael@0 356 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 357 return;
michael@0 358 }
michael@0 359
michael@0 360 aResolve(Cu.cloneInto({ operation: 'update', id: self._revision.objectId,
michael@0 361 data: aEvent.target.result }, self._window));
michael@0 362 }
michael@0 363 break;
michael@0 364 }
michael@0 365
michael@0 366 case REVISION_VOID:
michael@0 367 // Internal error!
michael@0 368 dump('Internal error: Revision "' + REVISION_VOID + '" should not be found!!!\n');
michael@0 369 break;
michael@0 370
michael@0 371 case REVISION_SKIP:
michael@0 372 // This revision contains data that has already been sent by another one.
michael@0 373 this.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 374 break;
michael@0 375 }
michael@0 376 },
michael@0 377
michael@0 378 stateMachineDone: function(aStore, aRevisionStore, aResolve, aReject) {
michael@0 379 this.close();
michael@0 380 aResolve(Cu.cloneInto({ revisionId: this._revision.revisionId,
michael@0 381 operation: 'done' }, this._window));
michael@0 382 },
michael@0 383
michael@0 384 // public interface
michael@0 385
michael@0 386 get store() {
michael@0 387 return this._dataStore.exposedObject;
michael@0 388 },
michael@0 389
michael@0 390 next: function() {
michael@0 391 debug('Next');
michael@0 392
michael@0 393 let self = this;
michael@0 394 return new this._window.Promise(function(aResolve, aReject) {
michael@0 395 self._dataStore._db.cursorTxn(
michael@0 396 function(aTxn, aStore, aRevisionStore) {
michael@0 397 self.stateMachine(aStore, aRevisionStore, aResolve, aReject);
michael@0 398 },
michael@0 399 function(aEvent) {
michael@0 400 aReject(createDOMError(self._window, aEvent));
michael@0 401 }
michael@0 402 );
michael@0 403 });
michael@0 404 },
michael@0 405
michael@0 406 close: function() {
michael@0 407 this._dataStore.syncTerminated(this);
michael@0 408 }
michael@0 409 };

mercurial