<commit_before>
/* =========================================================================
 * Svelto - Widgets - Form Validate
 * =========================================================================
 * Copyright (c) 2015-2016 Fabio Spampinato
 * Licensed under MIT (https://github.com/svelto/svelto/blob/master/LICENSE)
 * =========================================================================
 * @require core/widget/widget.js
 * @require lib/validator/validator.js
 * @require widgets/toast/toast.js
 * ========================================================================= */

//FIXME: If a form gets autofilled with `SafeInCloud` the value doesn't get updated (it probably doesn't trigger 'change') -> force a value refresh when submitting
//TODO: Add support for multiple checkboxes validation
//TODO: Add meta validators that accepts other validators as arguments, for example not[email], oppure not[matches[1,2,3]] oppure or[email,url] etc... maybe write it this way: or[matches(1-2-3)/matches(a-b-c)], or just use a smarter regex

(function ( $, _, Svelto, Widgets, Factory, Validator ) {

  'use strict';

  /* CONFIG */

  let config = {
    name: 'formValidate',
    plugin: true,
    selector: 'form.validate',
    templates: {
      message: '<p class="form-validate-message <%= o.validity %>">' +
                 '<%= o.message %>' +
               '
',
      messages: '<ul class="form-validate-message <%= o.validity %>">' +
                  '<% for ( var i = 0, l = o.messages.length; i < l; i++ ) { %>' +
                    '
<%= o.messages[i] %>
' +
                  '<% } %>' +
                '
'
    },
    options: {
      validators: { // If not found here it will use `Validator`'s validators
        required ( value ) {
          return !Validator.empty ( value );
        },
        values ( value, ...values ) {
          return Validator.included ( value, values );
        },
        field ( value, fieldName ) {
          let fieldValue = _.find ( this.elements, { name: fieldName } ).value;
          return ( value === fieldValue );
        },
        checked () {
          return this.element.$element.prop ( 'checked' );
        },
        regex ( value, regex ) {
          return !!value.match ( new RegExp ( regex ) );
        }
      },
      messages: {
        form: {
          invalid: 'The form contains some errors',
        },
        validators: {
          invalid: {
            general: 'This value is not valid',
            alpha: 'Only alphabetical characters are allowed',
            alphanumeric: 'Only alphanumeric characters are allowed',
            hexadecimal: 'Only hexadecimal characters are allowed',
            number: 'Only numbers are allowed',
            integer: 'Only integers numbers are allowed',
            float: 'Only floating point numbers are allowed',
            min: 'The number must be at least $2',
            max: 'The number must be at maximum $2',
            range: 'The number must be between $2 and $3',
            minLength: 'The length must be at least $2',
            maxLength: 'The length must be at maximum $2',
            rangeLength: 'The length must be between $2 and $3',
            exactLength: 'The length must be exactly $2',
            email: 'Enter a valid email address',
            creditCard: 'Enter a valid credit card number',
            ssn: 'Enter a valid Social Security Number',
            ipv4: 'Enter a valid IPv4 address',
            url: 'Enter a valid URL',
            required: 'This field is required',
            values: 'This value is not allowed',
            field: 'The two fields don\'t match',
            checked: 'This must be checked'
          }
        }
      },
      characters: {
        separators: {
          validations: '|',
          arguments: ','
        }
      },
      regexes: {
        validation: /^([^\[]+)(?:\[(.*)\])?$/
      },
      datas: {
        id: '_fveid',
        validations: 'validations',
        messages: {
          invalid: 'invalid',
          valid: 'valid'
        }
      },
      classes: {
        invalid: 'invalid',
        valid: 'valid'
      },
      selectors: {
        element: 'input:not([type="button"]), textarea, select',
        textfield: 'input:not([type="button"]):not([type="checkbox"]):not([type="radio"]), textarea',
        wrapper: '.checkbox, .colorpicker, .datepicker, .radio, .select, .slider, .switch'
      }
    }
  };

  /* FORM VALIDATE */

  class FormValidate extends Widgets.Widget {

    /* SPECIAL */

    _variables () {

      this.$form = this.$element;
      this.$elements = this.$form.find ( this.options.selectors.element );
      this.$textfields = this.$elements.filter ( this.options.selectors.textfield );

      this.___elements ();

    }

    _events () {

      this.___change ();
      this.___focus ();
      this.___blur ();
      this.___submit ();

    }

    /* ELEMENTS */

    ___elements () {

      this.elements = {};

      for ( let element of this.$elements ) {

        let $element = $(element),
            $wrappers = $element.parents ( this.options.selectors.wrapper ),
            $wrapper = $wrappers.length ? $wrappers.first () : $element,
            id = $.guid++,
            validationsStr = $element.data ( this.options.datas.validations ),
            validations = false;

        if ( validationsStr ) {

          validations = {};

          let validationsArr = validationsStr.split ( this.options.characters.separators.validations );

          for ( let validationStr of validationsArr ) {

            let matches = validationStr.match ( this.options.regexes.validation );

            if ( !matches ) continue;

            let validationName = matches[1],
                validationArgs = matches[2] ? matches[2].split ( this.options.characters.separators.arguments ) : [],
                validator = this.options.validators[validationName] || Validator[validationName];

            if ( !validator ) continue;

            validations[validationName] = {
              args: validationArgs,
              validator: validator
            };

          }

          if ( _.isEmpty ( validations ) ) {

            validations = false;

          }

        }

        element[this.options.datas.id] = id;

        this.elements[id] = {
          id: id,
          $element: $element,
          $wrapper: $wrapper,
          $message: false,
          name: element.name,
          value: $element.val (),
          validations: validations,
          isDirty: false,
          isValid: undefined,
          messages: {
            invalid: $wrapper.data ( this.options.datas.messages.invalid ),
            valid: $wrapper.data ( this.options.datas.messages.valid )
          }
        };

      }

    }

    /* CHANGE */

    ___change () {

      this._on ( true, this.$elements, 'change', this.__change );

    }

    __change ( event ) {

      /* FORM */

      this._isValid = undefined;

      /* ELEMENT */

      let elementObj = this.elements[event.currentTarget[this.options.datas.id]];

      elementObj.isDirty = true;
      elementObj.isValid = undefined;

      this._validateWorker ( elementObj );

      /* OTHERS */

      for ( let id in this.elements ) {

        if ( this.elements.hasOwnProperty ( id ) ) {

          if ( id === elementObj.id ) continue;

          let otherElementObj = this.elements[id],
              isDepending = otherElementObj.validations && 'field' in otherElementObj.validations && otherElementObj.validations.field.args.indexOf ( elementObj.name ) !== -1,
              hasSameName = !_.isEmpty ( elementObj.name ) && otherElementObj.name === elementObj.name;

          if ( isDepending || hasSameName ) {

            otherElementObj.isValid = undefined;

            this._validateWorker ( otherElementObj );

          }

        }

      }

    }

    /* FOCUS */

    ___focus () {

      this._on ( this.$textfields, 'focus', this.__focus );

    }

    __focus ( event ) {

      let elementObj = this.elements[event.currentTarget[this.options.datas.id]];

      elementObj.isValid = undefined;

      this.__indeterminate ( elementObj );

    }

    /* BLUR */

    ___blur () {

      this._on ( this.$textfields, 'blur', this.__blur );

    }

    __blur ( event ) {

      let elementObj = this.elements[event.currentTarget[this.options.datas.id]];

      this._validateWorker ( elementObj );

    }

    /* SUBMIT */

    ___submit () {

      this._on ( true, 'submit', this.__submit );

    }

    __submit ( event ) {

      if ( !this.isValid () ) {

        event.preventDefault ();
        event.stopImmediatePropagation ();

        $.toast ( this.options.messages.form.invalid );

      }

    }

    /* VALIDATION */

    _validateWorker ( elementObj ) {

      if ( _.isUndefined ( elementObj.isValid ) ) {

        let result = this._validate ( elementObj ),
            isValid = ( result === true );

        elementObj.isValid = isValid;

        if ( isValid ) {

          this.__valid ( elementObj );

        } else {

          this.__invalid ( elementObj, result );

        }

      }

    }

    _validate ( elementObj ) {

      let errors = [],
          validations = elementObj.validations;

      if ( elementObj.isDirty ) {

        elementObj.value = elementObj.$element.val ();

        elementObj.isDirty = false;

      }

      if ( validations ) {

        for ( let name in validations ) {

          if ( validations.hasOwnProperty ( name ) ) {

            let validation = validations[name],
                isValid = validation.validator.apply ( { elements: this.elements, element: elementObj }, [elementObj.value].concat ( validation.args ) );

            if ( !isValid ) {

              let error = _.format ( this.options.messages.validators.invalid[name] || this.options.messages.validators.invalid.general, elementObj.value, ...validation.args );

              errors.push ( error );

            }

          }

        }

      }

      return _.isEmpty ( errors ) ? true : errors;

    }

    /* STATE */

    __indeterminate ( elementObj ) {

      elementObj.$wrapper.removeClass ( this.options.classes.invalid + ' ' + this.options.classes.valid );

      this._updateMessage ( elementObj, false );

    }

    __valid ( elementObj ) {

      elementObj.$wrapper.removeClass ( this.options.classes.invalid ).addClass ( this.options.classes.valid );

      this._updateMessage ( elementObj, elementObj.messages.valid );

    }

    __invalid ( elementObj, errors ) {

      elementObj.$wrapper.removeClass ( this.options.classes.valid ).addClass ( this.options.classes.invalid );

      this._updateMessage ( elementObj, elementObj.messages.invalid || errors );

    }

    /* ERRORS */

    _updateMessage ( elementObj, message ) {

      if ( elementObj.$message ) {

        elementObj.$message.remove ();

      }

      if ( message ) {

        let validity = elementObj.isValid ? this.options.classes.valid : this.options.classes.invalid,
            msgHtml = _.isString ( message )
                        ? this._template ( 'message', { message: message, validity: validity } )
                        : message.length === 1
                          ? this._template ( 'message', { message: message[0], validity: validity } )
                          : this._template ( 'messages', { messages: message, validity: validity } );

        elementObj.$message = $(msgHtml);

        elementObj.$wrapper.after ( elementObj.$message );

      } else {

        elementObj.$message = false;

      }

    }

    /* API */

    isValid () {

      if ( _.isUndefined ( this._isValid ) ) {

        for ( let id in this.elements ) {

          if ( this.elements.hasOwnProperty ( id ) ) {

            let elementObj = this.elements[id];

            if ( _.isUndefined ( elementObj.isValid ) ) {

              this._validateWorker ( elementObj );

            }

            if ( !elementObj.isValid ) {

              this._isValid = false;

            }

          }

        }

        if ( _.isUndefined ( this._isValid ) ) {

          this._isValid = true;

        }

      }

      return this._isValid;

    }

  }

  /* FACTORY */

  Factory.init ( FormValidate, config, Widgets );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory, Svelto.Validator ));
<commit_msg>Added `.editor`as a recognized wrapper element to formValidate<commit_after>
/* =========================================================================
 * Svelto - Widgets - Form Validate
 * =========================================================================
 * Copyright (c) 2015-2016 Fabio Spampinato
 * Licensed under MIT (https://github.com/svelto/svelto/blob/master/LICENSE)
 * =========================================================================
 * @require core/widget/widget.js
 * @require lib/validator/validator.js
 * @require widgets/toast/toast.js
 * ========================================================================= */

//FIXME: If a form gets autofilled with `SafeInCloud` the value doesn't get updated (it probably doesn't trigger 'change') -> force a value refresh when submitting
//TODO: Add support for multiple checkboxes validation
//TODO: Add meta validators that accepts other validators as arguments, for example not[email], oppure not[matches[1,2,3]] oppure or[email,url] etc... maybe write it this way: or[matches(1-2-3)/matches(a-b-c)], or just use a smarter regex
//TODO: Maybe make it generic and just call it `validate`

(function ( $, _, Svelto, Widgets, Factory, Validator ) {

  'use strict';

  /* CONFIG */

  let config = {
    name: 'formValidate',
    plugin: true,
    selector: 'form.validate',
    templates: {
      message: '<p class="form-validate-message <%= o.validity %>">' +
                 '<%= o.message %>' +
               '
',
      messages: '<ul class="form-validate-message <%= o.validity %>">' +
                  '<% for ( var i = 0, l = o.messages.length; i < l; i++ ) { %>' +
                    '
<%= o.messages[i] %>
' +
                  '<% } %>' +
                '
'
    },
    options: {
      validators: { // If not found here it will use `Validator`'s validators
        required ( value ) {
          return !Validator.empty ( value );
        },
        values ( value, ...values ) {
          return Validator.included ( value, values );
        },
        field ( value, fieldName ) {
          let fieldValue = _.find ( this.elements, { name: fieldName } ).value;
          return ( value === fieldValue );
        },
        checked () {
          return this.element.$element.prop ( 'checked' );
        },
        regex ( value, regex ) {
          return !!value.match ( new RegExp ( regex ) );
        }
      },
      messages: {
        form: {
          invalid: 'The form contains some errors',
        },
        validators: {
          invalid: {
            general: 'This value is not valid',
            alpha: 'Only alphabetical characters are allowed',
            alphanumeric: 'Only alphanumeric characters are allowed',
            hexadecimal: 'Only hexadecimal characters are allowed',
            number: 'Only numbers are allowed',
            integer: 'Only integers numbers are allowed',
            float: 'Only floating point numbers are allowed',
            min: 'The number must be at least $2',
            max: 'The number must be at maximum $2',
            range: 'The number must be between $2 and $3',
            minLength: 'The length must be at least $2',
            maxLength: 'The length must be at maximum $2',
            rangeLength: 'The length must be between $2 and $3',
            exactLength: 'The length must be exactly $2',
            email: 'Enter a valid email address',
            creditCard: 'Enter a valid credit card number',
            ssn: 'Enter a valid Social Security Number',
            ipv4: 'Enter a valid IPv4 address',
            url: 'Enter a valid URL',
            required: 'This field is required',
            values: 'This value is not allowed',
            field: 'The two fields don\'t match',
            checked: 'This must be checked'
          }
        }
      },
      characters: {
        separators: {
          validations: '|',
          arguments: ','
        }
      },
      regexes: {
        validation: /^([^\[]+)(?:\[(.*)\])?$/
      },
      datas: {
        id: '_fveid',
        validations: 'validations',
        messages: {
          invalid: 'invalid',
          valid: 'valid'
        }
      },
      classes: {
        invalid: 'invalid',
        valid: 'valid'
      },
      selectors: {
        element: 'input:not([type="button"]), textarea, select',
        textfield: 'input:not([type="button"]):not([type="checkbox"]):not([type="radio"]), textarea',
        wrapper: '.checkbox, .colorpicker, .datepicker, .editor, .radio, .select, .slider, .switch'
      }
    }
  };

  /* FORM VALIDATE */

  class FormValidate extends Widgets.Widget {

    /* SPECIAL */

    _variables () {

      this.$form = this.$element;
      this.$elements = this.$form.find ( this.options.selectors.element );
      this.$textfields = this.$elements.filter ( this.options.selectors.textfield );

      this.___elements ();

    }

    _events () {

      this.___change ();
      this.___focus ();
      this.___blur ();
      this.___submit ();

    }

    /* ELEMENTS */

    ___elements () {

      this.elements = {};

      for ( let element of this.$elements ) {

        let $element = $(element),
            $wrappers = $element.parents ( this.options.selectors.wrapper ),
            $wrapper = $wrappers.length ? $wrappers.first () : $element,
            id = $.guid++,
            validationsStr = $element.data ( this.options.datas.validations ),
            validations = false;

        if ( validationsStr ) {

          validations = {};

          let validationsArr = validationsStr.split ( this.options.characters.separators.validations );

          for ( let validationStr of validationsArr ) {

            let matches = validationStr.match ( this.options.regexes.validation );

            if ( !matches ) continue;

            let validationName = matches[1],
                validationArgs = matches[2] ? matches[2].split ( this.options.characters.separators.arguments ) : [],
                validator = this.options.validators[validationName] || Validator[validationName];

            if ( !validator ) continue;

            validations[validationName] = {
              args: validationArgs,
              validator: validator
            };

          }

          if ( _.isEmpty ( validations ) ) {

            validations = false;

          }

        }

        element[this.options.datas.id] = id;

        this.elements[id] = {
          id: id,
          $element: $element,
          $wrapper: $wrapper,
          $message: false,
          name: element.name,
          value: $element.val (),
          validations: validations,
          isDirty: false,
          isValid: undefined,
          messages: {
            invalid: $wrapper.data ( this.options.datas.messages.invalid ),
            valid: $wrapper.data ( this.options.datas.messages.valid )
          }
        };

      }

    }

    /* CHANGE */

    ___change () {

      this._on ( true, this.$elements, 'change', this.__change );

    }

    __change ( event ) {

      /* FORM */

      this._isValid = undefined;

      /* ELEMENT */

      let elementObj = this.elements[event.currentTarget[this.options.datas.id]];

      elementObj.isDirty = true;
      elementObj.isValid = undefined;

      this._validateWorker ( elementObj );

      /* OTHERS */

      for ( let id in this.elements ) {

        if ( this.elements.hasOwnProperty ( id ) ) {

          if ( id === elementObj.id ) continue;

          let otherElementObj = this.elements[id],
              isDepending = otherElementObj.validations && 'field' in otherElementObj.validations && otherElementObj.validations.field.args.indexOf ( elementObj.name ) !== -1,
              hasSameName = !_.isEmpty ( elementObj.name ) && otherElementObj.name === elementObj.name;

          if ( isDepending || hasSameName ) {

            otherElementObj.isValid = undefined;

            this._validateWorker ( otherElementObj );

          }

        }

      }

    }

    /* FOCUS */

    ___focus () {

      this._on ( this.$textfields, 'focus', this.__focus );

    }

    __focus ( event ) {

      let elementObj = this.elements[event.currentTarget[this.options.datas.id]];

      elementObj.isValid = undefined;

      this.__indeterminate ( elementObj );

    }

    /* BLUR */

    ___blur () {

      this._on ( this.$textfields, 'blur', this.__blur );

    }

    __blur ( event ) {

      let elementObj = this.elements[event.currentTarget[this.options.datas.id]];

      this._validateWorker ( elementObj );

    }

    /* SUBMIT */

    ___submit () {

      this._on ( true, 'submit', this.__submit );

    }

    __submit ( event ) {

      if ( !this.isValid () ) {

        event.preventDefault ();
        event.stopImmediatePropagation ();

        $.toast ( this.options.messages.form.invalid );

      }

    }

    /* VALIDATION */

    _validateWorker ( elementObj ) {

      if ( _.isUndefined ( elementObj.isValid ) ) {

        let result = this._validate ( elementObj ),
            isValid = ( result === true );

        elementObj.isValid = isValid;

        if ( isValid ) {

          this.__valid ( elementObj );

        } else {

          this.__invalid ( elementObj, result );

        }

      }

    }

    _validate ( elementObj ) {

      let errors = [],
          validations = elementObj.validations;

      if ( elementObj.isDirty ) {

        elementObj.value = elementObj.$element.val ();

        elementObj.isDirty = false;

      }

      if ( validations ) {

        for ( let name in validations ) {

          if ( validations.hasOwnProperty ( name ) ) {

            let validation = validations[name],
                isValid = validation.validator.apply ( { elements: this.elements, element: elementObj }, [elementObj.value].concat ( validation.args ) );

            if ( !isValid ) {

              let error = _.format ( this.options.messages.validators.invalid[name] || this.options.messages.validators.invalid.general, elementObj.value, ...validation.args );

              errors.push ( error );

            }

          }

        }

      }

      return _.isEmpty ( errors ) ? true : errors;

    }

    /* STATE */

    __indeterminate ( elementObj ) {

      elementObj.$wrapper.removeClass ( this.options.classes.invalid + ' ' + this.options.classes.valid );

      this._updateMessage ( elementObj, false );

    }

    __valid ( elementObj ) {

      elementObj.$wrapper.removeClass ( this.options.classes.invalid ).addClass ( this.options.classes.valid );

      this._updateMessage ( elementObj, elementObj.messages.valid );

    }

    __invalid ( elementObj, errors ) {

      elementObj.$wrapper.removeClass ( this.options.classes.valid ).addClass ( this.options.classes.invalid );

      this._updateMessage ( elementObj, elementObj.messages.invalid || errors );

    }

    /* ERRORS */

    _updateMessage ( elementObj, message ) {

      if ( elementObj.$message ) {

        elementObj.$message.remove ();

      }

      if ( message ) {

        let validity = elementObj.isValid ? this.options.classes.valid : this.options.classes.invalid,
            msgHtml = _.isString ( message )
                        ? this._template ( 'message', { message: message, validity: validity } )
                        : message.length === 1
                          ? this._template ( 'message', { message: message[0], validity: validity } )
                          : this._template ( 'messages', { messages: message, validity: validity } );

        elementObj.$message = $(msgHtml);

        elementObj.$wrapper.after ( elementObj.$message );

      } else {

        elementObj.$message = false;

      }

    }

    /* API */

    isValid () {

      if ( _.isUndefined ( this._isValid ) ) {

        for ( let id in this.elements ) {

          if ( this.elements.hasOwnProperty ( id ) ) {

            let elementObj = this.elements[id];

            if ( _.isUndefined ( elementObj.isValid ) ) {

              this._validateWorker ( elementObj );

            }

            if ( !elementObj.isValid ) {

              this._isValid = false;

            }

          }

        }

        if ( _.isUndefined ( this._isValid ) ) {

          this._isValid = true;

        }

      }

      return this._isValid;

    }

  }

  /* FACTORY */

  Factory.init ( FormValidate, config, Widgets );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory, Svelto.Validator ));
<|endoftext|>


<commit_before>
describe 'Express'
  before_each
    reset()
    use(require('express/plugins/cache').Cache)
    cache = require('express/plugins/cache')
  end

  describe 'Cache'
    describe 'Request'
      describe '#cache'
        it 'should use memory store by default'
          get('/item', function(){
            return this.cache.toString()
          })
          get('/item').body.should.eql '[Memory Store]'
        end
      end
    end
  end

  describe 'cache Store.Memory'
    before_each
      store = new cache.Store.Memory
    end

    describe '#toString()'
      it 'should return [Memory Store]'
        store.toString().should.eql '[Memory Store]'
      end
    end

    describe '#set()'
      describe 'given a key and value'
        it 'should set the cache data'
          store.set('foo', 'bar')
          store.get('foo').should.eql 'bar'
        end

        it 'should override existing data'
          store.set('foo', 'bar')
          store.set('foo', 'baz')
          store.get('foo').should.eql 'baz'
        end

        it 'should return data'
          store.set('foo', 'bar').should.eql 'bar'
        end
      end

      describe 'given an abitrary key'
        it 'should throw an error'
          -{ store.set({}, 'foo') }.should.throw_error
        end
      end

      describe 'given an abitrary value'
        it 'should serialize as JSON'
          store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
        end
      end
    end

    describe '#get()'
      describe 'given a key'
        it 'should return cached value'
          store.set('foo', 'bar')
          store.get('foo').should.eql 'bar'
        end

        it 'should unserialize JSON data'
          store.set('user', { name: 'tj' })
          store.get('user').should.eql { name: 'tj' }
        end
      end

      describe 'given wildcards'
        it 'should return a set of caches'
          store.set('user:1', 'a')
          store.set('user:2', 'b')
          store.set('foo', 'bar')
          store.get('user:*').should.eql { 'user:1': 'a', 'user:2': 'b' }
        end
      end
    end

    describe '#clear()'
      describe 'given a key'
        it 'should delete previous data'
          store.set('foo', 'bar')
          store.clear('foo')
          store.get('foo').should.be_null
        end
      end

      describe 'given wildcards'
        it 'should clear a set of caches'
          store.set('user:one', '1')
          store.set('user:two', '2')
          store.clear('user:*')
          store.get('user:one').should.be_null
          store.get('user:two').should.be_null
        end
      end
    end

    describe '#reap()'
      it 'should destroy caches older than the given age in milliseconds'
        store.set('user:one', '1')
        store.data['user:one'].created = Number(new Date) - 300
        store.set('user:two', '2')
        store.data['user:two'].created = Number(new Date) - 100
        store.reap(200)
        store.get('user:one').should.be_null
        store.get('user:two').should.not.be_null
      end
    end
  end
end<commit_msg>Async cache specs<commit_after>
describe 'Express'
  before_each
    reset()
    use(require('express/plugins/cache').Cache)
    cache = require('express/plugins/cache')
  end

  describe 'Cache'
    describe 'Request'
      describe '#cache'
        it 'should use memory store by default'
          get('/item', function(){
            return this.cache.toString()
          })
          get('/item').body.should.eql '[Memory Store]'
        end
      end
    end
  end

  describe 'cache Store.Memory'
    before_each
      store = new cache.Store.Memory
    end

    describe '#toString()'
      it 'should return [Memory Store]'
        store.toString().should.eql '[Memory Store]'
      end
    end

    describe '#set()'
      describe 'given a key and value'
        it 'should set the cache data'
          var result
          store.set('foo', 'bar', function(){
            store.get('foo', function(val){
              result = val
            })
          })
          result.should.eql 'bar'
        end

        it 'should override existing data'
          var result
          store.set('foo', 'bar', function(){
            store.set('foo', 'baz', function(){
              store.get('foo', function(val){
                result = val
              })
            })
          })
          result.should.eql 'baz'
        end
      end

      describe 'given an abitrary key'
        it 'should throw an error'
          -{ store.set({}, 'foo') }.should.throw_error
        end
      end

      describe 'given an abitrary value'
        it 'should serialize as JSON'
          var result
          store.set('user', { name: 'tj' }, function(val){
            result = val
          })
          result.should.eql { name: 'tj' }
        end
      end
    end

    describe '#get()'
      describe 'given a key'
        it 'should unserialize JSON data'
          var result
          store.set('user', { name: 'tj' }, function(){
            store.get('user', function(val){
              result = val
            })
          })
          result.should.eql { name: 'tj' }
        end
      end

      describe 'given wildcards'
        it 'should pass a set of caches'
          var result
          store.set('user:1', 'a', function(){
            store.set('user:2', 'b', function(){
              store.set('foo', 'bar', function(){
                store.get('user:*', function(val){
                  result = val
                })
              })
            })
          })
          result.should.eql { 'user:1': 'a', 'user:2': 'b' }
        end
      end
    end

    describe '#clear()'
      describe 'given a key'
        it 'should delete previous data'
          var result
          store.set('foo', 'bar', function(){
            store.clear('foo', function(){
              store.get('foo', function(val){
                result = val
              })
            })
          })
          result.should.be_null
        end
      end

      describe 'given wildcards'
        it 'should clear a set of caches'
          var results = []
          store.set('user:one', '1', function(){
            store.set('user:two', '2', function(){
              store.clear('user:*', function(){
                store.get('user:one', function(val){
                  results.push(val)
                  store.get('user:two', function(val){
                    results.push(val)
                  })
                })
              })
            })
          })
          results.should.eql [null, nukk]
        end
      end
    end

    describe '#reap()'
      it 'should destroy caches older than the given age in milliseconds'
        store.set('user:one', '1')
        store.data['user:one'].created = Number(new Date) - 300
        store.set('user:two', '2')
        store.data['user:two'].created = Number(new Date) - 100
        store.reap(200)
        store.get('user:one').should.be_null
        store.get('user:two').should.not.be_null
      end
    end
  end
end<|endoftext|>


<commit_before>Ext.namespace('Vps.Auto');
Vps.Auto.SyncTreePanel = Ext.extend(Vps.Binding.AbstractPanel, {

    layout: 'fit',

    initComponent : function()
    {
        if (this.autoLoad !== false) {
            this.autoLoad = true;
        } else {
            delete this.autoLoad;
        }

        this.addEvents(
            'selectionchange',
            'editaction',
            'addaction'
        );
        this.actions['delete'] = new Ext.Action({
            text    : trlVps('Delete'),
            handler : this.onDelete,
            cls     : 'x-btn-text-icon',
            disabled: true,
            scope   : this
        });
        this.actions.add = new Ext.Action({
            text    : trlVps('Add'),
            handler : this.onAdd,
            cls     : 'x-btn-text-icon',
            scope   : this
        });
        this.actions.edit = new Ext.Action({
            text    : trlVps('Edit'),
            handler : this.onEdit,
            cls     : 'x-btn-text-icon',
            disabled: true,
            scope   : this
        });
        this.actions.invisible = new Ext.Action({
            text    : trlVps('Toggle Visibility'),
            handler : this.onVisible,
            cls     : 'x-btn-text-icon',
            disabled: true,
            scope   : this
        });
        this.actions.reload = new Ext.Action({
            text    : '',
            handler : function () { this.tree.getRootNode().reload(); },
            icon    : '/assets/silkicons/arrow_rotate_clockwise.png',
            cls     : 'x-btn-icon',
            tooltip : trlVps('Reload'),
            scope   : this
        });

        Vps.Auto.SyncTreePanel.superclass.initComponent.call(this);
    },
    doAutoLoad : function()
    {
        //autoLoad kann in der zwischenzeit abgeschaltet werden, zB wenn
        //wir in einem Binding sind
        if (!this.autoLoad) return;
        this.loadMeta();
    },

    loadMeta: function()
    {
        Ext.Ajax.request({
            mask: true,
            url: this.controllerUrl + '/json-meta',
            params: this.baseParams,
            success: this.onMetaChange,
            scope: this
        });
    },

    onMetaChange: function(response, options, meta) {
        this.icons = meta.icons;
        for (var i in this.icons) {
            if (i in this.actions) {
                this.actions[i].initialConfig.icon = this.icons[i];
            }
        }

        // Toolbar
        if (meta.buttons.each == undefined) { // Abfrage nötig, falls keine Buttons geliefert
            var tbar = [];
            for (var button in meta.buttons) {
                tbar.add(this.getAction(button));
            }
        }

        // Tree
        var baseParams = this.baseParams != undefined ? this.baseParams : {};
        if (this.openedId != undefined) { baseParams.openedId = this.openedId; }
        this.tree = new Ext.tree.TreePanel({
            border      : false,
//            animate     : true,
            loader      : new Ext.tree.TreeLoader({
                baseParams  : baseParams,
                dataUrl     : this.controllerUrl + '/json-data'
            }),
            enableDD    : meta.enableDD,
            autoScroll: true,
            rootVisible : meta.rootVisible,
            tbar        : tbar,
            dropConfig  : meta.dropConfig
        });

        this.tree.setRootNode(
            new Ext.tree.AsyncTreeNode({
                text: meta.rootText,
                id: '0',
                allowDrag: false
            })
        );

        this.tree.getSelectionModel().on('selectionchange', this.onSelectionchange, this);
        this.tree.getSelectionModel().on('beforeselect', function(selModel, newNode, oldNode) {
            return this.fireEvent('beforeselectionchange', newNode.attributes.id);
        }, this);
        this.tree.on('beforenodedrop', this.onMove, this);
        this.tree.on('collapsenode', this.onCollapseNode, this);
        this.tree.on('expandnode', this.onExpandNode, this);

        this.tree.on('load', function(node) {
            if (this.openedId == node.id) {
                node.select();
            }
            return true;
        }, this);

        this.relayEvents(this.tree, ['click', 'dblclick']);

        this.add(this.tree);
        this.doLayout();

        this.tree.getRootNode().expand();
        if (meta.rootVisible) {
            this.tree.getRootNode().ui.iconNode.style.backgroundImage = 'url(' + meta.icons.root + ')';
            this.tree.getRootNode().select();
        }

        if (!this.editDialog && meta.editDialog) {
            this.editDialog = meta.editDialog;
        }
        if (this.editDialog && !(this.editDialog instanceof Vps.Auto.Form.Window)) {
            this.editDialog = new Vps.Auto.Form.Window(meta.editDialog);
        }
        if (this.editDialog) {
            this.editDialog.on('datachange', function(o) {
                if (o.data.addedId != undefined) {
                    id = o.data.addedId;
                } else {
                    id = this.tree.getSelectionModel().getSelectedNode().id;
                }
                this.onSave(id);
            }, this);
            this.tree.on('dblclick', function(grid, rowIndex) {
                this.onEdit();
            }, this);
            if (this.editDialog.applyBaseParams) {
                this.editDialog.applyBaseParams(this.getBaseParams());
            }
        }

        this.fireEvent('loaded', this.tree);
    },

    onEdit : function (o, e) {
        var node = this.tree.getSelectionModel().getSelectedNode();
        if (!node.id) return;
        if (this.editDialog != undefined) {
            this.editDialog.showEdit(node.id);
        } else {
            this.fireEvent('editaction', node);
        }
    },

    onAdd: function (o, e) {
        if (this.editDialog != undefined) {
            this.editDialog.getAutoForm().applyBaseParams({
                parent_id: this.getSelectedId()
            });
            this.editDialog.showAdd();
        } else {
            this.fireEvent('addaction', this.tree.getSelectionModel().getSelectedNode());
        }
    },

    onSave : function (id)
    {
        Ext.Ajax.request({
            url: this.controllerUrl + '/json-node-data',
            params: Ext.apply({node:id}, this.getBaseParams()),
            success: function(response, options, result) {
                this.onSaved(result.data);
            },
            scope: this
        })
    },

    onSelectionchange: function (selModel, node) {
        if (node && node.id != 0) {
            this.getAction('edit').enable();
            this.getAction('invisible').enable();
            this.getAction('delete').enable();
        } else {
            this.getAction('edit').disable();
            this.getAction('invisible').disable();
            this.getAction('delete').disable();
        }
        this.fireEvent('selectionchange', node);
    },

    onDelete: function (o, e) {
        Ext.MessageBox.confirm(trlVps('Delete'), trlVps('Do you really want to delete this entry:\n\n"') + this.tree.getSelectionModel().getSelectedNode().text + '"',
            function  (button) {
                if (button == 'yes') {
                    Ext.Ajax.request({
                        url: this.controllerUrl + '/json-delete',
                        params: Ext.apply({id:this.getSelectedId()}, this.getBaseParams()),
                        success: function(response, options, result) {
                            this.onDeleted(result);
                        },
                        scope: this
                    });
                }
            },
            this
        );
    },

    onMove : function(dropEvent){
        var params = this.getBaseParams()
        params.source = dropEvent.dropNode.id;
        params.target = dropEvent.target.id;
        params.point = dropEvent.point;
        Ext.Ajax.request({
            url: this.controllerUrl + '/json-move',
            params: params,
            success: function(response, options, result) {
                if (result.parent) {
                    var parent = this.tree.getNodeById(result.parent);
                } else {
                    var parent = this.tree.getRootNode();
                }
                var node = this.tree.getNodeById(result.node);
                var before = this.tree.getNodeById(result.before);
                parent.insertBefore(node, before);
                parent.expand();
            },
            failure: function(r) {
                this.tree.getRootNode().reload();
            },
            scope: this
        });
        dropEvent.dropStatus = true;
        dropEvent.cancel = true;
        return true;
    },

    onCollapseNode : function(node) {
        Ext.Ajax.request({
            url: this.controllerUrl + '/json-collapse',
            params: Ext.apply({id:node.id}, this.getBaseParams())
        });
    },

    onExpandNode : function(node) {
        if (node.attributes.children && node.attributes.children.length > 0) {
            Ext.Ajax.request({
                url: this.controllerUrl + '/json-expand',
                params: Ext.apply({id:node.id}, this.getBaseParams())
            });
        }
    },

    onVisible : function (o, e) {
        Ext.Ajax.request({
            url: this.controllerUrl + '/json-visible',
            params: Ext.apply({id:this.getSelectedId()}, this.getBaseParams()),
            success: function(response, options, result) {
                node = this.tree.getNodeById(result.id);
                node.attributes.visible = result.visible;
                this.setVisible(node);
            },
            scope: this
        })
    },

    setVisible : function (node) {
        if (node.attributes.visible) {
            node.ui.iconNode.style.backgroundImage = 'url(' + this.icons['default'] + ')';
        } else {
            node.ui.iconNode.style.backgroundImage = 'url(' + this.icons['invisible'] + ')';
        }
    },

    getTree : function() {
        return this.tree;
    },
    getSelectionModel : function() {
        if (!this.getTree()) return null;
        return this.getTree().getSelectionModel();
    },
    getSelectedNode : function() {
        if (!this.getSelectionModel()) return null;
        return this.getSelectionModel().getSelectedNode();
    },

    //für AbstractPanel
    getSelectedId: function() {
        var s = this.getSelectedNode();
        if (s) return s.id;
        return null;
    },

    //für AbstractPanel
    selectId: function(id) {
        if (id) {
            if (this.getTree()) {
                var n = this.getTree().getNodeById(id);
                if (n) {
                    n.select();
                }
            } else {
                this.openedId = id;
            }
        } else {
            var m = this.getSelectionModel();
            if (m) m.clearSelections();
        }
    },

    onSaved : function (response)
    {
        this.tree.getRootNode().reload();
    },

    onDeleted: function (response) {
        this.tree.getRootNode().reload();
    },

    setBaseParams : function(baseParams) {
        Vps.Auto.SyncTreePanel.superclass.setBaseParams.apply(this, arguments);
        if (this.editDialog && this.editDialog.setBaseParams) {
            this.editDialog.setBaseParams(baseParams);
        }
    },
    applyBaseParams : function(baseParams) {
        Vps.Auto.SyncTreePanel.superclass.applyBaseParams.apply(this, arguments);
        if (this.editDialog && this.editDialog.applyBaseParams) {
            this.editDialog.applyBaseParams(baseParams);
        }
    }

});
Ext.reg('vps.autotreesync', Vps.Auto.SyncTreePanel);
<commit_msg>kleiner fix im synctree: root kann nie editiert werden<commit_after>Ext.namespace('Vps.Auto');
Vps.Auto.SyncTreePanel = Ext.extend(Vps.Binding.AbstractPanel, {

    layout: 'fit',

    initComponent : function()
    {
        if (this.autoLoad !== false) {
            this.autoLoad = true;
        } else {
            delete this.autoLoad;
        }

        this.addEvents(
            'selectionchange',
            'editaction',
            'addaction'
        );
        this.actions['delete'] = new Ext.Action({
            text    : trlVps('Delete'),
            handler : this.onDelete,
            cls     : 'x-btn-text-icon',
            disabled: true,
            scope   : this
        });
        this.actions.add = new Ext.Action({
            text    : trlVps('Add'),
            handler : this.onAdd,
            cls     : 'x-btn-text-icon',
            scope   : this
        });
        this.actions.edit = new Ext.Action({
            text    : trlVps('Edit'),
            handler : this.onEdit,
            cls     : 'x-btn-text-icon',
            disabled: true,
            scope   : this
        });
        this.actions.invisible = new Ext.Action({
            text    : trlVps('Toggle Visibility'),
            handler : this.onVisible,
            cls     : 'x-btn-text-icon',
            disabled: true,
            scope   : this
        });
        this.actions.reload = new Ext.Action({
            text    : '',
            handler : function () { this.tree.getRootNode().reload(); },
            icon    : '/assets/silkicons/arrow_rotate_clockwise.png',
            cls     : 'x-btn-icon',
            tooltip : trlVps('Reload'),
            scope   : this
        });

        Vps.Auto.SyncTreePanel.superclass.initComponent.call(this);
    },
    doAutoLoad : function()
    {
        //autoLoad kann in der zwischenzeit abgeschaltet werden, zB wenn
        //wir in einem Binding sind
        if (!this.autoLoad) return;
        this.loadMeta();
    },

    loadMeta: function()
    {
        Ext.Ajax.request({
            mask: true,
            url: this.controllerUrl + '/json-meta',
            params: this.baseParams,
            success: this.onMetaChange,
            scope: this
        });
    },

    onMetaChange: function(response, options, meta) {
        this.icons = meta.icons;
        for (var i in this.icons) {
            if (i in this.actions) {
                this.actions[i].initialConfig.icon = this.icons[i];
            }
        }

        // Toolbar
        if (meta.buttons.each == undefined) { // Abfrage nötig, falls keine Buttons geliefert
            var tbar = [];
            for (var button in meta.buttons) {
                tbar.add(this.getAction(button));
            }
        }

        // Tree
        var baseParams = this.baseParams != undefined ? this.baseParams : {};
        if (this.openedId != undefined) { baseParams.openedId = this.openedId; }
        this.tree = new Ext.tree.TreePanel({
            border      : false,
//            animate     : true,
            loader      : new Ext.tree.TreeLoader({
                baseParams  : baseParams,
                dataUrl     : this.controllerUrl + '/json-data'
            }),
            enableDD    : meta.enableDD,
            autoScroll: true,
            rootVisible : meta.rootVisible,
            tbar        : tbar,
            dropConfig  : meta.dropConfig
        });

        this.tree.setRootNode(
            new Ext.tree.AsyncTreeNode({
                text: meta.rootText,
                id: '0',
                allowDrag: false
            })
        );

        this.tree.getSelectionModel().on('selectionchange', this.onSelectionchange, this);
        this.tree.getSelectionModel().on('beforeselect', function(selModel, newNode, oldNode) {
            return this.fireEvent('beforeselectionchange', newNode.attributes.id);
        }, this);
        this.tree.on('beforenodedrop', this.onMove, this);
        this.tree.on('collapsenode', this.onCollapseNode, this);
        this.tree.on('expandnode', this.onExpandNode, this);

        this.tree.on('load', function(node) {
            if (this.openedId == node.id) {
                node.select();
            }
            return true;
        }, this);

        this.relayEvents(this.tree, ['click', 'dblclick']);

        this.add(this.tree);
        this.doLayout();

        this.tree.getRootNode().expand();
        if (meta.rootVisible) {
            this.tree.getRootNode().ui.iconNode.style.backgroundImage = 'url(' + meta.icons.root + ')';
            this.tree.getRootNode().select();
        }

        if (!this.editDialog && meta.editDialog) {
            this.editDialog = meta.editDialog;
        }
        if (this.editDialog && !(this.editDialog instanceof Vps.Auto.Form.Window)) {
            this.editDialog = new Vps.Auto.Form.Window(meta.editDialog);
        }
        if (this.editDialog) {
            this.editDialog.on('datachange', function(o) {
                if (o.data.addedId != undefined) {
                    id = o.data.addedId;
                } else {
                    id = this.tree.getSelectionModel().getSelectedNode().id;
                }
                this.onSave(id);
            }, this);
            this.tree.on('dblclick', function(grid, rowIndex) {
                this.onEdit();
            }, this);
            if (this.editDialog.applyBaseParams) {
                this.editDialog.applyBaseParams(this.getBaseParams());
            }
        }

        this.fireEvent('loaded', this.tree);
    },

    onEdit : function (o, e) {
        var node = this.tree.getSelectionModel().getSelectedNode();
        if (!node.id || node.id === 0 || node.id === '0') return;
        if (this.editDialog != undefined) {
            this.editDialog.showEdit(node.id);
        } else {
            this.fireEvent('editaction', node);
        }
    },

    onAdd: function (o, e) {
        if (this.editDialog != undefined) {
            this.editDialog.getAutoForm().applyBaseParams({
                parent_id: this.getSelectedId()
            });
            this.editDialog.showAdd();
        } else {
            this.fireEvent('addaction', this.tree.getSelectionModel().getSelectedNode());
        }
    },

    onSave : function (id)
    {
        Ext.Ajax.request({
            url: this.controllerUrl + '/json-node-data',
            params: Ext.apply({node:id}, this.getBaseParams()),
            success: function(response, options, result) {
                this.onSaved(result.data);
            },
            scope: this
        })
    },

    onSelectionchange: function (selModel, node) {
        if (node && node.id != 0) {
            this.getAction('edit').enable();
            this.getAction('invisible').enable();
            this.getAction('delete').enable();
        } else {
            this.getAction('edit').disable();
            this.getAction('invisible').disable();
            this.getAction('delete').disable();
        }
        this.fireEvent('selectionchange', node);
    },

    onDelete: function (o, e) {
        Ext.MessageBox.confirm(trlVps('Delete'), trlVps('Do you really want to delete this entry:\n\n"') + this.tree.getSelectionModel().getSelectedNode().text + '"',
            function  (button) {
                if (button == 'yes') {
                    Ext.Ajax.request({
                        url: this.controllerUrl + '/json-delete',
                        params: Ext.apply({id:this.getSelectedId()}, this.getBaseParams()),
                        success: function(response, options, result) {
                            this.onDeleted(result);
                        },
                        scope: this
                    });
                }
            },
            this
        );
    },

    onMove : function(dropEvent){
        var params = this.getBaseParams()
        params.source = dropEvent.dropNode.id;
        params.target = dropEvent.target.id;
        params.point = dropEvent.point;
        Ext.Ajax.request({
            url: this.controllerUrl + '/json-move',
            params: params,
            success: function(response, options, result) {
                if (result.parent) {
                    var parent = this.tree.getNodeById(result.parent);
                } else {
                    var parent = this.tree.getRootNode();
                }
                var node = this.tree.getNodeById(result.node);
                var before = this.tree.getNodeById(result.before);
                parent.insertBefore(node, before);
                parent.expand();
            },
            failure: function(r) {
                this.tree.getRootNode().reload();
            },
            scope: this
        });
        dropEvent.dropStatus = true;
        dropEvent.cancel = true;
        return true;
    },

    onCollapseNode : function(node) {
        Ext.Ajax.request({
            url: this.controllerUrl + '/json-collapse',
            params: Ext.apply({id:node.id}, this.getBaseParams())
        });
    },

    onExpandNode : function(node) {
        if (node.attributes.children && node.attributes.children.length > 0) {
            Ext.Ajax.request({
                url: this.controllerUrl + '/json-expand',
                params: Ext.apply({id:node.id}, this.getBaseParams())
            });
        }
    },

    onVisible : function (o, e) {
        Ext.Ajax.request({
            url: this.controllerUrl + '/json-visible',
            params: Ext.apply({id:this.getSelectedId()}, this.getBaseParams()),
            success: function(response, options, result) {
                node = this.tree.getNodeById(result.id);
                node.attributes.visible = result.visible;
                this.setVisible(node);
            },
            scope: this
        })
    },

    setVisible : function (node) {
        if (node.attributes.visible) {
            node.ui.iconNode.style.backgroundImage = 'url(' + this.icons['default'] + ')';
        } else {
            node.ui.iconNode.style.backgroundImage = 'url(' + this.icons['invisible'] + ')';
        }
    },

    getTree : function() {
        return this.tree;
    },
    getSelectionModel : function() {
        if (!this.getTree()) return null;
        return this.getTree().getSelectionModel();
    },
    getSelectedNode : function() {
        if (!this.getSelectionModel()) return null;
        return this.getSelectionModel().getSelectedNode();
    },

    //für AbstractPanel
    getSelectedId: function() {
        var s = this.getSelectedNode();
        if (s) return s.id;
        return null;
    },

    //für AbstractPanel
    selectId: function(id) {
        if (id) {
            if (this.getTree()) {
                var n = this.getTree().getNodeById(id);
                if (n) {
                    n.select();
                }
            } else {
                this.openedId = id;
            }
        } else {
            var m = this.getSelectionModel();
            if (m) m.clearSelections();
        }
    },

    onSaved : function (response)
    {
        this.tree.getRootNode().reload();
    },

    onDeleted: function (response) {
        this.tree.getRootNode().reload();
    },

    setBaseParams : function(baseParams) {
        Vps.Auto.SyncTreePanel.superclass.setBaseParams.apply(this, arguments);
        if (this.editDialog && this.editDialog.setBaseParams) {
            this.editDialog.setBaseParams(baseParams);
        }
    },
    applyBaseParams : function(baseParams) {
        Vps.Auto.SyncTreePanel.superclass.applyBaseParams.apply(this, arguments);
        if (this.editDialog && this.editDialog.applyBaseParams) {
            this.editDialog.applyBaseParams(baseParams);
        }
    }

});
Ext.reg('vps.autotreesync', Vps.Auto.SyncTreePanel);
<|endoftext|>


<commit_before>export { ConvBlockType };

import { Int } from "./ValueDesc_Base.js";

/** Describe id, range, name of ConvBlockType.
 *
 * Convert number value into integer between [ 0, 11 ] representing operation:
 *   -  0: MOBILE_NET_V1_HEAD_BODY_TAIL                      (General Pointwise1-Depthwise1-Pointwise2)
 *                                                           (MobileNetV1 or
 *                                                            MobileNetV2's head)
 *
 *   -  1: MOBILE_NET_V2_BODY_TAIL
 *   -  2: SHUFFLE_NET_V2_HEAD
 *   -  3: SHUFFLE_NET_V2_BODY
 *   -  4: SHUFFLE_NET_V2_TAIL
 *   -  5: SHUFFLE_NET_V2_BY_MOBILE_NET_V1_HEAD
 *   -  6: SHUFFLE_NET_V2_BY_MOBILE_NET_V1_BODY
 *   -  7: SHUFFLE_NET_V2_BY_MOBILE_NET_V1_TAIL
 *   -  8: SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE1  (ShuffleNetV2_ByPointwise21's head with ( pointwise1ChannelCount == 0 ))
 *   -  9: SHUFFLE_NET_V2_BY_POINTWISE21_HEAD                (ShuffleNetV2_ByPointwise21's head with ( pointwise1ChannelCount >= 1 ))
 *   - 10: SHUFFLE_NET_V2_BY_POINTWISE21_BODY
 *   - 11: SHUFFLE_NET_V2_BY_POINTWISE21_TAIL
 */
class ConvBlockType extends Int {

  constructor() {
    super( 0, 11,
      [
        "MOBILE_NET_V1_HEAD_BODY_TAIL",                      // ( 0)
        "MOBILE_NET_V2_BODY_TAIL",                           // ( 1)
        "SHUFFLE_NET_V2_HEAD",                               // ( 2)
        "SHUFFLE_NET_V2_BODY",                               // ( 3)
        "SHUFFLE_NET_V2_TAIL",                               // ( 4)
        "SHUFFLE_NET_V2_BY_MOBILE_NET_V1_HEAD",              // ( 5)
        "SHUFFLE_NET_V2_BY_MOBILE_NET_V1_BODY",              // ( 6)
        "SHUFFLE_NET_V2_BY_MOBILE_NET_V1_TAIL",              // ( 7)
        "SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE1",  // ( 8)
        "SHUFFLE_NET_V2_BY_POINTWISE21_HEAD",                // ( 9)
        "SHUFFLE_NET_V2_BY_POINTWISE21_BODY",                // (10)
        "SHUFFLE_NET_V2_BY_POINTWISE21_TAIL",                // (11)
      ],

      [
         new ConvBlockType.Info(  0, 1, 1, false, false, false, false, false, false, 0, false ),
         new ConvBlockType.Info(  1, 1, 1, false, false,  true, false, false, false, 0, false ),
         new ConvBlockType.Info(  2, 1, 2,  true, false, false,  true, false, false, 0,  true ),
         new ConvBlockType.Info(  3, 2, 2, false, false, false,  true, false, false, 0, false ),
         new ConvBlockType.Info(  4, 2, 1, false, false, false,  true, false, false, 0, false ),
         new ConvBlockType.Info(  5, 1, 1, false, false, false, false,  true,  true, 2, false ),
         new ConvBlockType.Info(  6, 1, 1, false, false, false, false,  true, false, 2, false ),
         new ConvBlockType.Info(  7, 1, 1, false, false, false, false,  true, false, 0, false ),
         new ConvBlockType.Info(  8, 1, 2, false, false, false, false, false, false, 0,  true ),
         new ConvBlockType.Info(  9, 1, 2,  true,  true, false, false, false, false, 0,  true ),
         new ConvBlockType.Info( 10, 2, 2, false,  true, false, false, false, false, 0,  true ),
         new ConvBlockType.Info( 11, 2, 1, false,  true, false, false, false, false, 0, false ),
      ]
    );
  }

  /**
   * @param {number} nConvBlockTypeId  The numeric identifier of ConvBlockType. (ConvBlockType.Singleton.Ids.Xxx)
   * @return {number} Return the input tensor count (1 or 2) of the ConvBlockType.Singleton.Ids.Xxx.
   */
  static inputTensorCount_get( nConvBlockTypeId ) {
    let info = ConvBlockType.Singleton.getInfoById( nConvBlockTypeId );
    if ( info )
      return info.inputTensorCount;
    return NaN;
  }

  /**
   * @param {number} nConvBlockTypeId  The numeric identifier of ConvBlockType. (ConvBlockType.Singleton.Ids.Xxx)
   * @return {number} Return the output tensor count (1 or 2) of the ConvBlockType.Singleton.Ids.Xxx.
   */
  static outputTensorCount_get( nConvBlockTypeId ) {
    let info = ConvBlockType.Singleton.getInfoById( nConvBlockTypeId );
    if ( info )
      return info.outputTensorCount;
    return NaN;
  }

  /**
   * Convert convolution block type id to information object.
   *
   * @param {umber} nConvBlockTypeId
   *   It should be one of ValueDesc.ConvBlockType.Singleton.Ids.Xxx.
   *
   * @return {ConvBlockType.Info}
   *   It should be one of ValueDesc.ConvBlockType.Singleton.integerToObjectMap according to the nStridesPadId.
   */
  getInfoById( nConvBlockTypeId ) {
    let info = this.integerToObjectMap.get( nConvBlockTypeId );
    return info;
  }

}

/**
 *
 * @member {number} nConvBlockTypeId
 *   The convolution block type id (ValueDesc.ConvBlockType.Singleton.Ids.Xxx).
 *
 * @member {number} inputTensorCount
 *   The input tensor count for The convolution block type. Either 1 or 2.
 *
 * @member {number} outputTensorCount
 *   The output tensor count for The convolution block type. Either 1 or 2.
 *
 * @member {boolean} bDepthwise2Requested
 *   Whether needs depthwise2. Usually true only if SHUFFLE_NET_V2_BY_HEAD and SHUFFLE_NET_V2_BY_POINTWISE21_HEAD (except
 * SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE).
 *
 * @member {boolean} bConcat1Requested
 *   Whether needs concat1 (i.e. concat after depthwise). Usually true only if SHUFFLE_NET_V2_BY_POINTWISE21_Xxx (except
 * SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE).
 *
 * @member {boolean} bAddInputToOutputRequested
 *   Whether needs add-input-to-output. Usually true only if MOBILE_NET_V2_BODY_TAIL.
 *
 * @member {boolean} bConcat2ShuffleSplitRequested
 *   Whether needs add-input-to-output. Usually true only if SHUFFLE_NET_V2_HEAD, SHUFFLE_NET_V2_BODY, SHUFFLE_NET_V2_TAIL.
 *
 * @member {boolean} bHigherHalfDifferent
 *   Whether the higher half channels will be handled different. Usually true only if SHUFFLE_NET_V2_BY_MOBILE_NET_V1_Xxx.
 *
 * @member {boolean} bHigherHalfDepthwise2
 *   Whether the higher half channels will be processed by depthwise2. Usually true only if SHUFFLE_NET_V2_BY_MOBILE_NET_V1_HEAD.
 *
 * @member {number} channelShuffler_outputGroupCount
 *   The output group count of the pointwise2's channel shuffler when
 * ( nHigherHalfDifferent == ValueDesc.Pointwise_HigherHalfDifferent.Singleton.Ids.HIGHER_HALF_PASS_THROUGH ). Either 0 or 2.
 * Usually 2 ony if SHUFFLE_NET_V2_BY_MOBILE_NET_V1_HEAD or SHUFFLE_NET_V2_BY_MOBILE_NET_V1_BODY.
 *
 * @member {boolean} bPointwise21
 *   Whether the 2nd pointwise2 existed. Usually true only if SHUFFLE_NET_V2_HEAD, SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE,
 * SHUFFLE_NET_V2_BY_POINTWISE21_HEAD, SHUFFLE_NET_V2_BY_POINTWISE21_BODY. Note: Even if ( outputTensorCount == 2 ), it does not
 * means pointwise21 existed.
 *
 */
ConvBlockType.Info = class {

  /**
   *
   */
  constructor( nConvBlockTypeId, inputTensorCount, outputTensorCount,
    bDepthwise2Requested, bConcat1Requested, bAddInputToOutputRequested, bConcat2ShuffleSplitRequested,
    bHigherHalfDifferent, bHigherHalfDepthwise2, channelShuffler_outputGroupCount,
    bPointwise21
  ) {
    this.nConvBlockTypeId = nConvBlockTypeId;
    this.inputTensorCount = inputTensorCount;
    this.outputTensorCount = outputTensorCount;

    this.bDepthwise2Requested = bDepthwise2Requested;
    this.bConcat1Requested = bConcat1Requested;
    this.bAddInputToOutputRequested = bAddInputToOutputRequested;
    this.bConcat2ShuffleSplitRequested = bConcat2ShuffleSplitRequested;

    this.bHigherHalfDifferent = bHigherHalfDifferent;
    this.bHigherHalfDepthwise2 = bHigherHalfDepthwise2;
    this.channelShuffler_outputGroupCount = channelShuffler_outputGroupCount;

    this.bPointwise21 = bPointwise21;
  }

}

/** The only one ValueDesc.ConvBlockType instance. */
ConvBlockType.Singleton = new ConvBlockType;

<commit_msg>Update ValueDesc_ConvBlockType.js<commit_after>export { ConvBlockType };

import { Int } from "./ValueDesc_Base.js";

/** Describe id, range, name of ConvBlockType.
 *
 * Convert number value into integer between [ 0, 11 ] representing operation:
 *   -  0: MOBILE_NET_V1_HEAD_BODY_TAIL                      (General Pointwise1-Depthwise1-Pointwise2)
 *                                                           (MobileNetV1 or
 *                                                            MobileNetV2's head)
 *
 *   -  1: MOBILE_NET_V2_BODY_TAIL
 *   -  2: SHUFFLE_NET_V2_HEAD
 *   -  3: SHUFFLE_NET_V2_BODY
 *   -  4: SHUFFLE_NET_V2_TAIL
 *   -  5: SHUFFLE_NET_V2_BY_MOBILE_NET_V1_HEAD
 *   -  6: SHUFFLE_NET_V2_BY_MOBILE_NET_V1_BODY
 *   -  7: SHUFFLE_NET_V2_BY_MOBILE_NET_V1_TAIL
 *   -  8: SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE1  (ShuffleNetV2_ByPointwise21's head with ( pointwise1ChannelCount == 0 ))
 *   -  9: SHUFFLE_NET_V2_BY_POINTWISE21_HEAD                (ShuffleNetV2_ByPointwise21's head with ( pointwise1ChannelCount >= 1 ))
 *   - 10: SHUFFLE_NET_V2_BY_POINTWISE21_BODY
 *   - 11: SHUFFLE_NET_V2_BY_POINTWISE21_TAIL
 */
class ConvBlockType extends Int {

  constructor() {
    super( 0, 11,
      [
        "MOBILE_NET_V1_HEAD_BODY_TAIL",                      // ( 0)
        "MOBILE_NET_V2_BODY_TAIL",                           // ( 1)
        "SHUFFLE_NET_V2_HEAD",                               // ( 2)
        "SHUFFLE_NET_V2_BODY",                               // ( 3)
        "SHUFFLE_NET_V2_TAIL",                               // ( 4)
        "SHUFFLE_NET_V2_BY_MOBILE_NET_V1_HEAD",              // ( 5)
        "SHUFFLE_NET_V2_BY_MOBILE_NET_V1_BODY",              // ( 6)
        "SHUFFLE_NET_V2_BY_MOBILE_NET_V1_TAIL",              // ( 7)
        "SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE1",  // ( 8)
        "SHUFFLE_NET_V2_BY_POINTWISE21_HEAD",                // ( 9)
        "SHUFFLE_NET_V2_BY_POINTWISE21_BODY",                // (10)
        "SHUFFLE_NET_V2_BY_POINTWISE21_TAIL",                // (11)
      ],

      [
         new ConvBlockType.Info(  0, 1, 1, false, false, false, false, false, false, 0, false ),
         new ConvBlockType.Info(  1, 1, 1, false, false,  true, false, false, false, 0, false ),
         new ConvBlockType.Info(  2, 1, 2,  true, false, false,  true, false, false, 0,  true ),
         new ConvBlockType.Info(  3, 2, 2, false, false, false,  true, false, false, 0, false ),
         new ConvBlockType.Info(  4, 2, 1, false, false, false,  true, false, false, 0, false ),
         new ConvBlockType.Info(  5, 1, 1, false, false, false, false,  true,  true, 2, false ),
         new ConvBlockType.Info(  6, 1, 1, false, false, false, false,  true, false, 2, false ),
         new ConvBlockType.Info(  7, 1, 1, false, false, false, false,  true, false, 0, false ),
         new ConvBlockType.Info(  8, 1, 2, false, false, false, false, false, false, 0,  true ),
         new ConvBlockType.Info(  9, 1, 2,  true,  true, false, false, false, false, 0,  true ),
         new ConvBlockType.Info( 10, 2, 2, false,  true, false, false, false, false, 0,  true ),
         new ConvBlockType.Info( 11, 2, 1, false,  true, false, false, false, false, 0, false ),
      ]
    );
  }

  /**
   * @param {number} nConvBlockTypeId  The numeric identifier of ConvBlockType. (ConvBlockType.Singleton.Ids.Xxx)
   * @return {number} Return the input tensor count (1 or 2) of the ConvBlockType.Singleton.Ids.Xxx.
   */
  static inputTensorCount_get( nConvBlockTypeId ) {
    let info = ConvBlockType.Singleton.getInfoById( nConvBlockTypeId );
    if ( info )
      return info.inputTensorCount;
    return NaN;
  }

  /**
   * @param {number} nConvBlockTypeId  The numeric identifier of ConvBlockType. (ConvBlockType.Singleton.Ids.Xxx)
   * @return {number} Return the output tensor count (1 or 2) of the ConvBlockType.Singleton.Ids.Xxx.
   */
  static outputTensorCount_get( nConvBlockTypeId ) {
    let info = ConvBlockType.Singleton.getInfoById( nConvBlockTypeId );
    if ( info )
      return info.outputTensorCount;
    return NaN;
  }

  /**
   * Convert convolution block type id to information object.
   *
   * @param {umber} nConvBlockTypeId
   *   It should be one of ValueDesc.ConvBlockType.Singleton.Ids.Xxx.
   *
   * @return {ConvBlockType.Info}
   *   It should be one of ValueDesc.ConvBlockType.Singleton.integerToObjectMap according to the nStridesPadId.
   */
  getInfoById( nConvBlockTypeId ) {
    let info = this.integerToObjectMap.get( nConvBlockTypeId );
    return info;
  }

}

/**
 *
 * @member {number} nConvBlockTypeId
 *   The convolution block type id (ValueDesc.ConvBlockType.Singleton.Ids.Xxx).
 *
 * @member {number} inputTensorCount
 *   The input tensor count for The convolution block type. Either 1 or 2.
 *
 * @member {number} outputTensorCount
 *   The output tensor count for The convolution block type. Either 1 or 2.
 *
 * @member {boolean} bDepthwise2Requested
 *   Whether needs depthwise2. Usually true only if SHUFFLE_NET_V2_BY_HEAD and SHUFFLE_NET_V2_BY_POINTWISE21_HEAD (except
 * SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE).
 *
 * @member {boolean} bConcat1Requested
 *   Whether needs concat1 (i.e. concat after depthwise). Usually true only if SHUFFLE_NET_V2_BY_POINTWISE21_Xxx (except
 * SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE).
 *
 * @member {boolean} bAddInputToOutputRequested
 *   Whether needs add-input-to-output. Usually true only if MOBILE_NET_V2_BODY_TAIL.
 *
 * @member {boolean} bConcat2ShuffleSplitRequested
 *   Whether needs add-input-to-output. Usually true only if SHUFFLE_NET_V2_HEAD, SHUFFLE_NET_V2_BODY, SHUFFLE_NET_V2_TAIL.
 *
 * @member {boolean} bHigherHalfDifferent
 *   Whether the higher half channels will be handled different. Usually true only if SHUFFLE_NET_V2_BY_MOBILE_NET_V1_Xxx.
 *
 * @member {boolean} bHigherHalfDepthwise2
 *   Whether the higher half channels will be processed by depthwise2. Usually true only if SHUFFLE_NET_V2_BY_MOBILE_NET_V1_HEAD.
 *
 * @member {number} pointwise20_channelShuffler_outputGroupCount
 *   The output group count of the pointwise20's channel shuffler when
 * ( nHigherHalfDifferent == ValueDesc.Pointwise_HigherHalfDifferent.Singleton.Ids.HIGHER_HALF_PASS_THROUGH ). Either 0 or 2.
 * Usually 2 ony if SHUFFLE_NET_V2_BY_MOBILE_NET_V1_HEAD or SHUFFLE_NET_V2_BY_MOBILE_NET_V1_BODY.
 *
 * @member {boolean} bPointwise21
 *   Whether the 2nd pointwise2 existed. Usually true only if SHUFFLE_NET_V2_HEAD, SHUFFLE_NET_V2_BY_POINTWISE21_HEAD_NO_POINTWISE,
 * SHUFFLE_NET_V2_BY_POINTWISE21_HEAD, SHUFFLE_NET_V2_BY_POINTWISE21_BODY. Note: Even if ( outputTensorCount == 2 ), it does not
 * means pointwise21 existed.
 *
 */
ConvBlockType.Info = class {

  /**
   *
   */
  constructor( nConvBlockTypeId, inputTensorCount, outputTensorCount,
    bDepthwise2Requested, bConcat1Requested, bAddInputToOutputRequested, bConcat2ShuffleSplitRequested,
    bHigherHalfDifferent, bHigherHalfDepthwise2, pointwise20_channelShuffler_outputGroupCount,
    bPointwise21
  ) {
    this.nConvBlockTypeId = nConvBlockTypeId;
    this.inputTensorCount = inputTensorCount;
    this.outputTensorCount = outputTensorCount;

    this.bDepthwise2Requested = bDepthwise2Requested;
    this.bConcat1Requested = bConcat1Requested;
    this.bAddInputToOutputRequested = bAddInputToOutputRequested;
    this.bConcat2ShuffleSplitRequested = bConcat2ShuffleSplitRequested;

    this.bHigherHalfDifferent = bHigherHalfDifferent;
    this.bHigherHalfDepthwise2 = bHigherHalfDepthwise2;
    this.pointwise20_channelShuffler_outputGroupCount = pointwise20_channelShuffler_outputGroupCount;

    this.bPointwise21 = bPointwise21;
  }

}

/** The only one ValueDesc.ConvBlockType instance. */
ConvBlockType.Singleton = new ConvBlockType;

<|endoftext|>


<commit_before>var util = require('util');
var EventEmitter = require('events').EventEmitter;

function CB(id) {
    this.btServiceIds = ['ae0e019cef7511e981b42a2ae2dbcce4'];

    this._id = id;
    this._gmCh = null;
    this._dev = null;
    this._moving = false;
    this._position = 0;
    this._stepsPerUnit = 1;
    this._backlash = 0;
    this._lastDirection = 0;
    this._lastPosition = null;
    this._lastMoving = null;
}

util.inherits(CB, EventEmitter);

CB.prototype._connectBt = function(btPeripheral, callback) {
    var self = this;
    btPeripheral.connect(function(err1) {
        if (err1) {
            if (callback) callback(false);
            return;
        }
        console.log('CB(' + self._id + '): connecting via BLE');
        setTimeout(function(){
            btPeripheral.discoverServices(self.btServiceIds, function(err2, services) {
                if (services && services[0]) {
                    services[0].discoverCharacteristics([], function(err, characteristics) {
                        //console.log("characteristics:", characteristics);
                        self._writeCh = null;
                        self._readCh = null;
                        self._dev = null;
                        for (var i = 0; i < characteristics.length; i++) {
                            ch = characteristics[i];
                            //console.log("ch.uuid", ch.uuid);
                            //console.log("ch", ch);
                            if (ch.uuid == "ae0e0408ef7511e981b42a2ae2dbcce4") {
                                self._writeCh = ch;
                            } else if (ch.uuid == "ae0e055cef7511e981b42a2ae2dbcce4") {
                                self._readCh = ch;
                            }
                        }
                        if (self._writeCh && self._readCh) {
                            var failures = 0;
                            var first = true;
                            var tryRead = function() {
                                if(failures > 5) {
                                    btPeripheral.disconnect();
                                    console.log("CB(" + self._id + "): reading failed, disconnecting");
                                    if (first && callback) callback(false);
                                }
                                try {
                                    //console.log("CB(" + self._id + "): subscribing...");
                                    self._readCh.read(function(){
                                        if(first) {
                                            console.log("CB(" + self._id + "): read requested");
                                            self._dev = btPeripheral;
                                            self._dev.connected = true;
                                            self.connected = true;
                                            self._dev.type = "bt";
                                            console.log("CB(" + self._id + "): connected!");
                                            self._init();
                                            if (callback) callback(true);
                                        }
                                        if(self.connected) setTimeout(tryRead, 200);
                                        first = false;
                                    });
                                } catch(err3) {
                                    btPeripheral.disconnect();
                                    console.log("CB(" + self._id + "): exception while connecting: ", err3);
                                    if (first && callback) callback(false);
                                }

                            }
                            self._readCh.on('data', function(data, isNotification) {
                                if(first) console.log("CB(" + self._id + "): data read:", data);
                                self._parseIncoming(data);
                            });
                            tryRead();
                        } else {
                            console.log("CB(" + self._id + "): couldn't locate characteristics, disconnecting... ", err);
                            btPeripheral.disconnect();
                            if (callback) callback(false);
                        }
                    });
                } else {
                    console.log("CB(" + self._id + "): couldn't locate services, disconnecting... ", err2);
                    btPeripheral.disconnect();
                    if (callback) callback(false);
                }

            });
        }, 1000);

        btPeripheral.once('disconnect', function() {
            console.log("CB(" + self._id + "): disconnected");
            self._dev = null;
            self.connected = false;
            //clearInterval(self.statusIntervalHandle);
            self.emit("status", self.getStatus());
        });

    });
}

CB.prototype._init = function() {
    var self = this;
    self._writeCh.write(new Buffer("S\n"));
    self.emit("status", self.getStatus());
}

CB.prototype._parseIncoming = function(data) {
    if(data.length != 5) return;

    this._moving = data.readUInt8(0) & 0x01;
    this._position = data.readUInt32BE(1);

    if(this._lastPosition !== this._position || this._lastMoving !== this._moving) {
        this._lastPosition = this._position;
        this._lastMoving = this._moving;
        console.log("CB(" + this._id + "): moving: ", this._moving, ", position:", this._position);
        self.emit("status", self.getStatus());
    }
}

CB.prototype.connect = function(device, callback) {
    if (device && device.connect) {
        this._moving = false;
        console.log("CB(" + this._id + "): connecting...");
        this._connectBt(device, callback);
    } else {
        console.log("CB(" + this._id + "): invalid device, cannot connect");
        if (callback) callback("invalid device");
    }
}

CB.prototype.disconnect = function() {
    console.log("CB(" + this._id + "): disconnecting...");
    if (this._dev && this._dev.connected) {
        this.connected = false;
        this._dev.connected = false;
        this._dev.disconnect();
    } else {
        this._dev = null;
    }
    this.emit("status", this.getStatus());
}

CB.prototype.getStatus = function() {
    var type = (this._dev && this._dev.type) ? this._dev.type : null;
    return {
        connected: (this._dev && this._dev.connected) ? true : false,
        connectionType: type,
        position: this._position / this._stepsPerUnit,
        backlash: this._backlash,
        moving: this._moving
    }
}

CB.prototype.disable = function(motor) {
    this._enabled = false;
}

CB.prototype.enable = function(motor) {
    this._enabled = true;
}

CB.prototype.move = function(motor, amount, callback) {
    if (this._moving) {
        if (callback) callback("CB(" + this._id + "): motor already running");
        return console.log("CB(" + this._id + "): motor already running");
    }

    var steps = Math.round(amount * this._stepsPerUnit);

    console.log("CB(" + this._id + "): moving motor", steps, "steps");
    if(!this._enabled) this.enable();

    this._moving = true;

    var self = this;
    self._writeCh.write(new Buffer('M' + steps.toString()), function(err) {
        self._moving = true;
        if (!err) {
            var n = 0;
            var check = function() {
                setTimeout(function() {
                    if(self._moving || self._moving === undefined) {
                        n++;
                        if(n > 500) {
                            console.log("CB(" + self._id + "): ERROR: timed out waiting for move");
                            if (callback) callback("timeout");
                            self._moving = false;
                        } else {
                            check(); // keep checking until stop
                        }
                    } else {
                        console.log("CB(" + self._id + "): position:", self._position);
                        if (callback) callback(null, self._position / self._stepsPerUnit);
                    }
                }, 300);
            }
            check();
        } else {
            if (callback) callback(err);
            self._moving = false;
        }
    });
}

CB.prototype.constantMove = function(motor, speed, callback) {
    console.log("CB(" + this._id + "): moving motor at speed ", speed, "%");
    if(!this._enabled) this.enable();

    var self = this;

    self._constantSpeed = speed;

    if(self._watchdog) {
        clearTimeout(self._watchdog);
        self._watchdog = null;
    }
    if(self._moveTimer) {
        clearTimeout(self._moveTimer);
        self._moveTimer = null;
    }
    if(speed) {
        self._watchdog = setTimeout(function(){
            console.log("CB(" + self._id + "): stopping via watchdog");
            self._constantSpeed = 0;
            clearTimeout(self._moveTimer);
            self._moveTimer = null;
        }, 3000);
    }

    var sendMove = function() {
        if(self._constantSpeed) {
            self.move(motor, self._constantSpeed, function() {
                if (callback) callback(null, self._position / self._stepsPerUnit);
                if(speed) {
                    self._moveTimer = setTimeout(function() {
                        sendMove();
                    }, 100);
                }
            });
        } else {
            if (callback) callback(null, self._position / self._stepsPerUnit);
        }
    }
    sendMove();
}

CB.prototype.resetMotorPosition = function(motor, callback) {
    this.setMotorPosition(motor, 0, callback);
}

CB.prototype.setMotorPosition = function(motor, position, callback) {
    var self = this;
    var check = function() {
        if(self._moving) {
            setTimeout(check, 200); // keep checking until stop
        } else {
            if(!position) position = 0;
            self._position = Math.round(position);
            self._writeCh.write(new Buffer('P' + self._position.toString()));
            if (callback) callback();
        }
    }
    check();
}

CB.prototype.setMotorBacklash = function(motor, backlash, callback) {
    this._backlash = backlash;
    if (callback) callback(this._backlash);
}

CB.prototype.getMotorBacklash = function(motor, callback) {
    if (callback) callback(this._backlash);
}

module.exports = CB;
<commit_msg>motion testing<commit_after>var util = require('util');
var EventEmitter = require('events').EventEmitter;

function CB(id) {
    this.btServiceIds = ['ae0e019cef7511e981b42a2ae2dbcce4'];

    this._id = id;
    this._gmCh = null;
    this._dev = null;
    this._moving = false;
    this._position = 0;
    this._stepsPerUnit = 1;
    this._backlash = 0;
    this._lastDirection = 0;
    this._lastPosition = null;
    this._lastMoving = null;
}

util.inherits(CB, EventEmitter);

CB.prototype._connectBt = function(btPeripheral, callback) {
    var self = this;
    btPeripheral.connect(function(err1) {
        if (err1) {
            if (callback) callback(false);
            return;
        }
        console.log('CB(' + self._id + '): connecting via BLE');
        setTimeout(function(){
            btPeripheral.discoverServices(self.btServiceIds, function(err2, services) {
                if (services && services[0]) {
                    services[0].discoverCharacteristics([], function(err, characteristics) {
                        //console.log("characteristics:", characteristics);
                        self._writeCh = null;
                        self._readCh = null;
                        self._dev = null;
                        for (var i = 0; i < characteristics.length; i++) {
                            ch = characteristics[i];
                            //console.log("ch.uuid", ch.uuid);
                            //console.log("ch", ch);
                            if (ch.uuid == "ae0e0408ef7511e981b42a2ae2dbcce4") {
                                self._writeCh = ch;
                            } else if (ch.uuid == "ae0e055cef7511e981b42a2ae2dbcce4") {
                                self._readCh = ch;
                            }
                        }
                        if (self._writeCh && self._readCh) {
                            var failures = 0;
                            var first = true;
                            var tryRead = function() {
                                if(failures > 5) {
                                    btPeripheral.disconnect();
                                    console.log("CB(" + self._id + "): reading failed, disconnecting");
                                    if (first && callback) callback(false);
                                }
                                try {
                                    //console.log("CB(" + self._id + "): subscribing...");
                                    self._readCh.read(function(){
                                        if(first) {
                                            console.log("CB(" + self._id + "): read requested");
                                            self._dev = btPeripheral;
                                            self._dev.connected = true;
                                            self.connected = true;
                                            self._dev.type = "bt";
                                            console.log("CB(" + self._id + "): connected!");
                                            self._init();
                                            if (callback) callback(true);
                                        }
                                        if(self.connected) setTimeout(tryRead, 200);
                                        first = false;
                                    });
                                } catch(err3) {
                                    btPeripheral.disconnect();
                                    console.log("CB(" + self._id + "): exception while connecting: ", err3);
                                    if (first && callback) callback(false);
                                }

                            }
                            self._readCh.on('data', function(data, isNotification) {
                                if(first) console.log("CB(" + self._id + "): data read:", data);
                                self._parseIncoming(data);
                            });
                            tryRead();
                        } else {
                            console.log("CB(" + self._id + "): couldn't locate characteristics, disconnecting... ", err);
                            btPeripheral.disconnect();
                            if (callback) callback(false);
                        }
                    });
                } else {
                    console.log("CB(" + self._id + "): couldn't locate services, disconnecting... ", err2);
                    btPeripheral.disconnect();
                    if (callback) callback(false);
                }

            });
        }, 1000);

        btPeripheral.once('disconnect', function() {
            console.log("CB(" + self._id + "): disconnected");
            self._dev = null;
            self.connected = false;
            //clearInterval(self.statusIntervalHandle);
            self.emit("status", self.getStatus());
        });

    });
}

CB.prototype._init = function() {
    var self = this;
    self._writeCh.write(new Buffer("S\n"));
    self.emit("status", self.getStatus());
}

CB.prototype._parseIncoming = function(data) {
    if(data.length != 5) return;

    this._moving = data.readUInt8(0) & 0x01;
    this._position = data.readUInt32BE(1);

    if(this._lastPosition !== this._position || this._lastMoving !== this._moving) {
        this._lastPosition = this._position;
        this._lastMoving = this._moving;
        console.log("CB(" + this._id + "): moving: ", this._moving, ", position:", this._position);
        this.emit("status", self.getStatus());
    }
}

CB.prototype.connect = function(device, callback) {
    if (device && device.connect) {
        this._moving = false;
        console.log("CB(" + this._id + "): connecting...");
        this._connectBt(device, callback);
    } else {
        console.log("CB(" + this._id + "): invalid device, cannot connect");
        if (callback) callback("invalid device");
    }
}

CB.prototype.disconnect = function() {
    console.log("CB(" + this._id + "): disconnecting...");
    if (this._dev && this._dev.connected) {
        this.connected = false;
        this._dev.connected = false;
        this._dev.disconnect();
    } else {
        this._dev = null;
    }
    this.emit("status", this.getStatus());
}

CB.prototype.getStatus = function() {
    var type = (this._dev && this._dev.type) ? this._dev.type : null;
    return {
        connected: (this._dev && this._dev.connected) ? true : false,
        connectionType: type,
        position: this._position / this._stepsPerUnit,
        backlash: this._backlash,
        moving: this._moving
    }
}

CB.prototype.disable = function(motor) {
    this._enabled = false;
}

CB.prototype.enable = function(motor) {
    this._enabled = true;
}

CB.prototype.move = function(motor, amount, callback) {
    if (this._moving) {
        if (callback) callback("CB(" + this._id + "): motor already running");
        return console.log("CB(" + this._id + "): motor already running");
    }

    var steps = Math.round(amount * this._stepsPerUnit);

    console.log("CB(" + this._id + "): moving motor", steps, "steps");
    if(!this._enabled) this.enable();

    this._moving = true;

    var self = this;
    self._writeCh.write(new Buffer('M' + steps.toString()), function(err) {
        self._moving = true;
        if (!err) {
            var n = 0;
            var check = function() {
                setTimeout(function() {
                    if(self._moving || self._moving === undefined) {
                        n++;
                        if(n > 500) {
                            console.log("CB(" + self._id + "): ERROR: timed out waiting for move");
                            if (callback) callback("timeout");
                            self._moving = false;
                        } else {
                            check(); // keep checking until stop
                        }
                    } else {
                        console.log("CB(" + self._id + "): position:", self._position);
                        if (callback) callback(null, self._position / self._stepsPerUnit);
                    }
                }, 300);
            }
            check();
        } else {
            if (callback) callback(err);
            self._moving = false;
        }
    });
}

CB.prototype.constantMove = function(motor, speed, callback) {
    console.log("CB(" + this._id + "): moving motor at speed ", speed, "%");
    if(!this._enabled) this.enable();

    var self = this;

    self._constantSpeed = speed;

    if(self._watchdog) {
        clearTimeout(self._watchdog);
        self._watchdog = null;
    }
    if(self._moveTimer) {
        clearTimeout(self._moveTimer);
        self._moveTimer = null;
    }
    if(speed) {
        self._watchdog = setTimeout(function(){
            console.log("CB(" + self._id + "): stopping via watchdog");
            self._constantSpeed = 0;
            clearTimeout(self._moveTimer);
            self._moveTimer = null;
        }, 3000);
    }

    var sendMove = function() {
        if(self._constantSpeed) {
            self.move(motor, self._constantSpeed, function() {
                if (callback) callback(null, self._position / self._stepsPerUnit);
                if(speed) {
                    self._moveTimer = setTimeout(function() {
                        sendMove();
                    }, 100);
                }
            });
        } else {
            if (callback) callback(null, self._position / self._stepsPerUnit);
        }
    }
    sendMove();
}

CB.prototype.resetMotorPosition = function(motor, callback) {
    this.setMotorPosition(motor, 0, callback);
}

CB.prototype.setMotorPosition = function(motor, position, callback) {
    var self = this;
    var check = function() {
        if(self._moving) {
            setTimeout(check, 200); // keep checking until stop
        } else {
            if(!position) position = 0;
            self._position = Math.round(position);
            self._writeCh.write(new Buffer('P' + self._position.toString()));
            if (callback) callback();
        }
    }
    check();
}

CB.prototype.setMotorBacklash = function(motor, backlash, callback) {
    this._backlash = backlash;
    if (callback) callback(this._backlash);
}

CB.prototype.getMotorBacklash = function(motor, callback) {
    if (callback) callback(this._backlash);
}

module.exports = CB;
<|endoftext|>