dom/datastore/DataStoreCursorImpl.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

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

mercurial