<commit_before>/**
 * Created by michael on 10/15/14.
 */

var LowlaDB = (function(LowlaDB) {

  var Cursor = function(collection, filter, options) {
    if (!(this instanceof Cursor)) {
      return new Cursor(collection, filter, options);
    }

    this._collection = collection;
    this._filter = filter;
    this._options = {
      sort: null,
      limit: 0,
      showPending: false
    };

    for (var i in options) {
      if (options.hasOwnProperty(i)) {
        this._options[i] = options[i];
      }
    }
  };

  var filterApplies = function(filter, doc) {
    for (var i in filter) {
      if (filter.hasOwnProperty(i)) {
        if (!doc.hasOwnProperty(i) || filter[i] !== doc[i]) {
          return false;
        }
      }
    }

    return true;
  };

  var docCompareFunc = function(sort, a, b) {
    if (a.hasOwnProperty(sort)) {
      if (!b.hasOwnProperty(sort)) {
        return 1;
      }
      else {
        return a[sort] < b[sort] ? -1 : (a[sort] == b[sort] ? 0 : 1);
      }
    }
    else {
      if (!b.hasOwnProperty(sort)) {
        return 0;
      }
      return -1;
    }
  };

  Cursor.prototype._applyFilterInTx = function(tx, docsCallback) {
    var data = [];
    var coll = this._collection;
    var clientIdPrefix = coll.dbName + '.' + coll.collectionName + '$';
    var cursor = this;

    tx.scan(collectDocs, processDocs);

    function collectDocs(clientId, doc) {
      if (clientId.indexOf(clientIdPrefix) === 0 && filterApplies(cursor._filter, doc)) {
        data.push(doc);
      }
    }

    function processDocs() {
      data = data.map(LowlaDB.SyncCoordinator.convertSpecialTypes);
      if (cursor._options.sort) {
        sortData();
      }

      if (cursor._options.limit) {
        data = data.slice(0, cursor._options.limit);
      }

      if (data.length && cursor._options.showPending) {
        tx.load("$metadata", loadMetaForPending);
      }
      else {
        try {
          docsCallback(data, tx);
        }
        catch (err) {
          tx.errCb(err);
        }
      }
    }

    function loadMetaForPending(metaDoc, tx) {
      if (metaDoc && metaDoc.changes) {
        data.forEach(function(doc) {
          doc.$pending = metaDoc.changes.hasOwnProperty(clientIdPrefix + doc._id);
        });
      }

      docsCallback(data, tx);
    }

    function sortData() {
      var sort = cursor._options.sort;
      data.sort(function(a,b) {
        if (typeof(sort) == 'string') {
          return docCompareFunc(sort, a, b);
        }
        else if (sort instanceof Array) {
          for (var i = 0; i < sort.length; i += 2) {
            var answer = docCompareFunc(sort[i], a, b);
            if (sort[i+1] < 0) {
              answer = -answer;
            }
            if (answer) {
              return answer;
            }
          }

          return 0;
        }
      });
    }
  };

  Cursor.prototype._applyFilter = function() {
    var cursor = this;
    var answer;
    return new Promise(function(resolve, reject) {
      LowlaDB.Datastore.transact(applyFilter, resolve, reject);
      function applyFilter(tx) {
        cursor._applyFilterInTx(tx, function(docs) {
          answer = docs;
        });
      }
    })
      .then(function() {
        return answer;
      });
  };

  var liveCursors = {};
  Cursor.notifyLive = function (coll) {
    var key = coll.dbName + '.' + coll.collectionName;
    if (!liveCursors[key]) {
      return;
    }

    liveCursors[key].forEach(function (watcher) {
      watcher.callback(null, watcher.cursor);
    });
  };

  Cursor.off = function() {
    liveCursors = {};
  };

  Cursor.prototype.on = function (callback) {
    var coll = this._collection;
    var key = coll.dbName + '.' + coll.collectionName;
    if (!liveCursors[key]) {
      liveCursors[key] = [];
    }

    liveCursors[key].push({ cursor: this, callback: callback });
    callback(null, this);
  };

  Cursor.prototype.cloneWithOptions = function(options) {
    var answer = new Cursor(this._collection, this._filter);
    answer._options = this._options;
    for (var i in options) {
      if (options.hasOwnProperty(i)) {
        answer._options[i] = options[i];
      }
    }

    return answer;
  };

  Cursor.prototype.limit = function(amount) {
    return this.cloneWithOptions({ limit: amount });
  };

  Cursor.prototype.sort = function(sort) {
    if (sort instanceof Array && sort.length % 2) {
      throw Error('Invalid sort array, must be pairs');
    }
    return this.cloneWithOptions({ sort: sort });
  };

  Cursor.prototype.showPending = function() {
    return this.cloneWithOptions({ showPending: true });
  };

  Cursor.prototype.each = function(callback) {
    if (!callback) {
      return;
    }

    var data = this._applyFilter();
    data.forEach(function(doc) {
      callback(null, doc);
    });
  };

  Cursor.prototype.toArray = function(callback) {
    return this._applyFilter()
      .then(function(filtered) {
        if (callback) {
          callback(null, filtered);
        }
        return filtered;
      }, function(err) {
        if (callback) {
          callback(err);
        }
        throw err;
      });
  };

  Cursor.prototype.count = function(applySkipLimit, callback) {
    if (typeof(applySkipLimit) === 'function') {
      callback = applySkipLimit;
      applySkipLimit = false;
    }

    var cursor = this;
    if (!applySkipLimit) {
      cursor = this.cloneWithOptions({skip: 0, limit: 0});
    }

    return cursor.toArray().then(function(arr) {
      if (callback) {
        callback(null, arr.length);
      }
      return arr.length;
    }, function(err) {
      if (callback) {
        callback(err);
      }
      throw err;
    });
  };

  LowlaDB.Cursor = Cursor;
  return LowlaDB;

})(LowlaDB || {});<commit_msg>Refactor Cursor class.<commit_after>/**
 * Created by michael on 10/15/14.
 */

