| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <title>JSDoc: Source: cache.js</title> |
| |
| <script src="scripts/prettify/prettify.js"> </script> |
| <script src="scripts/prettify/lang-css.js"> </script> |
| <!--[if lt IE 9]> |
| <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> |
| <![endif]--> |
| <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> |
| <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> |
| </head> |
| |
| <body> |
| |
| <div id="main"> |
| |
| <h1 class="page-title">Source: cache.js</h1> |
| |
| |
| |
| |
| |
| <section> |
| <article> |
| <pre class="prettyprint source"><code>/*
|
| * Licensed to the Apache Software Foundation (ASF) under one
|
| * or more contributor license agreements. See the NOTICE file
|
| * distributed with this work for additional information
|
| * regarding copyright ownership. The ASF licenses this file
|
| * to you under the Apache License, Version 2.0 (the
|
| * "License"); you may not use this file except in compliance
|
| * with the License. You may obtain a copy of the License at
|
| *
|
| * http://www.apache.org/licenses/LICENSE-2.0
|
| *
|
| * Unless required by applicable law or agreed to in writing,
|
| * software distributed under the License is distributed on an
|
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
| * KIND, either express or implied. See the License for the
|
| * specific language governing permissions and limitations
|
| * under the License.
|
| */
|
| 'use strict';
|
|
|
| /** @module cache */
|
|
|
| //var odatajs = require('./odatajs/utils.js');
|
| var utils = require('./utils.js');
|
| var deferred = require('./deferred.js');
|
| var storeReq = require('./store.js');
|
| var cacheSource = require('./cache/source.js');
|
|
|
|
|
| var assigned = utils.assigned;
|
| var delay = utils.delay;
|
| var extend = utils.extend;
|
| var djsassert = utils.djsassert;
|
| var isArray = utils.isArray;
|
| var normalizeURI = utils.normalizeURI;
|
| var parseInt10 = utils.parseInt10;
|
| var undefinedDefault = utils.undefinedDefault;
|
|
|
| var createDeferred = deferred.createDeferred;
|
| var DjsDeferred = deferred.DjsDeferred;
|
|
|
|
|
| var getJsonValueArraryLength = utils.getJsonValueArraryLength;
|
| var sliceJsonValueArray = utils.sliceJsonValueArray;
|
| var concatJsonValueArray = utils.concatJsonValueArray;
|
|
|
|
|
|
|
| /** Appends a page's data to the operation data.
|
| * @param {Object} operation - Operation with (i)ndex, (c)ount and (d)ata.
|
| * @param {Object} page - Page with (i)ndex, (c)ount and (d)ata.
|
| */
|
| function appendPage(operation, page) {
|
|
|
| var intersection = intersectRanges(operation, page);
|
| var start = 0;
|
| var end = 0;
|
| if (intersection) {
|
| start = intersection.i - page.i;
|
| end = start + (operation.c - getJsonValueArraryLength(operation.d));
|
| }
|
|
|
| operation.d = concatJsonValueArray(operation.d, sliceJsonValueArray(page.d, start, end));
|
| }
|
|
|
| /** Returns the {(i)ndex, (c)ount} range for the intersection of x and y.
|
| * @param {Object} x - Range with (i)ndex and (c)ount members.
|
| * @param {Object} y - Range with (i)ndex and (c)ount members.
|
| * @returns {Object} The intersection (i)ndex and (c)ount; undefined if there is no intersection.
|
| */
|
| function intersectRanges(x, y) {
|
|
|
| var xLast = x.i + x.c;
|
| var yLast = y.i + y.c;
|
| var resultIndex = (x.i > y.i) ? x.i : y.i;
|
| var resultLast = (xLast < yLast) ? xLast : yLast;
|
| var result;
|
| if (resultLast >= resultIndex) {
|
| result = { i: resultIndex, c: resultLast - resultIndex };
|
| }
|
|
|
| return result;
|
| }
|
|
|
| /** Checks whether val is a defined number with value zero or greater.
|
| * @param {Number} val - Value to check.
|
| * @param {String} name - Parameter name to use in exception.
|
| * @throws Throws an exception if the check fails
|
| */
|
| function checkZeroGreater(val, name) {
|
|
|
| if (val === undefined || typeof val !== "number") {
|
| throw { message: "'" + name + "' must be a number." };
|
| }
|
|
|
| if (isNaN(val) || val < 0 || !isFinite(val)) {
|
| throw { message: "'" + name + "' must be greater than or equal to zero." };
|
| }
|
| }
|
|
|
| /** Checks whether val is undefined or a number with value greater than zero.
|
| * @param {Number} val - Value to check.
|
| * @param {String} name - Parameter name to use in exception.
|
| * @throws Throws an exception if the check fails
|
| */
|
| function checkUndefinedGreaterThanZero(val, name) {
|
|
|
| if (val !== undefined) {
|
| if (typeof val !== "number") {
|
| throw { message: "'" + name + "' must be a number." };
|
| }
|
|
|
| if (isNaN(val) || val <= 0 || !isFinite(val)) {
|
| throw { message: "'" + name + "' must be greater than zero." };
|
| }
|
| }
|
| }
|
|
|
| /** Checks whether val is undefined or a number
|
| * @param {Number} val - Value to check.
|
| * @param {String} name - Parameter name to use in exception.
|
| * @throws Throws an exception if the check fails
|
| */
|
| function checkUndefinedOrNumber(val, name) {
|
| if (val !== undefined && (typeof val !== "number" || isNaN(val) || !isFinite(val))) {
|
| throw { message: "'" + name + "' must be a number." };
|
| }
|
| }
|
|
|
| /** Performs a linear search on the specified array and removes the first instance of 'item'.
|
| * @param {Array} arr - Array to search.
|
| * @param {*} item - Item being sought.
|
| * @returns {Boolean} true if the item was removed otherwise false
|
| */
|
| function removeFromArray(arr, item) {
|
|
|
| var i, len;
|
| for (i = 0, len = arr.length; i < len; i++) {
|
| if (arr[i] === item) {
|
| arr.splice(i, 1);
|
| return true;
|
| }
|
| }
|
|
|
| return false;
|
| }
|
|
|
| /** Estimates the size of an object in bytes.
|
| * Object trees are traversed recursively
|
| * @param {Object} object - Object to determine the size of.
|
| * @returns {Number} Estimated size of the object in bytes.
|
| */
|
| function estimateSize(object) {
|
| var size = 0;
|
| var type = typeof object;
|
|
|
| if (type === "object" && object) {
|
| for (var name in object) {
|
| size += name.length * 2 + estimateSize(object[name]);
|
| }
|
| } else if (type === "string") {
|
| size = object.length * 2;
|
| } else {
|
| size = 8;
|
| }
|
| return size;
|
| }
|
|
|
| /** Snaps low and high indices into page sizes and returns a range.
|
| * @param {Number} lowIndex - Low index to snap to a lower value.
|
| * @param {Number} highIndex - High index to snap to a higher value.
|
| * @param {Number} pageSize - Page size to snap to.
|
| * @returns {Object} A range with (i)ndex and (c)ount of elements.
|
| */
|
| function snapToPageBoundaries(lowIndex, highIndex, pageSize) {
|
| lowIndex = Math.floor(lowIndex / pageSize) * pageSize;
|
| highIndex = Math.ceil((highIndex + 1) / pageSize) * pageSize;
|
| return { i: lowIndex, c: highIndex - lowIndex };
|
| }
|
|
|
| // The DataCache is implemented using state machines. The following constants are used to properly
|
| // identify and label the states that these machines transition to.
|
| var CACHE_STATE_DESTROY = "destroy";
|
| var CACHE_STATE_IDLE = "idle";
|
| var CACHE_STATE_INIT = "init";
|
| var CACHE_STATE_READ = "read";
|
| var CACHE_STATE_PREFETCH = "prefetch";
|
| var CACHE_STATE_WRITE = "write";
|
|
|
| // DataCacheOperation state machine states.
|
| // Transitions on operations also depend on the cache current of the cache.
|
| var OPERATION_STATE_CANCEL = "cancel";
|
| var OPERATION_STATE_END = "end";
|
| var OPERATION_STATE_ERROR = "error";
|
| var OPERATION_STATE_START = "start";
|
| var OPERATION_STATE_WAIT = "wait";
|
|
|
| // Destroy state machine states
|
| var DESTROY_STATE_CLEAR = "clear";
|
|
|
| // Read / Prefetch state machine states
|
| var READ_STATE_DONE = "done";
|
| var READ_STATE_LOCAL = "local";
|
| var READ_STATE_SAVE = "save";
|
| var READ_STATE_SOURCE = "source";
|
|
|
| /** Creates a new operation object.
|
| * @class DataCacheOperation
|
| * @param {Function} stateMachine - State machine that describes the specific behavior of the operation.
|
| * @param {DjsDeferred} promise - Promise for requested values.
|
| * @param {Boolean} isCancelable - Whether this operation can be canceled or not.
|
| * @param {Number} index - Index of first item requested.
|
| * @param {Number} count - Count of items requested.
|
| * @param {Array} data - Array with the items requested by the operation.
|
| * @param {Number} pending - Total number of pending prefetch records.
|
| * @returns {DataCacheOperation} A new data cache operation instance.
|
| */
|
| function DataCacheOperation(stateMachine, promise, isCancelable, index, count, data, pending) {
|
|
|
| var stateData;
|
| var cacheState;
|
| var that = this;
|
|
|
| that.p = promise;
|
| that.i = index;
|
| that.c = count;
|
| that.d = data;
|
| that.s = OPERATION_STATE_START;
|
|
|
| that.canceled = false;
|
| that.pending = pending;
|
| that.oncomplete = null;
|
|
|
| /** Transitions this operation to the cancel state and sets the canceled flag to true.
|
| * The function is a no-op if the operation is non-cancelable.
|
| * @method DataCacheOperation#cancel
|
| */
|
| that.cancel = function cancel() {
|
|
|
| if (!isCancelable) {
|
| return;
|
| }
|
|
|
| var state = that.s;
|
| if (state !== OPERATION_STATE_ERROR && state !== OPERATION_STATE_END && state !== OPERATION_STATE_CANCEL) {
|
| that.canceled = true;
|
| that.transition(OPERATION_STATE_CANCEL, stateData);
|
| }
|
| };
|
|
|
| /** Transitions this operation to the end state.
|
| * @method DataCacheOperation#complete
|
| */
|
| that.complete = function () {
|
|
|
| djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.complete() - operation is in the end state", that);
|
| that.transition(OPERATION_STATE_END, stateData);
|
| };
|
|
|
| /** Transitions this operation to the error state.
|
| * @method DataCacheOperation#error
|
| */
|
| that.error = function (err) {
|
| if (!that.canceled) {
|
| djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.error() - operation is in the end state", that);
|
| djsassert(that.s !== OPERATION_STATE_ERROR, "DataCacheOperation.error() - operation is in the error state", that);
|
| that.transition(OPERATION_STATE_ERROR, err);
|
| }
|
| };
|
|
|
| /** Executes the operation's current state in the context of a new cache state.
|
| * @method DataCacheOperation#run
|
| * @param {Object} state - New cache state.
|
| */
|
| that.run = function (state) {
|
|
|
| cacheState = state;
|
| that.transition(that.s, stateData);
|
| };
|
|
|
| /** Transitions this operation to the wait state.
|
| * @method DataCacheOperation#wait
|
| */
|
| that.wait = function (data) {
|
|
|
| djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.wait() - operation is in the end state", that);
|
| that.transition(OPERATION_STATE_WAIT, data);
|
| };
|
|
|
| /** State machine that describes all operations common behavior.
|
| * @method DataCacheOperation#operationStateMachine
|
| * @param {Object} opTargetState - Operation state to transition to.
|
| * @param {Object} cacheState - Current cache state.
|
| * @param {Object} [data] - Additional data passed to the state.
|
| */
|
| var operationStateMachine = function (opTargetState, cacheState, data) {
|
|
|
| switch (opTargetState) {
|
| case OPERATION_STATE_START:
|
| // Initial state of the operation. The operation will remain in this state until the cache has been fully initialized.
|
| if (cacheState !== CACHE_STATE_INIT) {
|
| stateMachine(that, opTargetState, cacheState, data);
|
| }
|
| break;
|
|
|
| case OPERATION_STATE_WAIT:
|
| // Wait state indicating that the operation is active but waiting for an asynchronous operation to complete.
|
| stateMachine(that, opTargetState, cacheState, data);
|
| break;
|
|
|
| case OPERATION_STATE_CANCEL:
|
| // Cancel state.
|
| stateMachine(that, opTargetState, cacheState, data);
|
| that.fireCanceled();
|
| that.transition(OPERATION_STATE_END);
|
| break;
|
|
|
| case OPERATION_STATE_ERROR:
|
| // Error state. Data is expected to be an object detailing the error condition.
|
| stateMachine(that, opTargetState, cacheState, data);
|
| that.canceled = true;
|
| that.fireRejected(data);
|
| that.transition(OPERATION_STATE_END);
|
| break;
|
|
|
| case OPERATION_STATE_END:
|
| // Final state of the operation.
|
| if (that.oncomplete) {
|
| that.oncomplete(that);
|
| }
|
| if (!that.canceled) {
|
| that.fireResolved();
|
| }
|
| stateMachine(that, opTargetState, cacheState, data);
|
| break;
|
|
|
| default:
|
| // Any other state is passed down to the state machine describing the operation's specific behavior.
|
|
|
| if (true) {
|
| // Check that the state machine actually handled the sate.
|
| var handled = stateMachine(that, opTargetState, cacheState, data);
|
| djsassert(handled, "Bad operation state: " + opTargetState + " cacheState: " + cacheState, this);
|
| } else {
|
|
|
| stateMachine(that, opTargetState, cacheState, data);
|
|
|
| }
|
|
|
| break;
|
| }
|
| };
|
|
|
|
|
|
|
| /** Transitions this operation to a new state.
|
| * @method DataCacheOperation#transition
|
| * @param {Object} state - State to transition the operation to.
|
| * @param {Object} [data] -
|
| */
|
| that.transition = function (state, data) {
|
| that.s = state;
|
| stateData = data;
|
| operationStateMachine(state, cacheState, data);
|
| };
|
|
|
| return that;
|
| }
|
|
|
| /** Fires a resolved notification as necessary.
|
| * @method DataCacheOperation#fireResolved
|
| */
|
| DataCacheOperation.prototype.fireResolved = function () {
|
|
|
| // Fire the resolve just once.
|
| var p = this.p;
|
| if (p) {
|
| this.p = null;
|
| p.resolve(this.d);
|
| }
|
| };
|
|
|
| /** Fires a rejected notification as necessary.
|
| * @method DataCacheOperation#fireRejected
|
| */
|
| DataCacheOperation.prototype.fireRejected = function (reason) {
|
|
|
| // Fire the rejection just once.
|
| var p = this.p;
|
| if (p) {
|
| this.p = null;
|
| p.reject(reason);
|
| }
|
| };
|
|
|
| /** Fires a canceled notification as necessary.
|
| * @method DataCacheOperation#fireCanceled
|
| */
|
| DataCacheOperation.prototype.fireCanceled = function () {
|
|
|
| this.fireRejected({ canceled: true, message: "Operation canceled" });
|
| };
|
|
|
|
|
| /** Creates a data cache for a collection that is efficiently loaded on-demand.
|
| * @class DataCache
|
| * @param options - Options for the data cache, including name, source, pageSize,
|
| * prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
|
| * @returns {DataCache} A new data cache instance.
|
| */
|
| function DataCache(options) {
|
|
|
| var state = CACHE_STATE_INIT;
|
| var stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
|
|
|
| var clearOperations = [];
|
| var readOperations = [];
|
| var prefetchOperations = [];
|
|
|
| var actualCacheSize = 0; // Actual cache size in bytes.
|
| var allDataLocal = false; // Whether all data is local.
|
| var cacheSize = undefinedDefault(options.cacheSize, 1048576); // Requested cache size in bytes, default 1 MB.
|
| var collectionCount = 0; // Number of elements in the server collection.
|
| var highestSavedPage = 0; // Highest index of all the saved pages.
|
| var highestSavedPageSize = 0; // Item count of the saved page with the highest index.
|
| var overflowed = cacheSize === 0; // If the cache has overflowed (actualCacheSize > cacheSize or cacheSize == 0);
|
| var pageSize = undefinedDefault(options.pageSize, 50); // Number of elements to store per page.
|
| var prefetchSize = undefinedDefault(options.prefetchSize, pageSize); // Number of elements to prefetch from the source when the cache is idling.
|
| var version = "1.0";
|
| var cacheFailure;
|
|
|
| var pendingOperations = 0;
|
|
|
| var source = options.source;
|
| if (typeof source === "string") {
|
| // Create a new cache source.
|
| source = new cacheSource.ODataCacheSource(options);
|
| }
|
| source.options = options;
|
|
|
| // Create a cache local store.
|
| var store = storeReq.createStore(options.name, options.mechanism);
|
|
|
| var that = this;
|
|
|
| that.onidle = options.idle;
|
| that.stats = stats;
|
|
|
| /** Counts the number of items in the collection.
|
| * @method DataCache#count
|
| * @returns {Object} A promise with the number of items.
|
| */
|
| that.count = function () {
|
|
|
| if (cacheFailure) {
|
| throw cacheFailure;
|
| }
|
|
|
| var deferred = createDeferred();
|
| var canceled = false;
|
|
|
| if (allDataLocal) {
|
| delay(function () {
|
| deferred.resolve(collectionCount);
|
| });
|
|
|
| return deferred.promise();
|
| }
|
|
|
| // TODO: Consider returning the local data count instead once allDataLocal flag is set to true.
|
| var request = source.count(function (count) {
|
| request = null;
|
| stats.counts++;
|
| deferred.resolve(count);
|
| }, function (err) {
|
| request = null;
|
| deferred.reject(extend(err, { canceled: canceled }));
|
| });
|
|
|
| return extend(deferred.promise(), {
|
|
|
| /** Aborts the count operation (used within promise callback)
|
| * @method DataCache#cancelCount
|
| */
|
| cancel: function () {
|
|
|
| if (request) {
|
| canceled = true;
|
| request.abort();
|
| request = null;
|
| }
|
| }
|
| });
|
| };
|
|
|
| /** Cancels all running operations and clears all local data associated with this cache.
|
| * New read requests made while a clear operation is in progress will not be canceled.
|
| * Instead they will be queued for execution once the operation is completed.
|
| * @method DataCache#clear
|
| * @returns {Object} A promise that has no value and can't be canceled.
|
| */
|
| that.clear = function () {
|
|
|
| if (cacheFailure) {
|
| throw cacheFailure;
|
| }
|
|
|
| if (clearOperations.length === 0) {
|
| var deferred = createDeferred();
|
| var op = new DataCacheOperation(destroyStateMachine, deferred, false);
|
| queueAndStart(op, clearOperations);
|
| return deferred.promise();
|
| }
|
| return clearOperations[0].p;
|
| };
|
|
|
| /** Filters the cache data based a predicate.
|
| * Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
|
| * @method DataCache#filterForward
|
| * @param {Number} index - The index of the item to start filtering forward from.
|
| * @param {Number} count - Maximum number of items to include in the result.
|
| * @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
|
| * @returns {DjsDeferred} A promise for an array of results.
|
| */
|
| that.filterForward = function (index, count, predicate) {
|
| return filter(index, count, predicate, false);
|
| };
|
|
|
| /** Filters the cache data based a predicate.
|
| * Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
|
| * @method DataCache#filterBack
|
| * @param {Number} index - The index of the item to start filtering backward from.
|
| * @param {Number} count - Maximum number of items to include in the result.
|
| * @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
|
| * @returns {DjsDeferred} A promise for an array of results.
|
| */
|
| that.filterBack = function (index, count, predicate) {
|
| return filter(index, count, predicate, true);
|
| };
|
|
|
| /** Reads a range of adjacent records.
|
| * New read requests made while a clear operation is in progress will not be canceled.
|
| * Instead they will be queued for execution once the operation is completed.
|
| * @method DataCache#readRange
|
| * @param {Number} index - Zero-based index of record range to read.
|
| * @param {Number} count - Number of records in the range.
|
| * @returns {DjsDeferred} A promise for an array of records; less records may be returned if the
|
| * end of the collection is found.
|
| */
|
| that.readRange = function (index, count) {
|
|
|
| checkZeroGreater(index, "index");
|
| checkZeroGreater(count, "count");
|
|
|
| if (cacheFailure) {
|
| throw cacheFailure;
|
| }
|
|
|
| var deferred = createDeferred();
|
|
|
| // Merging read operations would be a nice optimization here.
|
| var op = new DataCacheOperation(readStateMachine, deferred, true, index, count, {}, 0);
|
| queueAndStart(op, readOperations);
|
|
|
| return extend(deferred.promise(), {
|
| cancel: function () {
|
| /** Aborts the readRange operation (used within promise callback)
|
| * @method DataCache#cancelReadRange
|
| */
|
| op.cancel();
|
| }
|
| });
|
| };
|
|
|
| /** Creates an Observable object that enumerates all the cache contents.
|
| * @method DataCache#toObservable
|
| * @returns A new Observable object that enumerates all the cache contents.
|
| */
|
| that.ToObservable = that.toObservable = function () {
|
| if ( !utils.inBrowser()) {
|
| throw { message: "Only in broser supported" };
|
| }
|
|
|
| if (!window.Rx || !window.Rx.Observable) {
|
| throw { message: "Rx library not available - include rx.js" };
|
| }
|
|
|
| if (cacheFailure) {
|
| throw cacheFailure;
|
| }
|
|
|
| //return window.Rx.Observable.create(function (obs) {
|
| return new window.Rx.Observable(function (obs) {
|
| var disposed = false;
|
| var index = 0;
|
|
|
| var errorCallback = function (error) {
|
| if (!disposed) {
|
| obs.onError(error);
|
| }
|
| };
|
|
|
| var successCallback = function (data) {
|
| if (!disposed) {
|
| var i, len;
|
| for (i = 0, len = data.value.length; i < len; i++) {
|
| // The wrapper automatically checks for Dispose
|
| // on the observer, so we don't need to check it here.
|
| //obs.next(data.value[i]);
|
| obs.onNext(data.value[i]);
|
| }
|
|
|
| if (data.value.length < pageSize) {
|
| //obs.completed();
|
| obs.onCompleted();
|
| } else {
|
| index += pageSize;
|
| that.readRange(index, pageSize).then(successCallback, errorCallback);
|
| }
|
| }
|
| };
|
|
|
| that.readRange(index, pageSize).then(successCallback, errorCallback);
|
|
|
| return { Dispose: function () {
|
| obs.dispose(); // otherwise the check isStopped obs.onNext(data.value[i]);
|
| disposed = true;
|
| } };
|
| });
|
| };
|
|
|
| /** Creates a function that handles a callback by setting the cache into failure mode.
|
| * @method DataCache~cacheFailureCallback
|
| * @param {String} message - Message text.
|
| * @returns {Function} Function to use as error callback.
|
| * This function will specifically handle problems with critical store resources
|
| * during cache initialization.
|
| */
|
| var cacheFailureCallback = function (message) {
|
|
|
|
|
| return function (error) {
|
| cacheFailure = { message: message, error: error };
|
|
|
| // Destroy any pending clear or read operations.
|
| // At this point there should be no prefetch operations.
|
| // Count operations will go through but are benign because they
|
| // won't interact with the store.
|
| djsassert(prefetchOperations.length === 0, "prefetchOperations.length === 0");
|
| var i, len;
|
| for (i = 0, len = readOperations.length; i < len; i++) {
|
| readOperations[i].fireRejected(cacheFailure);
|
| }
|
| for (i = 0, len = clearOperations.length; i < len; i++) {
|
| clearOperations[i].fireRejected(cacheFailure);
|
| }
|
|
|
| // Null out the operation arrays.
|
| readOperations = clearOperations = null;
|
| };
|
| };
|
|
|
| /** Updates the cache's state and signals all pending operations of the change.
|
| * @method DataCache~changeState
|
| * @param {Object} newState - New cache state.
|
| * This method is a no-op if the cache's current state and the new state are the same.
|
| */
|
| var changeState = function (newState) {
|
|
|
| if (newState !== state) {
|
| state = newState;
|
| var operations = clearOperations.concat(readOperations, prefetchOperations);
|
| var i, len;
|
| for (i = 0, len = operations.length; i < len; i++) {
|
| operations[i].run(state);
|
| }
|
| }
|
| };
|
|
|
| /** Removes all the data stored in the cache.
|
| * @method DataCache~clearStore
|
| * @returns {DjsDeferred} A promise with no value.
|
| */
|
| var clearStore = function () {
|
| djsassert(state === CACHE_STATE_DESTROY || state === CACHE_STATE_INIT, "DataCache.clearStore() - cache is not on the destroy or initialize state, current sate = " + state);
|
|
|
| var deferred = new DjsDeferred();
|
| store.clear(function () {
|
|
|
| // Reset the cache settings.
|
| actualCacheSize = 0;
|
| allDataLocal = false;
|
| collectionCount = 0;
|
| highestSavedPage = 0;
|
| highestSavedPageSize = 0;
|
| overflowed = cacheSize === 0;
|
|
|
| // version is not reset, in case there is other state in eg V1.1 that is still around.
|
|
|
| // Reset the cache stats.
|
| stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
|
| that.stats = stats;
|
|
|
| store.close();
|
| deferred.resolve();
|
| }, function (err) {
|
| deferred.reject(err);
|
| });
|
| return deferred;
|
| };
|
|
|
| /** Removes an operation from the caches queues and changes the cache state to idle.
|
| * @method DataCache~dequeueOperation
|
| * @param {DataCacheOperation} operation - Operation to dequeue.
|
| * This method is used as a handler for the operation's oncomplete event.
|
| */
|
| var dequeueOperation = function (operation) {
|
|
|
| var removed = removeFromArray(clearOperations, operation);
|
| if (!removed) {
|
| removed = removeFromArray(readOperations, operation);
|
| if (!removed) {
|
| removeFromArray(prefetchOperations, operation);
|
| }
|
| }
|
|
|
| pendingOperations--;
|
| changeState(CACHE_STATE_IDLE);
|
| };
|
|
|
| /** Requests data from the cache source.
|
| * @method DataCache~fetchPage
|
| * @param {Number} start - Zero-based index of items to request.
|
| * @returns {DjsDeferred} A promise for a page object with (i)ndex, (c)ount, (d)ata.
|
| */
|
| var fetchPage = function (start) {
|
|
|
| djsassert(state !== CACHE_STATE_DESTROY, "DataCache.fetchPage() - cache is on the destroy state");
|
| djsassert(state !== CACHE_STATE_IDLE, "DataCache.fetchPage() - cache is on the idle state");
|
|
|
| var deferred = new DjsDeferred();
|
| var canceled = false;
|
|
|
| var request = source.read(start, pageSize, function (data) {
|
| var length = getJsonValueArraryLength(data);
|
| var page = { i: start, c: length, d: data };
|
| deferred.resolve(page);
|
| }, function (err) {
|
| deferred.reject(err);
|
| });
|
|
|
| return extend(deferred, {
|
| cancel: function () {
|
| if (request) {
|
| request.abort();
|
| canceled = true;
|
| request = null;
|
| }
|
| }
|
| });
|
| };
|
|
|
| /** Filters the cache data based a predicate.
|
| * @method DataCache~filter
|
| * @param {Number} index - The index of the item to start filtering from.
|
| * @param {Number} count - Maximum number of items to include in the result.
|
| * @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
|
| * @param {Boolean} backwards - True if the filtering should move backward from the specified index, falsey otherwise.
|
| * Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
|
| * @returns {DjsDeferred} A promise for an array of results.
|
| */
|
| var filter = function (index, count, predicate, backwards) {
|
|
|
| index = parseInt10(index);
|
| count = parseInt10(count);
|
|
|
| if (isNaN(index)) {
|
| throw { message: "'index' must be a valid number.", index: index };
|
| }
|
| if (isNaN(count)) {
|
| throw { message: "'count' must be a valid number.", count: count };
|
| }
|
|
|
| if (cacheFailure) {
|
| throw cacheFailure;
|
| }
|
|
|
| index = Math.max(index, 0);
|
|
|
| var deferred = createDeferred();
|
| var returnData = {};
|
| returnData.value = [];
|
| var canceled = false;
|
| var pendingReadRange = null;
|
|
|
| var readMore = function (readIndex, readCount) {
|
| if (!canceled) {
|
| if (count > 0 && returnData.value.length >= count) {
|
| deferred.resolve(returnData);
|
| } else {
|
| pendingReadRange = that.readRange(readIndex, readCount).then(function (data) {
|
| if (data["@odata.context"] && !returnData["@odata.context"]) {
|
| returnData["@odata.context"] = data["@odata.context"];
|
| }
|
|
|
| for (var i = 0, length = data.value.length; i < length && (count < 0 || returnData.value.length < count); i++) {
|
| var dataIndex = backwards ? length - i - 1 : i;
|
| var item = data.value[dataIndex];
|
| if (predicate(item)) {
|
| var element = {
|
| index: readIndex + dataIndex,
|
| item: item
|
| };
|
|
|
| backwards ? returnData.value.unshift(element) : returnData.value.push(element);
|
| }
|
| }
|
|
|
| // Have we reached the end of the collection?
|
| if ((!backwards && data.value.length < readCount) || (backwards && readIndex <= 0)) {
|
| deferred.resolve(returnData);
|
| } else {
|
| var nextIndex = backwards ? Math.max(readIndex - pageSize, 0) : readIndex + readCount;
|
| readMore(nextIndex, pageSize);
|
| }
|
| }, function (err) {
|
| deferred.reject(err);
|
| });
|
| }
|
| }
|
| };
|
|
|
| // Initially, we read from the given starting index to the next/previous page boundary
|
| var initialPage = snapToPageBoundaries(index, index, pageSize);
|
| var initialIndex = backwards ? initialPage.i : index;
|
| var initialCount = backwards ? index - initialPage.i + 1 : initialPage.i + initialPage.c - index;
|
| readMore(initialIndex, initialCount);
|
|
|
| return extend(deferred.promise(), {
|
| /** Aborts the filter operation (used within promise callback)
|
| * @method DataCache#cancelFilter
|
| */
|
| cancel: function () {
|
|
|
| if (pendingReadRange) {
|
| pendingReadRange.cancel();
|
| }
|
| canceled = true;
|
| }
|
| });
|
| };
|
|
|
| /** Fires an onidle event if any functions are assigned.
|
| * @method DataCache~fireOnIdle
|
| */
|
| var fireOnIdle = function () {
|
|
|
| if (that.onidle && pendingOperations === 0) {
|
| that.onidle();
|
| }
|
| };
|
|
|
| /** Creates and starts a new prefetch operation.
|
| * @method DataCache~prefetch
|
| * @param {Number} start - Zero-based index of the items to prefetch.
|
| * This method is a no-op if any of the following conditions is true:
|
| * 1.- prefetchSize is 0
|
| * 2.- All data has been read and stored locally in the cache.
|
| * 3.- There is already an all data prefetch operation queued.
|
| * 4.- The cache has run out of available space (overflowed).
|
| */
|
| var prefetch = function (start) {
|
|
|
|
|
| if (allDataLocal || prefetchSize === 0 || overflowed) {
|
| return;
|
| }
|
|
|
| djsassert(state === CACHE_STATE_READ, "DataCache.prefetch() - cache is not on the read state, current state: " + state);
|
|
|
| if (prefetchOperations.length === 0 || (prefetchOperations[0] && prefetchOperations[0].c !== -1)) {
|
| // Merging prefetch operations would be a nice optimization here.
|
| var op = new DataCacheOperation(prefetchStateMachine, null, true, start, prefetchSize, null, prefetchSize);
|
| queueAndStart(op, prefetchOperations);
|
| }
|
| };
|
|
|
| /** Queues an operation and runs it.
|
| * @param {DataCacheOperation} op - Operation to queue.
|
| * @param {Array} queue - Array that will store the operation.
|
| */
|
| var queueAndStart = function (op, queue) {
|
|
|
| op.oncomplete = dequeueOperation;
|
| queue.push(op);
|
| pendingOperations++;
|
| op.run(state);
|
| };
|
|
|
| /** Requests a page from the cache local store.
|
| * @method DataCache~readPage
|
| * @param {Number} key - Zero-based index of the reuqested page.
|
| * @returns {DjsDeferred} A promise for a found flag and page object with (i)ndex, (c)ount, (d)ata, and (t)icks.
|
| */
|
| var readPage = function (key) {
|
|
|
| djsassert(state !== CACHE_STATE_DESTROY, "DataCache.readPage() - cache is on the destroy state");
|
|
|
| var canceled = false;
|
| var deferred = extend(new DjsDeferred(), {
|
| /** Aborts the readPage operation. (used within promise callback)
|
| * @method DataCache#cancelReadPage
|
| */
|
| cancel: function () {
|
| canceled = true;
|
| }
|
| });
|
|
|
| var error = storeFailureCallback(deferred, "Read page from store failure");
|
|
|
| store.contains(key, function (contained) {
|
| if (canceled) {
|
| return;
|
| }
|
| if (contained) {
|
| store.read(key, function (_, data) {
|
| if (!canceled) {
|
| deferred.resolve(data !== undefined, data);
|
| }
|
| }, error);
|
| return;
|
| }
|
| deferred.resolve(false);
|
| }, error);
|
| return deferred;
|
| };
|
|
|
| /** Saves a page to the cache local store.
|
| * @method DataCache~savePage
|
| * @param {Number} key - Zero-based index of the requested page.
|
| * @param {Object} page - Object with (i)ndex, (c)ount, (d)ata, and (t)icks.
|
| * @returns {DjsDeferred} A promise with no value.
|
| */
|
| var savePage = function (key, page) {
|
|
|
| djsassert(state !== CACHE_STATE_DESTROY, "DataCache.savePage() - cache is on the destroy state");
|
| djsassert(state !== CACHE_STATE_IDLE, "DataCache.savePage() - cache is on the idle state");
|
|
|
| var canceled = false;
|
|
|
| var deferred = extend(new DjsDeferred(), {
|
| /** Aborts the savePage operation. (used within promise callback)
|
| * @method DataCache#cancelReadPage
|
| */
|
| cancel: function () {
|
| canceled = true;
|
| }
|
| });
|
|
|
| var error = storeFailureCallback(deferred, "Save page to store failure");
|
|
|
| var resolve = function () {
|
| deferred.resolve(true);
|
| };
|
|
|
| if (page.c > 0) {
|
| var pageBytes = estimateSize(page);
|
| overflowed = cacheSize >= 0 && cacheSize < actualCacheSize + pageBytes;
|
|
|
| if (!overflowed) {
|
| store.addOrUpdate(key, page, function () {
|
| updateSettings(page, pageBytes);
|
| saveSettings(resolve, error);
|
| }, error);
|
| } else {
|
| resolve();
|
| }
|
| } else {
|
| updateSettings(page, 0);
|
| saveSettings(resolve, error);
|
| }
|
| return deferred;
|
| };
|
|
|
| /** Saves the cache's current settings to the local store.
|
| * @method DataCache~saveSettings
|
| * @param {Function} success - Success callback.
|
| * @param {Function} error - Errror callback.
|
| */
|
| var saveSettings = function (success, error) {
|
|
|
| var settings = {
|
| actualCacheSize: actualCacheSize,
|
| allDataLocal: allDataLocal,
|
| cacheSize: cacheSize,
|
| collectionCount: collectionCount,
|
| highestSavedPage: highestSavedPage,
|
| highestSavedPageSize: highestSavedPageSize,
|
| pageSize: pageSize,
|
| sourceId: source.identifier,
|
| version: version
|
| };
|
|
|
| store.addOrUpdate("__settings", settings, success, error);
|
| };
|
|
|
| /** Creates a function that handles a store error.
|
| * @method DataCache~storeFailureCallback
|
| * @param {DjsDeferred} deferred - Deferred object to resolve.
|
| * @returns {Function} Function to use as error callback.
|
|
|
| * This function will specifically handle problems when interacting with the store.
|
| */
|
| var storeFailureCallback = function (deferred/*, message*/) {
|
|
|
|
|
| return function (/*error*/) {
|
| // var console = windo1w.console;
|
| // if (console && console.log) {
|
| // console.log(message);
|
| // console.dir(error);
|
| // }
|
| deferred.resolve(false);
|
| };
|
| };
|
|
|
| /** Updates the cache's settings based on a page object.
|
| * @method DataCache~updateSettings
|
| * @param {Object} page - Object with (i)ndex, (c)ount, (d)ata.
|
| * @param {Number} pageBytes - Size of the page in bytes.
|
| */
|
| var updateSettings = function (page, pageBytes) {
|
|
|
| var pageCount = page.c;
|
| var pageIndex = page.i;
|
|
|
| // Detect the collection size.
|
| if (pageCount === 0) {
|
| if (highestSavedPage === pageIndex - pageSize) {
|
| collectionCount = highestSavedPage + highestSavedPageSize;
|
| }
|
| } else {
|
| highestSavedPage = Math.max(highestSavedPage, pageIndex);
|
| if (highestSavedPage === pageIndex) {
|
| highestSavedPageSize = pageCount;
|
| }
|
| actualCacheSize += pageBytes;
|
| if (pageCount < pageSize && !collectionCount) {
|
| collectionCount = pageIndex + pageCount;
|
| }
|
| }
|
|
|
| // Detect the end of the collection.
|
| if (!allDataLocal && collectionCount === highestSavedPage + highestSavedPageSize) {
|
| allDataLocal = true;
|
| }
|
| };
|
|
|
| /** State machine describing the behavior for cancelling a read or prefetch operation.
|
| * @method DataCache~cancelStateMachine
|
| * @param {DataCacheOperation} operation - Operation being run.
|
| * @param {Object} opTargetState - Operation state to transition to.
|
| * @param {Object} cacheState - Current cache state.
|
| * @param {Object} [data] -
|
| * This state machine contains behavior common to read and prefetch operations.
|
| */
|
| var cancelStateMachine = function (operation, opTargetState, cacheState, data) {
|
|
|
|
|
| var canceled = operation.canceled && opTargetState !== OPERATION_STATE_END;
|
| if (canceled) {
|
| if (opTargetState === OPERATION_STATE_CANCEL) {
|
| // Cancel state.
|
| // Data is expected to be any pending request made to the cache.
|
| if (data && data.cancel) {
|
| data.cancel();
|
| }
|
| }
|
| }
|
| return canceled;
|
| };
|
|
|
| /** State machine describing the behavior of a clear operation.
|
| * @method DataCache~destroyStateMachine
|
| * @param {DataCacheOperation} operation - Operation being run.
|
| * @param {Object} opTargetState - Operation state to transition to.
|
| * @param {Object} cacheState - Current cache state.
|
|
|
| * Clear operations have the highest priority and can't be interrupted by other operations; however,
|
| * they will preempt any other operation currently executing.
|
| */
|
| var destroyStateMachine = function (operation, opTargetState, cacheState) {
|
|
|
|
|
| var transition = operation.transition;
|
|
|
| // Signal the cache that a clear operation is running.
|
| if (cacheState !== CACHE_STATE_DESTROY) {
|
| changeState(CACHE_STATE_DESTROY);
|
| return true;
|
| }
|
|
|
| switch (opTargetState) {
|
| case OPERATION_STATE_START:
|
| // Initial state of the operation.
|
| transition(DESTROY_STATE_CLEAR);
|
| break;
|
|
|
| case OPERATION_STATE_END:
|
| // State that signals the operation is done.
|
| fireOnIdle();
|
| break;
|
|
|
| case DESTROY_STATE_CLEAR:
|
| // State that clears all the local data of the cache.
|
| clearStore().then(function () {
|
| // Terminate the operation once the local store has been cleared.
|
| operation.complete();
|
| });
|
| // Wait until the clear request completes.
|
| operation.wait();
|
| break;
|
|
|
| default:
|
| return false;
|
| }
|
| return true;
|
| };
|
|
|
| /** State machine describing the behavior of a prefetch operation.
|
| * @method DataCache~prefetchStateMachine
|
| * @param {DataCacheOperation} operation - Operation being run.
|
| * @param {Object} opTargetState - Operation state to transition to.
|
| * @param {Object} cacheState - Current cache state.
|
| * @param {Object} [data] -
|
|
|
| * Prefetch operations have the lowest priority and will be interrupted by operations of
|
| * other kinds. A preempted prefetch operation will resume its execution only when the state
|
| * of the cache returns to idle.
|
| *
|
| * If a clear operation starts executing then all the prefetch operations are canceled,
|
| * even if they haven't started executing yet.
|
| */
|
| var prefetchStateMachine = function (operation, opTargetState, cacheState, data) {
|
|
|
|
|
| // Handle cancelation
|
| if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
|
|
|
| var transition = operation.transition;
|
|
|
| // Handle preemption
|
| if (cacheState !== CACHE_STATE_PREFETCH) {
|
| if (cacheState === CACHE_STATE_DESTROY) {
|
| if (opTargetState !== OPERATION_STATE_CANCEL) {
|
| operation.cancel();
|
| }
|
| } else if (cacheState === CACHE_STATE_IDLE) {
|
| // Signal the cache that a prefetch operation is running.
|
| changeState(CACHE_STATE_PREFETCH);
|
| }
|
| return true;
|
| }
|
|
|
| switch (opTargetState) {
|
| case OPERATION_STATE_START:
|
| // Initial state of the operation.
|
| if (prefetchOperations[0] === operation) {
|
| transition(READ_STATE_LOCAL, operation.i);
|
| }
|
| break;
|
|
|
| case READ_STATE_DONE:
|
| // State that determines if the operation can be resolved or has to
|
| // continue processing.
|
| // Data is expected to be the read page.
|
| var pending = operation.pending;
|
|
|
| if (pending > 0) {
|
| pending -= Math.min(pending, data.c);
|
| }
|
|
|
| // Are we done, or has all the data been stored?
|
| if (allDataLocal || pending === 0 || data.c < pageSize || overflowed) {
|
| operation.complete();
|
| } else {
|
| // Continue processing the operation.
|
| operation.pending = pending;
|
| transition(READ_STATE_LOCAL, data.i + pageSize);
|
| }
|
| break;
|
|
|
| default:
|
| return readSaveStateMachine(operation, opTargetState, cacheState, data, true);
|
| }
|
| }
|
| return true;
|
| };
|
|
|
| /** State machine describing the behavior of a read operation.
|
| * @method DataCache~readStateMachine
|
| * @param {DataCacheOperation} operation - Operation being run.
|
| * @param {Object} opTargetState - Operation state to transition to.
|
| * @param {Object} cacheState - Current cache state.
|
| * @param {Object} [data] -
|
|
|
| * Read operations have a higher priority than prefetch operations, but lower than
|
| * clear operations. They will preempt any prefetch operation currently running
|
| * but will be interrupted by a clear operation.
|
| *
|
| * If a clear operation starts executing then all the currently running
|
| * read operations are canceled. Read operations that haven't started yet will
|
| * wait in the start state until the destory operation finishes.
|
| */
|
| var readStateMachine = function (operation, opTargetState, cacheState, data) {
|
|
|
|
|
| // Handle cancelation
|
| if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
|
|
|
| var transition = operation.transition;
|
|
|
| // Handle preemption
|
| if (cacheState !== CACHE_STATE_READ && opTargetState !== OPERATION_STATE_START) {
|
| if (cacheState === CACHE_STATE_DESTROY) {
|
| if (opTargetState !== OPERATION_STATE_START) {
|
| operation.cancel();
|
| }
|
| } else if (cacheState !== CACHE_STATE_WRITE) {
|
| // Signal the cache that a read operation is running.
|
| djsassert(state == CACHE_STATE_IDLE || state === CACHE_STATE_PREFETCH, "DataCache.readStateMachine() - cache is not on the read or idle state.");
|
| changeState(CACHE_STATE_READ);
|
| }
|
|
|
| return true;
|
| }
|
|
|
| switch (opTargetState) {
|
| case OPERATION_STATE_START:
|
| // Initial state of the operation.
|
| // Wait until the cache is idle or prefetching.
|
| if (cacheState === CACHE_STATE_IDLE || cacheState === CACHE_STATE_PREFETCH) {
|
| // Signal the cache that a read operation is running.
|
| changeState(CACHE_STATE_READ);
|
| if (operation.c >= 0) {
|
| // Snap the requested range to a page boundary.
|
| var range = snapToPageBoundaries(operation.i, operation.c, pageSize);
|
| transition(READ_STATE_LOCAL, range.i);
|
| } else {
|
| transition(READ_STATE_DONE, operation);
|
| }
|
| }
|
| break;
|
|
|
| case READ_STATE_DONE:
|
| // State that determines if the operation can be resolved or has to
|
| // continue processing.
|
| // Data is expected to be the read page.
|
| appendPage(operation, data);
|
| var len = getJsonValueArraryLength(operation.d);
|
| // Are we done?
|
| if (operation.c === len || data.c < pageSize) {
|
| // Update the stats, request for a prefetch operation.
|
| stats.cacheReads++;
|
| prefetch(data.i + data.c);
|
| // Terminate the operation.
|
| operation.complete();
|
| } else {
|
| // Continue processing the operation.
|
| transition(READ_STATE_LOCAL, data.i + pageSize);
|
| }
|
| break;
|
|
|
| default:
|
| return readSaveStateMachine(operation, opTargetState, cacheState, data, false);
|
| }
|
| }
|
|
|
| return true;
|
| };
|
|
|
| /** State machine describing the behavior for reading and saving data into the cache.
|
| * @method DataCache~readSaveStateMachine
|
| * @param {DataCacheOperation} operation - Operation being run.
|
| * @param {Object} opTargetState - Operation state to transition to.
|
| * @param {Object} cacheState - Current cache state.
|
| * @param {Object} [data] -
|
| * @param {Boolean} isPrefetch - Flag indicating whether a read (false) or prefetch (true) operation is running.
|
| * This state machine contains behavior common to read and prefetch operations.
|
| */
|
| var readSaveStateMachine = function (operation, opTargetState, cacheState, data, isPrefetch) {
|
|
|
| var error = operation.error;
|
| var transition = operation.transition;
|
| var wait = operation.wait;
|
| var request;
|
|
|
| switch (opTargetState) {
|
| case OPERATION_STATE_END:
|
| // State that signals the operation is done.
|
| fireOnIdle();
|
| break;
|
|
|
| case READ_STATE_LOCAL:
|
| // State that requests for a page from the local store.
|
| // Data is expected to be the index of the page to request.
|
| request = readPage(data).then(function (found, page) {
|
| // Signal the cache that a read operation is running.
|
| if (!operation.canceled) {
|
| if (found) {
|
| // The page is in the local store, check if the operation can be resolved.
|
| transition(READ_STATE_DONE, page);
|
| } else {
|
| // The page is not in the local store, request it from the source.
|
| transition(READ_STATE_SOURCE, data);
|
| }
|
| }
|
| });
|
| break;
|
|
|
| case READ_STATE_SOURCE:
|
| // State that requests for a page from the cache source.
|
| // Data is expected to be the index of the page to request.
|
| request = fetchPage(data).then(function (page) {
|
| // Signal the cache that a read operation is running.
|
| if (!operation.canceled) {
|
| // Update the stats and save the page to the local store.
|
| if (isPrefetch) {
|
| stats.prefetches++;
|
| } else {
|
| stats.netReads++;
|
| }
|
| transition(READ_STATE_SAVE, page);
|
| }
|
| }, error);
|
| break;
|
|
|
| case READ_STATE_SAVE:
|
| // State that saves a page to the local store.
|
| // Data is expected to be the page to save.
|
| // Write access to the store is exclusive.
|
| if (cacheState !== CACHE_STATE_WRITE) {
|
| changeState(CACHE_STATE_WRITE);
|
| request = savePage(data.i, data).then(function (saved) {
|
| if (!operation.canceled) {
|
| if (!saved && isPrefetch) {
|
| operation.pending = 0;
|
| }
|
| // Check if the operation can be resolved.
|
| transition(READ_STATE_DONE, data);
|
| }
|
| changeState(CACHE_STATE_IDLE);
|
| });
|
| }
|
| break;
|
|
|
| default:
|
| // Unknown state that can't be handled by this state machine.
|
| return false;
|
| }
|
|
|
| if (request) {
|
| // The operation might have been canceled between stack frames do to the async calls.
|
| if (operation.canceled) {
|
| request.cancel();
|
| } else if (operation.s === opTargetState) {
|
| // Wait for the request to complete.
|
| wait(request);
|
| }
|
| }
|
|
|
| return true;
|
| };
|
|
|
| // Initialize the cache.
|
| store.read("__settings", function (_, settings) {
|
| if (assigned(settings)) {
|
| var settingsVersion = settings.version;
|
| if (!settingsVersion || settingsVersion.indexOf("1.") !== 0) {
|
| cacheFailureCallback("Unsupported cache store version " + settingsVersion)();
|
| return;
|
| }
|
|
|
| if (pageSize !== settings.pageSize || source.identifier !== settings.sourceId) {
|
| // The shape or the source of the data was changed so invalidate the store.
|
| clearStore().then(function () {
|
| // Signal the cache is fully initialized.
|
| changeState(CACHE_STATE_IDLE);
|
| }, cacheFailureCallback("Unable to clear store during initialization"));
|
| } else {
|
| // Restore the saved settings.
|
| actualCacheSize = settings.actualCacheSize;
|
| allDataLocal = settings.allDataLocal;
|
| cacheSize = settings.cacheSize;
|
| collectionCount = settings.collectionCount;
|
| highestSavedPage = settings.highestSavedPage;
|
| highestSavedPageSize = settings.highestSavedPageSize;
|
| version = settingsVersion;
|
|
|
| // Signal the cache is fully initialized.
|
| changeState(CACHE_STATE_IDLE);
|
| }
|
| } else {
|
| // This is a brand new cache.
|
| saveSettings(function () {
|
| // Signal the cache is fully initialized.
|
| changeState(CACHE_STATE_IDLE);
|
| }, cacheFailureCallback("Unable to write settings during initialization."));
|
| }
|
| }, cacheFailureCallback("Unable to read settings from store."));
|
|
|
| return that;
|
| }
|
|
|
| /** Creates a data cache for a collection that is efficiently loaded on-demand.
|
| * @param options
|
| * Options for the data cache, including name, source, pageSize, TODO check doku
|
| * prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
|
| * @returns {DataCache} A new data cache instance.
|
| */
|
| function createDataCache (options) {
|
| checkUndefinedGreaterThanZero(options.pageSize, "pageSize");
|
| checkUndefinedOrNumber(options.cacheSize, "cacheSize");
|
| checkUndefinedOrNumber(options.prefetchSize, "prefetchSize");
|
|
|
| if (!assigned(options.name)) {
|
| throw { message: "Undefined or null name", options: options };
|
| }
|
|
|
| if (!assigned(options.source)) {
|
| throw { message: "Undefined source", options: options };
|
| }
|
|
|
| return new DataCache(options);
|
| }
|
|
|
|
|
| /** estimateSize (see {@link estimateSize}) */
|
| exports.estimateSize = estimateSize;
|
|
|
| /** createDataCache */
|
| exports.createDataCache = createDataCache;
|
|
|
|
|
|
|
| </code></pre> |
| </article> |
| </section> |
| |
| |
| |
| |
| </div> |
| |
| <nav> |
| <h2><a href="index.html">Index</a></h2><h3>Modules</h3><ul><li><a href="module-cache.html">cache</a></li><li><a href="source.html">cache/source</a></li><li><a href="module-odata.html">odata</a></li><li><a href="batch.html">odata/batch</a></li><li><a href="handler.html">odata/handler</a></li><li><a href="json.html">odata/json</a></li><li><a href="metadata.html">odata/metadata</a></li><li><a href="net.html">odata/net</a></li><li><a href="utils.html">odata/utils</a></li><li><a href="deferred.html">odatajs/deferred</a></li><li><a href="utils_.html">odatajs/utils</a></li><li><a href="xml.html">odatajs/xml</a></li><li><a href="module-store.html">store</a></li><li><a href="dom.html">store/dom</a></li><li><a href="indexeddb.html">store/indexeddb</a></li><li><a href="memory.html">store/memory</a></li></ul><h3>Classes</h3><ul><li><a href="DataCache.html">DataCache</a></li><li><a href="DataCacheOperation.html">DataCacheOperation</a></li><li><a href="DjsDeferred.html">DjsDeferred</a></li><li><a href="dom-DomStore.html">DomStore</a></li><li><a href="indexeddb-IndexedDBStore.html">IndexedDBStore</a></li><li><a href="memory-MemoryStore.html">MemoryStore</a></li><li><a href="ODataCacheSource.html">ODataCacheSource</a></li></ul><h3><a href="global.html">Global</a></h3> |
| </nav> |
| |
| <br clear="both"> |
| |
| <footer> |
| Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.2.2</a> on Thu Apr 09 2015 08:31:26 GMT+0200 (MESZ) |
| </footer> |
| |
| <script> prettyPrint(); </script> |
| <script src="scripts/linenumber.js"> </script> |
| </body> |
| </html> |