<commit_before>// -*- coding: utf-8 -*-
//-----------------------------------------------------------------------------
// file: $Id$
// lib:  syncml-js.store
// auth: griffin <<EMAIL>>
// date: 2012/11/04
// copy: (C) CopyLoose 2012 UberDev <<EMAIL>>, No Rights Reserved.
//-----------------------------------------------------------------------------

// for node compatibility...
if ( typeof(define) !== 'function')
  var define = require('amdefine')(module);

define([
  'underscore',
  'elementtree',
  './logging',
  './common',
  './constant',
  './codec',
  './ctype',
  './storage'
], function(
  _,
  ET,
  logging,
  common,
  constant,
  codec,
  ctype,
  storage
) {

  var log = logging.getLogger('syncml-js.store');
  var exports = {};

  //---------------------------------------------------------------------------
  exports.Store = common.Base.extend({

    //-------------------------------------------------------------------------
    constructor: function(adapter, options) {

      // todo: some of these attributes should be modifiable...

      //: for local stores, specifies the agent that will implement
      //: the actual item operations -- it must implement the
      //: syncml-js.Agent API.
      this.agent = options.agent || null;

      //: [read-only] specifies the SyncML URI that this store is bound to.
      this.uri = ( adapter ? adapter.normUri(options.uri) : options.uri ) || null;

      //: [read-only] specifies the human-readable name for this store.
      //: if undefined, defaults to URI.
      this.displayName = options.displayName || this.uri || null;

      //: [read-only] specifies the maximum GUID size for items in this store.
      //: if undefined, defaults to adapter setting.
      this.maxGuidSize = options.maxGuidSize || null;

      //: [read-only] specifies the maximum object size for items in this store.
      //: if undefined, defaults to adapter setting.
      this.maxObjSize = options.maxObjSize || null;

      //: [read-only] specifies conflict resolution policy for this store.
      //: if undefined, defaults to adapter setting.
      this.conflictPolicy = options.conflictPolicy || null;

      //: [read-only] specifies which syncTypes this store supports.
      //: (defaults to all.)
      this.syncTypes = options.syncTypes;
      if ( this.syncTypes == undefined || this.syncTypes.length <= 0 )
      {
        this.syncTypes = [
          constant.SYNCTYPE_TWO_WAY,
          constant.SYNCTYPE_SLOW_SYNC,
          constant.SYNCTYPE_ONE_WAY_FROM_CLIENT,
          constant.SYNCTYPE_REFRESH_FROM_CLIENT,
          constant.SYNCTYPE_ONE_WAY_FROM_SERVER,
          constant.SYNCTYPE_REFRESH_FROM_SERVER,
          constant.SYNCTYPE_SERVER_ALERTED,
        ];
      }

      ctypes = options.contentTypes;
      if ( ! ctypes && options.agent )
        ctypes = options.agent.getContentTypes();

      this.contentTypes = _.map(ctypes, function(e) {
        if ( e instanceof ctype.ContentTypeInfo )
          return e;
        return ctype.ContentTypeInfo.fromStruct(e);
      });

      // --- private attributes
      this.id       = options.id || common.makeID();
      this._a       = adapter;
    },

    //-------------------------------------------------------------------------
    _load: function(cb) {
      cb();
    },

    //-------------------------------------------------------------------------
    _getModel: function() {
      var self = this;
      var uri  = self._a.normUri(self.uri);
      return _.find(this._a._getModel().stores,
                    function(s) { return self._a.normUri(s.uri) == uri; });
    },

    //-------------------------------------------------------------------------
    _updateModel: function(cb) {
      if ( ! this._a._model || ! this._a._model.stores )
        return cb('store created on un-initialized adapter');
      // TODO: this squashes any data that may already be there, such
      //       as *BINDING* info!...
      this._a._model.stores = _.filter(this._a._model.stores, function(e) {
        return e.uri != this.uri;
      }, this);
      this._a._model.stores.push({
        id              : this.id,
        uri             : this.uri,
        displayName     : this.displayName,
        syncTypes       : this.syncTypes,
        maxGuidSize     : this.maxGuidSize,
        maxObjSize      : this.maxObjSize,
        conflictPolicy  : this.conflictPolicy,
        contentTypes    : _.map(this.contentTypes, function(e) { return e.toStruct(); })
      });
      cb();
    },

    //-------------------------------------------------------------------------
    getBinding: function() {
      return this._getModel().binding;
    },

    //-------------------------------------------------------------------------
    getContentTypes: function() {
      if ( this.agent != undefined )
        return this.agent.getContentTypes();
      return this.contentTypes;
    },

    //-------------------------------------------------------------------------
    getPeerStore: function(peer) {

      if ( this._a.isLocal )
      {
        var peerUri = this._a._c.router.getTargetUri(this._a, peer, this.uri);
        if ( ! peerUri )
          return null;

        log.warning('TODO ::: Store.getPeerStore NOT IMPLEMENTED');

        return null;
      }

      log.warning('TODO ::: Store.getPeerStore NOT IMPLEMENTED');

      return null;

    },

    //-------------------------------------------------------------------------
    _clearAllMappings: function(cb) {
      if ( this._a.isLocal )
        return cb(new common.InternalError(
          'unexpected mapping request for local store'));
      var mapping = this._a._c._dbtxn.objectStore('mapping');
      storage.deleteAll(mapping, {store_id: this.id}, cb);
    },

    //-------------------------------------------------------------------------
    _setMapping: function(guid, luid, cb) {
      var self = this;
      if ( this._a.isLocal )
        return cb(new common.InternalError(
          'unexpected mapping request for local store'));
      // delete all previous mappings for this guid/store (there should
      // be at most one)... but paranoia rules.
      var mapping = this._a._c._dbtxn.objectStore('mapping');
      storage.deleteAll(mapping, {store_id: this.id, guid: guid}, function(err) {
        if ( err )
          return cb(err);
        storage.put(mapping, {store_id: self.id, guid: guid, luid: luid}, function(err) {
          if ( err )
            return cb(err);
          cb();
        });
      });
    },

    //-------------------------------------------------------------------------
    _getMapping: function(guid, cb) {
      if ( this._a.isLocal )
        return cb(new common.InternalError(
          'unexpected mapping request for local store'));
      // todo: there must be a way to use IndexedDB since i have everything
      //       needed to generate the keyPath!... eg:
      //         objectStore.get({store_id:X,guid:Y})?...
      var storeMapping = this._a._c._dbtxn.objectStore('mapping').index('store_id');
      storage.getAll(storeMapping, this.id, null, function(err, list) {
        if ( err )
          return cb(err);
        var item = _.find(list, function(item) {
          return item.guid == guid;
        });
        return cb(null, item ? item.luid : null);
      });
    },

    //-------------------------------------------------------------------------
    _getReverseMapping: function(luid, cb) {
      if ( this._a.isLocal )
        return cb(new common.InternalError(
          'unexpected mapping request for local store'));
      // todo: there must be a way to use IndexedDB since i have everything
      //       needed to generate the keyPath!... eg:
      //         objectStore.get({store_id:X,guid:Y})?...
      var storeMapping = this._a._c._dbtxn.objectStore('mapping').index('store_id');
      storage.getAll(storeMapping, this.id, null, function(err, list) {
        if ( err )
          return cb(err);
        var item = _.find(list, function(item) {
          return item.luid == luid;
        });
        return cb(null, item ? item.guid : null);
      });
    },

    //-------------------------------------------------------------------------
    registerChange: function(itemID, state, options, cb) {
      // options can include:
      //   - changeSpec (bool)
      //   - excludePeerID (string)
      options = options || {};
      var self = this;
      if ( self._a.isLocal )
      {
        // TODO: THIS NEEDS TO BE SIGNIFICANTLY OPTIMIZED!... either:
        //         a) optimize this reverse lookup, or
        //         b) use a query that targets exactly the set of stores needed
        //       note that a pre-emptive model.session.flush() may be necessary.
        common.cascade(self._a.getPeers(), function(peer, cb) {
          if ( options.excludePeerID && options.excludePeerID == peer.id )
            return cb();
          common.cascade(peer.getStores(), function(store, cb) {
            var binding = store.getBinding()
            if ( ! binding || binding.uri != self.uri )<commit_msg>implemented store merging and renamed Store.getBinding to Store._getBinding<commit_after>// -*- coding: utf-8 -*-
//-----------------------------------------------------------------------------
// file: $Id$
// lib:  syncml-js.store
// auth: griffin <<EMAIL>>
// date: 2012/11/04
// copy: (C) CopyLoose 2012 UberDev <<EMAIL>>, No Rights Reserved.
//-----------------------------------------------------------------------------

// for node compatibility...
if ( typeof(define) !== 'function')
  var define = require('amdefine')(module);

define([
  'underscore',
  'elementtree',
  './logging',
  './common',
  './constant',
  './codec',
  './ctype',
  './storage'
], function(
  _,
  ET,
  logging,
  common,
  constant,
  codec,
  ctype,
  storage
) {

  var log = logging.getLogger('syncml-js.store');
  var exports = {};

  //---------------------------------------------------------------------------
  exports.Store = common.Base.extend({

    //-------------------------------------------------------------------------
    constructor: function(adapter, options) {

      // todo: some of these attributes should be modifiable...

      //: for local stores, specifies the agent that will implement
      //: the actual item operations -- it must implement the
      //: syncml-js.Agent API.
      this.agent = options.agent || null;

      //: [read-only] specifies the SyncML URI that this store is bound to.
      this.uri = ( adapter ? adapter.normUri(options.uri) : options.uri ) || null;

      //: [read-only] specifies the human-readable name for this store.
      //: if undefined, defaults to URI.
      this.displayName = options.displayName || this.uri || null;

      //: [read-only] specifies the maximum GUID size for items in this store.
      //: if undefined, defaults to adapter setting.
      this.maxGuidSize = options.maxGuidSize || null;

      //: [read-only] specifies the maximum object size for items in this store.
      //: if undefined, defaults to adapter setting.
      this.maxObjSize = options.maxObjSize || null;

      //: [read-only] specifies conflict resolution policy for this store.
      //: if undefined, defaults to adapter setting.
      this.conflictPolicy = options.conflictPolicy || null;

      //: [read-only] specifies which syncTypes this store supports.
      //: (defaults to all.)
      this.syncTypes = options.syncTypes;
      if ( this.syncTypes == undefined || this.syncTypes.length <= 0 )
      {
        this.syncTypes = [
          constant.SYNCTYPE_TWO_WAY,
          constant.SYNCTYPE_SLOW_SYNC,
          constant.SYNCTYPE_ONE_WAY_FROM_CLIENT,
          constant.SYNCTYPE_REFRESH_FROM_CLIENT,
          constant.SYNCTYPE_ONE_WAY_FROM_SERVER,
          constant.SYNCTYPE_REFRESH_FROM_SERVER,
          constant.SYNCTYPE_SERVER_ALERTED,
        ];
      }

      ctypes = options.contentTypes;
      if ( ! ctypes && options.agent )
        ctypes = options.agent.getContentTypes();

      this.contentTypes = _.map(ctypes, function(e) {
        if ( e instanceof ctype.ContentTypeInfo )
          return e;
        return ctype.ContentTypeInfo.fromStruct(e);
      });

      // --- private attributes
      this.id       = options.id || common.makeID();
      this._a       = adapter;
    },

    //-------------------------------------------------------------------------
    _load: function(cb) {
      cb();
    },

    //-------------------------------------------------------------------------
    _getModel: function() {
      var self = this;
      var uri  = self._a.normUri(self.uri);
      return _.find(this._a._getModel().stores,
                    function(s) { return self._a.normUri(s.uri) == uri; });
    },

    //-------------------------------------------------------------------------
    _updateModel: function(cb) {
      if ( ! this._a._model || ! this._a._model.stores )
        return cb('store created on un-initialized adapter');
      // TODO: this squashes any data that may already be there, such
      //       as *BINDING* info!...
      this._a._model.stores = _.filter(this._a._model.stores, function(e) {
        return e.uri != this.uri;
      }, this);
      this._a._model.stores.push({
        id              : this.id,
        uri             : this.uri,
        displayName     : this.displayName,
        syncTypes       : this.syncTypes,
        maxGuidSize     : this.maxGuidSize,
        maxObjSize      : this.maxObjSize,
        conflictPolicy  : this.conflictPolicy,
        contentTypes    : _.map(this.contentTypes, function(e) { return e.toStruct(); })
      });
      cb();
    },

    //-------------------------------------------------------------------------
    _getBinding: function() {
      return this._getModel().binding;
    },

    //-------------------------------------------------------------------------
    _setBinding: function(binding) {
      this._getModel().binding = binding;
    },

    //-------------------------------------------------------------------------
    getContentTypes: function() {
      if ( this.agent != undefined )
        return this.agent.getContentTypes();
      return this.contentTypes;
    },

    //-------------------------------------------------------------------------
    getPeerStore: function(peer) {

      if ( this._a.isLocal )
      {
        var peerUri = this._a._c.router.getTargetUri(this._a, peer, this.uri);
        if ( ! peerUri )
          return null;

        log.warning('TODO ::: Store.getPeerStore NOT IMPLEMENTED');

        return null;
      }

      log.warning('TODO ::: Store.getPeerStore NOT IMPLEMENTED');

      return null;

    },

    //-------------------------------------------------------------------------
    merge: function(store, cb) {

      log.critical('TODO ::: ensure that an adapter._save (at session end) saves merged info');

      if ( this.uri != store.uri )
        return cb(new common.InternalError(
          'unexpected merging of stores with different URIs ("' + this.uri
            + '" != "' + store.uri + '")'));
      if ( ! _.isEqual(this.contentTypes, store.contentTypes) )
      {
        // todo: this is a bit drastic... perhaps have an operational setting
        //       which controls how paranoid to be?...
        this._setBinding(null);
      }
      this.displayName    = store.displayName;
      this.contentTypes   = _.rest(store.contentTypes, 0);
      this.syncTypes      = _.rest(store.syncTypes, 0);
      this.maxGuidSize    = store.maxGuidSize;
      this.maxObjSize     = store.maxObjSize;
      this.agent          = store.agent;
      this.conflictPolicy = store.conflictPolicy;
      return cb();
    },

    //-------------------------------------------------------------------------
    _clearAllMappings: function(cb) {
      if ( this._a.isLocal )
        return cb(new common.InternalError(
          'unexpected mapping request for local store'));
      var mapping = this._a._c._dbtxn.objectStore('mapping');
      storage.deleteAll(mapping, {store_id: this.id}, cb);
    },

    //-------------------------------------------------------------------------
    _setMapping: function(guid, luid, cb) {
      var self = this;
      if ( this._a.isLocal )
        return cb(new common.InternalError(
          'unexpected mapping request for local store'));
      // delete all previous mappings for this guid/store (there should
      // be at most one)... but paranoia rules.
      var mapping = this._a._c._dbtxn.objectStore('mapping');
      storage.deleteAll(mapping, {store_id: this.id, guid: guid}, function(err) {
        if ( err )
          return cb(err);
        storage.put(mapping, {store_id: self.id, guid: guid, luid: luid}, function(err) {
          if ( err )
            return cb(err);
          cb();
        });
      });
    },

    //-------------------------------------------------------------------------
    _getMapping: function(guid, cb) {
      if ( this._a.isLocal )
        return cb(new common.InternalError(
          'unexpected mapping request for local store'));
      // todo: there must be a way to use IndexedDB since i have everything
      //       needed to generate the keyPath!... eg:
      //         objectStore.get({store_id:X,guid:Y})?...
      var storeMapping = this._a._c._dbtxn.objectStore('mapping').index('store_id');
      storage.getAll(storeMapping, this.id, null, function(err, list) {
        if ( err )
          return cb(err);
        var item = _.find(list, function(item) {
          return item.guid == guid;
        });
        return cb(null, item ? item.luid : null);
      });
    },

    //-------------------------------------------------------------------------
    _getReverseMapping: function(luid, cb) {
      if ( this._a.isLocal )
        return cb(new common.InternalError(
          'unexpected mapping request for local store'));
      // todo: there must be a way to use IndexedDB since i have everything
      //       needed to generate the keyPath!... eg:
      //         objectStore.get({store_id:X,guid:Y})?...
      var storeMapping = this._a._c._dbtxn.objectStore('mapping').index('store_id');
      storage.getAll(storeMapping, this.id, null, function(err, list) {
        if ( err )
          return cb(err);
        var item = _.find(list, function(item) {
          return item.luid == luid;
        });
        return cb(null, item ? item.guid : null);
      });
    },

    //-------------------------------------------------------------------------
    registerChange: function(itemID, state, options, cb) {
      // options can include:
      //   - changeSpec (bool)
      //   - excludePeerID (string)
      options = options || {};
      var self = this;
      if ( self._a.isLocal )
      {
        // TODO: THIS NEEDS TO BE SIGNIFICANTLY OPTIMIZED!... either:
        //         a) optimize this reverse lookup, or
        //         b) use a query that targets exactly the set of stores needed
        //       note that a pre-emptive model.session.flush() may be necessary.
        common.cascade(self._a.getPeers(), function(peer, cb) {
          if ( options.excludePeerID && options.excludePeerID == peer.id )
            return cb();
          common.cascade(peer.getStores(), function(store, cb) {
            var binding = store._getBinding()
            if ( ! binding || binding.uri != self.uri )
{'fraction_non_alphanumeric': 0.23555054105338735, 'fraction_numerical': 0.001538540437971178, 'mean_word_length': 1.4414673844998123, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 11, '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': '3912606', 'n_tokens_mistral': 5228, 'n_tokens_neox': 5118, 'n_words': 1285}