var LowlaDB = (function(LowlaDB) {
  'use strict';
  var liveCursors = {};

  // Public API
  LowlaDB.Cursor = Cursor;

  Cursor.prototype.count = count;
  Cursor.prototype.each = each;
  Cursor.prototype.limit = limit;
  Cursor.prototype.sort = sort;
  Cursor.prototype.showPending = showPending;
  Cursor.prototype.toArray = toArray;

  Cursor.prototype.on = on;
  Cursor.off = off;
  Cursor.notifyLive = notifyLive;
  Cursor.prototype.cloneWithOptions = cloneWithOptions;

  // Private API
  Cursor.prototype._applyFilter = _applyFilter;
  Cursor.prototype._applyFilterInTx = _applyFilterInTx;

  return LowlaDB;
  ///////////////

  function Cursor(collection, filter, options) {
    if (!(this instanceof Cursor)) {
      return new Cursor(collection, filter, options);
    }

    this._collection = collection;
    this._filter = filter;
    this._options = {
      sort: null,
      limit: 0,
      showPending: false
    };

    for (var i in options) {
      if (options.hasOwnProperty(i)) {
        this._options[i] = options[i];
      }
    }
  }

  function filterApplies(filter, doc) {
    for (var i in filter) {
      if (filter.hasOwnProperty(i)) {
        if (!doc.hasOwnProperty(i) || filter[i] !== doc[i]) {
          return false;
        }
      }
    }

    return true;
  }

  function docCompareFunc(sort, a, b) {
    if (a.hasOwnProperty(sort)) {
      if (!b.hasOwnProperty(sort)) {
        return 1;
      }
      else {
        return a[sort] < b[sort] ? -1 : (a[sort] == b[sort] ? 0 : 1);
      }
    }
    else {
      if (!b.hasOwnProperty(sort)) {
        return 0;
      }
      return -1;
    }
  }

  function _applyFilterInTx(tx, docsCallback) {
    /* jshint validthis:true */
    var data = [];
    var coll = this._collection;
    var clientIdPrefix = coll.dbName + '.' + coll.collectionName + '$';
    var cursor = this;

    tx.scan(collectDocs, processDocs);

    function collectDocs(clientId, doc) {
      if (clientId.indexOf(clientIdPrefix) === 0 && filterApplies(cursor._filter, doc)) {
        data.push(doc);
      }
    }

    function processDocs() {
      data = data.map(LowlaDB.SyncCoordinator.convertSpecialTypes);
      if (cursor._options.sort) {
        sortData();
      }

      if (cursor._options.limit) {
        data = data.slice(0, cursor._options.limit);
      }

      if (data.length && cursor._options.showPending) {
        tx.load("$metadata", loadMetaForPending);
      }
      else {
        try {
          docsCallback(data, tx);
        }
        catch (err) {
          tx.errCb(err);
        }
      }
    }

    function loadMetaForPending(metaDoc, tx) {
      if (metaDoc && metaDoc.changes) {
        data.forEach(function(doc) {
          doc.$pending = metaDoc.changes.hasOwnProperty(clientIdPrefix + doc._id);
        });
      }

      docsCallback(data, tx);
    }

    function sortData() {
      var sort = cursor._options.sort;
      data.sort(function(a,b) {
        if (typeof(sort) === 'string') {
          return docCompareFunc(sort, a, b);
        }
        else if (sort instanceof Array) {
          for (var i = 0; i < sort.length; i += 2) {
            var answer = docCompareFunc(sort[i], a, b);
            if (sort[i+1] < 0) {
              answer = -answer;
            }
            if (answer) {
              return answer;
            }
          }

          return 0;
        }
      });
    }
  }

  function _applyFilter() {
    /* jshint validthis:true */
    var cursor = this;
    var answer;
    return new Promise(function(resolve, reject) {
      LowlaDB.Datastore.transact(applyFilter, resolve, reject);
      function applyFilter(tx) {
        cursor._applyFilterInTx(tx, function(docs) {
          answer = docs;
        });
      }
    })
      .then(function() {
        return answer;
      });
  }

  function notifyLive(coll) {
    var key = coll.dbName + '.' + coll.collectionName;
    if (!liveCursors[key]) {
      return;
    }

    liveCursors[key].forEach(function (watcher) {
      watcher.callback(null, watcher.cursor);
    });
  }

  function off() {
    liveCursors = {};
  }

  function on(callback) {
    /* jshint validthis:true */
    var coll = this._collection;
    var key = coll.dbName + '.' + coll.collectionName;
    if (!liveCursors[key]) {
      liveCursors[key] = [];
    }

    liveCursors[key].push({ cursor: this, callback: callback });
    callback(null, this);
  }

  function cloneWithOptions(options) {
    /* jshint validthis:true */
    var answer = new Cursor(this._collection, this._filter);
    answer._options = this._options;
    for (var i in options) {
      if (options.hasOwnProperty(i)) {
        answer._options[i] = options[i];
      }
    }

    return answer;
  }

  function limit(amount) {
    /* jshint validthis:true */
    return this.cloneWithOptions({ limit: amount });
  }

  function sort(keyOrList) {
    /* jshint validthis:true */
    if (keyOrList instanceof Array && keyOrList.length % 2) {
      throw Error('Invalid sort array, must be pairs');
    }
    return this.cloneWithOptions({ sort: keyOrList });
  }

  function showPending() {
    /* jshint validthis:true */
    return this.cloneWithOptions({ showPending: true });
  }

  function each(callback) {
    /* jshint validthis:true */
    if (!callback) {
      return;
    }

    var data = this._applyFilter();
    data.forEach(function(doc) {
      callback(null, doc);
    });
  }

  function toArray(callback) {
    /* jshint validthis:true */
    return this._applyFilter()
      .then(function(filtered) {
        if (callback) {
          callback(null, filtered);
        }
        return filtered;
      }, function(err) {
        if (callback) {
          callback(err);
        }
        throw err;
      });
  }

  function count(applySkipLimit, callback) {
    /* jshint validthis:true */
    if (typeof(applySkipLimit) === 'function') {
      callback = applySkipLimit;
      applySkipLimit = false;
    }

    var cursor = this;
    if (!applySkipLimit) {
      cursor = this.cloneWithOptions({skip: 0, limit: 0});
    }

    return cursor.toArray().then(function(arr) {
      if (callback) {
        callback(null, arr.length);
      }
      return arr.length;
    }, function(err) {
      if (callback) {
        callback(err);
      }
      throw err;
    });
  }

})(LowlaDB || {});
{'fraction_non_alphanumeric': 0.13815681780978883, 'fraction_numerical': 0.0037649369782288425, 'mean_word_length': 2.0981237322515214, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6221894', 'n_tokens_mistral': 3928, 'n_tokens_neox': 3825, 'n_words': 793}