<commit_before>'use strict';

const Attribute = require('./Attribute');
const errors = require('./errors');
const VirtualType = require('./VirtualType');
//const util = require('util');

const debug = require('debug')('dynamoose:schema');

function Schema(obj, options) {
  debug('Creating Schema', obj);

  this.options = options || {};

  this.methods = {};
  this.statics = {};
  this.virtuals = {};
  this.tree = {};

  if(this.options.throughput) {
    const throughput = this.options.throughput;
    if(typeof throughput === 'number') {
      throughput = {read: throughput, write: throughput};
    }
    this.throughput = throughput;
  } else {
    this.throughput = {read: 1, write: 1};
  }

  if((!this.throughput.read || !this.throughput.write) &&
  this.throughput.read >= 1 && this.throughput.write >= 1) {
    throw new errors.SchemaError('Invalid throughput: '+ this.throughput);
  }

  /*
  * Added support for timestamps attribute
  */
  if (this.options.timestamps) {
    let createdAt = null;
    let updatedAt = null;

    if (this.options.timestamps === true) {
      createdAt = 'createdAt';
      updatedAt = 'updatedAt';
    } else if (typeof this.options.timestamps === 'object') {
      if (this.options.timestamps.createdAt && this.options.timestamps.updatedAt) {
        createdAt = this.options.timestamps.createdAt;
        updatedAt = this.options.timestamps.updatedAt;
      } else {
        throw new errors.SchemaError('Missing createdAt and updatedAt timestamps attribute. Maybe set timestamps: true?');
      }
    } else {
      throw new errors.SchemaError('Invalid syntax for timestamp: ' + name);
    }

    obj[createdAt] = obj[createdAt] || {};
    obj[createdAt].type = Date;
    obj[createdAt].default = Date.now;

    obj[updatedAt] = obj[updatedAt] || {};
    obj[updatedAt].type = Date;
    obj[updatedAt].default = Date.now;
    obj[updatedAt].set = function() { return Date.now(); };
    this.timestamps = { createdAt: createdAt, updatedAt: updatedAt };
  }

  /*
  * Added support for expires attribute
  */
  if (this.options.expires !== null && this.options.expires !== undefined ) {
    let expires = {
      attribute: 'expires'
    };

    if (typeof this.options.expires === 'number') {
      expires.ttl = this.options.expires;

    } else if (typeof this.options.expires === 'object') {
      if (typeof this.options.expires.ttl === 'number') {
        expires.ttl = this.options.expires.ttl;
      } else {
        throw new errors.SchemaError('Missing or invalided ttl for expires attribute.');
      }
      if(typeof this.options.expires.attribute === 'string') {
        expires.attribute = this.options.expires.attribute;
      }
    } else {
      throw new errors.SchemaError('Invalid syntax for expires: ' + name);
    }

    let defaultExpires = function () {
      return new Date(Date.now() + (expires.ttl * 1000));
    };

    obj[expires.attribute] = {
      type: Number,
      default: defaultExpires,
      set: function(v) {
        return Math.floor(v.getTime() / 1000);
      },
      get: function (v) {
        return new Date(v * 1000);
      }
    };
    this.expires = expires;
  }
  this.useDocumentTypes = !!this.options.useDocumentTypes;
  this.useNativeBooleans = !!this.options.useNativeBooleans;
  this.attributeFromDynamo = this.options.attributeFromDynamo;
  this.attributeToDynamo = this.options.attributeToDynamo;

  this.attributes = {};
  this.indexes = {local: {}, global: {}};

  for(const n in obj) {

    if(this.attributes[n]) {
      throw new errors.SchemaError('Duplicate attribute: ' + n);
    }

    debug('Adding Attribute to Schema (%s)', n, obj);
    this.attributes[n] = Attribute.create(this, n, obj[n]);
  }
}

/*Schema.prototype.attribute = function(name, obj) {
debug('Adding Attribute to Schema (%s)', name, obj);

this Attribute.create(name, obj);

};*/

Schema.prototype.toDynamo = function(model, options) {

  let dynamoObj = {};
  let name, attr;

  for(name in model) {
    if(!model.hasOwnProperty(name)){
      continue;
    }
    if (model[name] === undefined || model[name] === null || Number.isNaN(model[name])) {
      debug('toDynamo: skipping attribute: %s because its definition or value is null, undefined, or NaN', name);
      continue;
    }
    attr = this.attributes[name];
    if((!attr && this.options.saveUnknown === true) || (this.options.saveUnknown instanceof Array && this.options.saveUnknown.indexOf(name) >= 0)) {
      attr = Attribute.create(this, name, model[name]);
      this.attributes[name] = attr;
    }
  }

  for(name in this.attributes) {
    attr = this.attributes[name];

    attr.setDefault(model);
    let dynamoAttr;
    if (this.attributeToDynamo) {
      dynamoAttr = this.attributeToDynamo(name, model[name], model, attr.toDynamo.bind(attr), options);
    } else {
      dynamoAttr = attr.toDynamo(model[name], undefined, model, options);
    }
    if(dynamoAttr) {
      dynamoObj[attr.name] = dynamoAttr;
    }
  }

  debug('toDynamo: %s', dynamoObj );
  return dynamoObj;
};

Schema.prototype.parseDynamo = function(model, dynamoObj) {

  for(const name in dynamoObj) {
    let attr = this.attributes[name];

    if((!attr && this.options.saveUnknown === true) || (this.options.saveUnknown instanceof Array && this.options.saveUnknown.indexOf(name) >= 0)) {
      attr = Attribute.createUnknownAttrbuteFromDynamo(this, name, dynamoObj[name]);
      this.attributes[name] = attr;
    }

    if(attr) {
      let attrVal;
      if (this.attributeFromDynamo) {
        attrVal = this.attributeFromDynamo(name, dynamoObj[name], attr.parseDynamo.bind(attr), model);
      } else {
        attrVal = attr.parseDynamo(dynamoObj[name]);
      }
      if (attrVal !== undefined && attrVal !== null) {
        model[name] = attrVal;
      }
    } else {
      debug('parseDynamo: received an attribute name (%s) that is not defined in the schema', name);
    }
  }

  if (model.$__) {
    model.$__.originalItem = JSON.parse(JSON.stringify(model));
  }

  debug('parseDynamo: %s', model);

  return dynamoObj;

};

/**
* Adds an instance method to documents constructed from Models compiled from this schema.
*
* ####Example
*
*     let schema = kittySchema = new Schema(..);
*
*     schema.method('meow', function () {
*       console.log('meeeeeoooooooooooow');
*     })
*
*     let Kitty = mongoose.model('Kitty', schema);
*
*     let fizz = new Kitty;
*     fizz.meow(); // meeeeeooooooooooooow
*
* If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
*
*     schema.method({
*         purr: function () {}
*       , scratch: function () {}
*     });
*
*     // later
*     fizz.purr();
*     fizz.scratch();
*
* @param {String|Object} method name
* @param {Function} [fn]
* @api public
*/

Schema.prototype.method = function (name, fn) {
  if (typeof name !== 'string' ){
    for (const i in name){
      this.methods[i] = name[i];
    }
  } else {
    this.methods[name] = fn;
  }
  return this;
};

/**
* Adds static "class" methods to Models compiled from this schema.
*
* ####Example
*
*     let schema = new Schema(..);
*     schema.static('findByName', function (name, callback) {
*       return this.find({ name: name }, callback);
*     });
*
*     let Drink = mongoose.model('Drink', schema);
*     Drink.findByName('sanpellegrino', function (err, drinks) {
*       //
*     });
*
* If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
*
* @param {String} name
* @param {Function} fn
* @api public
*/

Schema.prototype.static = function(name, fn) {
  if (typeof name !== 'string' ){
    for (const i in name){
      this.statics[i] = name[i];
    }
  } else {
    this.statics[name] = fn;
  }
  return this;
};

/**
* Creates a virtual type with the given name.
*
* @param {String} name
* @param {Object} [options]
* @return {VirtualType}
*/

Schema.prototype.virtual = function (name, options) {
  //let virtuals = this.virtuals;
  const parts = name.split('.');

  return this.virtuals[name] = parts.reduce(function (mem, part, i) {
    mem[part] || (mem[part] = (i === parts.length-1) ? new VirtualType(options, name) : {});
    return mem[part];
  }, this.tree);
};

/**
* Returns the virtual type with the given `name`.
*
* @param {String} name
* @return {VirtualType}
*/

Schema.prototype.virtualpath = function (name) {
  return this.virtuals[name];
};

module.exports = Schema;
<commit_msg>Fixing tests<commit_after>'use strict';

const Attribute = require('./Attribute');
const errors = require('./errors');
const VirtualType = require('./VirtualType');
//const util = require('util');

const debug = require('debug')('dynamoose:schema');

function Schema(obj, options) {
  debug('Creating Schema', obj);

  this.options = options || {};

  this.methods = {};
  this.statics = {};
  this.virtuals = {};
  this.tree = {};

  if(this.options.throughput) {
    let throughput = this.options.throughput;
    if(typeof throughput === 'number') {
      throughput = {read: throughput, write: throughput};
    }
    this.throughput = throughput;
  } else {
    this.throughput = {read: 1, write: 1};
  }

  if((!this.throughput.read || !this.throughput.write) &&
  this.throughput.read >= 1 && this.throughput.write >= 1) {
    throw new errors.SchemaError('Invalid throughput: '+ this.throughput);
  }

  /*
  * Added support for timestamps attribute
  */
  if (this.options.timestamps) {
    let createdAt = null;
    let updatedAt = null;

    if (this.options.timestamps === true) {
      createdAt = 'createdAt';
      updatedAt = 'updatedAt';
    } else if (typeof this.options.timestamps === 'object') {
      if (this.options.timestamps.createdAt && this.options.timestamps.updatedAt) {
        createdAt = this.options.timestamps.createdAt;
        updatedAt = this.options.timestamps.updatedAt;
      } else {
        throw new errors.SchemaError('Missing createdAt and updatedAt timestamps attribute. Maybe set timestamps: true?');
      }
    } else {
      throw new errors.SchemaError('Invalid syntax for timestamp: ' + name);
    }

    obj[createdAt] = obj[createdAt] || {};
    obj[createdAt].type = Date;
    obj[createdAt].default = Date.now;

    obj[updatedAt] = obj[updatedAt] || {};
    obj[updatedAt].type = Date;
    obj[updatedAt].default = Date.now;
    obj[updatedAt].set = function() { return Date.now(); };
    this.timestamps = { createdAt: createdAt, updatedAt: updatedAt };
  }

  /*
  * Added support for expires attribute
  */
  if (this.options.expires !== null && this.options.expires !== undefined ) {
    let expires = {
      attribute: 'expires'
    };

    if (typeof this.options.expires === 'number') {
      expires.ttl = this.options.expires;

    } else if (typeof this.options.expires === 'object') {
      if (typeof this.options.expires.ttl === 'number') {
        expires.ttl = this.options.expires.ttl;
      } else {
        throw new errors.SchemaError('Missing or invalided ttl for expires attribute.');
      }
      if(typeof this.options.expires.attribute === 'string') {
        expires.attribute = this.options.expires.attribute;
      }
    } else {
      throw new errors.SchemaError('Invalid syntax for expires: ' + name);
    }

    let defaultExpires = function () {
      return new Date(Date.now() + (expires.ttl * 1000));
    };

    obj[expires.attribute] = {
      type: Number,
      default: defaultExpires,
      set: function(v) {
        return Math.floor(v.getTime() / 1000);
      },
      get: function (v) {
        return new Date(v * 1000);
      }
    };
    this.expires = expires;
  }
  this.useDocumentTypes = !!this.options.useDocumentTypes;
  this.useNativeBooleans = !!this.options.useNativeBooleans;
  this.attributeFromDynamo = this.options.attributeFromDynamo;
  this.attributeToDynamo = this.options.attributeToDynamo;

  this.attributes = {};
  this.indexes = {local: {}, global: {}};

  for(const n in obj) {

    if(this.attributes[n]) {
      throw new errors.SchemaError('Duplicate attribute: ' + n);
    }

    debug('Adding Attribute to Schema (%s)', n, obj);
    this.attributes[n] = Attribute.create(this, n, obj[n]);
  }
}

/*Schema.prototype.attribute = function(name, obj) {
debug('Adding Attribute to Schema (%s)', name, obj);

this Attribute.create(name, obj);

};*/

Schema.prototype.toDynamo = function(model, options) {

  let dynamoObj = {};
  let name, attr;

  for(name in model) {
    if(!model.hasOwnProperty(name)){
      continue;
    }
    if (model[name] === undefined || model[name] === null || Number.isNaN(model[name])) {
      debug('toDynamo: skipping attribute: %s because its definition or value is null, undefined, or NaN', name);
      continue;
    }
    attr = this.attributes[name];
    if((!attr && this.options.saveUnknown === true) || (this.options.saveUnknown instanceof Array && this.options.saveUnknown.indexOf(name) >= 0)) {
      attr = Attribute.create(this, name, model[name]);
      this.attributes[name] = attr;
    }
  }

  for(name in this.attributes) {
    attr = this.attributes[name];

    attr.setDefault(model);
    let dynamoAttr;
    if (this.attributeToDynamo) {
      dynamoAttr = this.attributeToDynamo(name, model[name], model, attr.toDynamo.bind(attr), options);
    } else {
      dynamoAttr = attr.toDynamo(model[name], undefined, model, options);
    }
    if(dynamoAttr) {
      dynamoObj[attr.name] = dynamoAttr;
    }
  }

  debug('toDynamo: %s', dynamoObj );
  return dynamoObj;
};

Schema.prototype.parseDynamo = function(model, dynamoObj) {

  for(const name in dynamoObj) {
    let attr = this.attributes[name];

    if((!attr && this.options.saveUnknown === true) || (this.options.saveUnknown instanceof Array && this.options.saveUnknown.indexOf(name) >= 0)) {
      attr = Attribute.createUnknownAttrbuteFromDynamo(this, name, dynamoObj[name]);
      this.attributes[name] = attr;
    }

    if(attr) {
      let attrVal;
      if (this.attributeFromDynamo) {
        attrVal = this.attributeFromDynamo(name, dynamoObj[name], attr.parseDynamo.bind(attr), model);
      } else {
        attrVal = attr.parseDynamo(dynamoObj[name]);
      }
      if (attrVal !== undefined && attrVal !== null) {
        model[name] = attrVal;
      }
    } else {
      debug('parseDynamo: received an attribute name (%s) that is not defined in the schema', name);
    }
  }

  if (model.$__) {
    model.$__.originalItem = JSON.parse(JSON.stringify(model));
  }

  debug('parseDynamo: %s', model);

  return dynamoObj;

};

/**
* Adds an instance method to documents constructed from Models compiled from this schema.
*
* ####Example
*
*     let schema = kittySchema = new Schema(..);
*
*     schema.method('meow', function () {
*       console.log('meeeeeoooooooooooow');
*     })
*
*     let Kitty = mongoose.model('Kitty', schema);
*
*     let fizz = new Kitty;
*     fizz.meow(); // meeeeeooooooooooooow
*
* If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods.
*
*     schema.method({
*         purr: function () {}
*       , scratch: function () {}
*     });
*
*     // later
*     fizz.purr();
*     fizz.scratch();
*
* @param {String|Object} method name
* @param {Function} [fn]
* @api public
*/

Schema.prototype.method = function (name, fn) {
  if (typeof name !== 'string' ){
    for (const i in name){
      this.methods[i] = name[i];
    }
  } else {
    this.methods[name] = fn;
  }
  return this;
};

/**
* Adds static "class" methods to Models compiled from this schema.
*
* ####Example
*
*     let schema = new Schema(..);
*     schema.static('findByName', function (name, callback) {
*       return this.find({ name: name }, callback);
*     });
*
*     let Drink = mongoose.model('Drink', schema);
*     Drink.findByName('sanpellegrino', function (err, drinks) {
*       //
*     });
*
* If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
*
* @param {String} name
* @param {Function} fn
* @api public
*/

Schema.prototype.static = function(name, fn) {
  if (typeof name !== 'string' ){
    for (const i in name){
      this.statics[i] = name[i];
    }
  } else {
    this.statics[name] = fn;
  }
  return this;
};

/**
* Creates a virtual type with the given name.
*
* @param {String} name
* @param {Object} [options]
* @return {VirtualType}
*/

Schema.prototype.virtual = function (name, options) {
  //let virtuals = this.virtuals;
  const parts = name.split('.');

  return this.virtuals[name] = parts.reduce(function (mem, part, i) {
    mem[part] || (mem[part] = (i === parts.length-1) ? new VirtualType(options, name) : {});
    return mem[part];
  }, this.tree);
};

/**
* Returns the virtual type with the given `name`.
*
* @param {String} name
* @return {VirtualType}
*/

Schema.prototype.virtualpath = function (name) {
  return this.virtuals[name];
};

module.exports = Schema;
<|endoftext|>


<commit_before>
  //==================================================
  //
  //
  //
  //  THOSE TEST ARE **REALLY** IMPORTANT SO IF SOME
  //  OF THEM IS BROKEN, PLEASE, TAKE CARE OF IT
  //
  //  Visualization model specs
  //  TODO: active_layer_id specs
  //  TODO: notices specs
  //
  //==================================================

  describe("Visualization model", function() {
    var vis, table, table_server;

    beforeEach(function() {

      cdb.admin.CartoDBLayer.updateCartoCss = function() {};

      // Visualization model
      vis = new cdb.admin.Visualization({
        map_id:           96,
        active_layer_id:  null,
        name:             "test_table",
        description:      "Visualization description",
        tags:             ["jamon","probando","test"],
        privacy:          "PUBLIC",
        updated_at:       "2013-03-04T18:09:34+01:00",
        derived:          false
      });
    });

    /* map_id specs */
    describe('> map_id', function() {});

    /* isVisualization spec */
    describe('> isVisualization', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("should return if the model is a visualization or a table", function() {
        expect(vis.isVisualization()).toBeFalsy();
        vis.set({
          "type": "derived"
        });

        vis.map.layers.reset([
          new cdb.geo.MapLayer()
        ]);

        expect(vis.isVisualization()).toBeTruthy();
      });
    });

    /* transform to visualization specs */
    describe('> transform to visualization', function() {
      var cartodb_layer, map_payload;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});
        map_payload = {
            id: 69,
            bounding_box_ne: "[]",
            bounding_box_sw: "[]",
            view_bounds_ne: "[]",
            view_bounds_sw: "[]",
            center: "[]"
        };

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("should copy correctly", function() {
        var map_id = null;
        cdb.geo.Map.prototype.sync = function(method, m, opts) {
          map_id = m.id
          opts.success(map_payload, 200);
        }

        cdb.admin.CartoDBLayer.prototype.sync = function(method, m, opts){};
        cdb.geo.MapLayer.prototype.sync = function(method, m, opts) {};
        cdb.admin.Map.prototype.saveLayers = function(a) {
          a.success();
        }

        var callbacks = {
          success_: function(new_vis) {
            expect(new_vis.get('map_id')).not.toBe(vis.get('map_id'));
            expect(new_vis.get('name')).toBe(vis.get('name'));
            expect(new_vis.get('description')).toBe(vis.get('description'));
            expect(new_vis.get('tags').length).toBe(3);
            expect(new_vis.get('privacy')).toBe(vis.get('privacy'));
            expect(new_vis.get('updated_at')).toBe(vis.get('updated_at'));
            expect(new_vis.get('derived')).toBeTruthy();
            expect(new_vis.map.cid).not.toBe(vis.map.cid);
            expect(map_id).toBe(null);
          },
          error_: function() {}
        }

        vis.copy(callbacks.success_, callbacks.error_);
      });

    });

    /* name specs */
    describe('> name', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("should set table layer name if there is only one data layer <==> table", function() {
        vis.set({
          'derived': false
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({
            name: 'this_is_a_name'
          }, 200);
        }

        vis.map.layers.each(function(l){
          l.updateCartoCss = function() {};
        });

        vis.set('name', 'this is a name');
        expect(cartodb_layer.get('table_name')).toBe('this_is_a_name');
        expect(vis.get('name')).toBe('this_is_a_name');
      });

      it("shouldn't set table layer name if sync fails", function() {
        vis.set({
          'type': 'table'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        vis.map.layers.each(function(l){
          l.updateCartoCss = function() {};
        });

        vis.set('name', 'a visualization name');
        expect(cartodb_layer.get('table_name')).toBe('test_table');
        expect(vis.get('name')).toBe('test_table');
      });

      it("should set name of the table when the visualization name changes", function() {
        vis.set({ 'type': 'table' },{ silent: true });
        vis.set('name', 'ola_ke_ase');
        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({'name': 'ola_ke_ase'}, 200);
        }
        expect(cartodb_layer.table.get('name')).toEqual('ola_ke_ase');

      });

      it("shouldn't set name if it is a visualization", function() {
        vis.map.layers.add(new cdb.admin.CartoDBLayer({ table_name: 'jam'}));

        vis.set({
          'type': 'derived'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        spyOn(cartodb_layer.table, 'sync');

        vis.set('name', 'another name');

        expect(vis.map.getLayerAt(1).table.get('name')).not.toBe('another name');
        expect(vis.map.getLayerAt(2).table.get('name')).not.toBe('another name');
        expect(cartodb_layer.table.sync).not.toHaveBeenCalled();
      });
    });

    /* description specs */
    describe('> description', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("shouldn't set description layer name in any case", function() {
        cartodb_layer.table.set('description', 'hey description');
        vis.set({
          'type': 'table'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({
            description: 'this is a description'
          }, 200);
        }

        vis.set('description', 'this is a description');
        expect(vis.map.getLayerAt(1).table.get('description')).not.toBe('this is a description');
      });

      it("shouldn't set table layer description if sync fails", function() {
        cartodb_layer.table.set('description', 'Visualization description');
        vis.set('type', 'table');

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        vis.set('description', 'this is a description');
        expect(vis.map.getLayerAt(1).table.get('description')).toBe('Visualization description');
        expect(vis.get('description')).toBe('this is a description');
      });

      it("shouldn't set itself if there are at least 2 data layers", function() {
        vis.map.layers.add(new cdb.admin.CartoDBLayer({ table_name: 'test_table'}));

        vis.map.layers.each(function(l){
          if (l.table)
            l.table.set('description', 'desc example');
        });

        vis.set({
          'type': 'derived'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        spyOn(cartodb_layer.table, 'sync');

        vis.set('description', 'another world');

        expect(vis.map.getLayerAt(1).table.get('description')).not.toBe('another world');
        expect(vis.map.getLayerAt(2).table.get('description')).not.toBe('another world');
        expect(cartodb_layer.table.sync).not.toHaveBeenCalled();
      });
    });

    /* tags specs */
    describe('> tags', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("shouldn't set table layer tags if there is only one data layer <==> table", function() {
        cartodb_layer.table.set('tags', ['zurullo']);
        vis.set({
          'type': 'table'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({
            tags: ['zurullo']
          }, 200);
        }

        vis.set('tags', ['zurullo']);
        expect(vis.map.getLayerAt(1).table.get('tags').length).toBe(1);
        expect(vis.map.getLayerAt(1).table.get('tags')[0]).toBe('zurullo');
      });

      it("shouldn't set table layer tags if sync fails", function() {
        cartodb_layer.table.set('tags', ['eing']);
        vis.set({
          'type': 'table'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        vis.set('tags', ['esto', 'va', 'a', 'fallar']);

        expect(vis.map.getLayerAt(1).table.get('tags').length).toBe(1);
        expect(vis.map.getLayerAt(1).table.get('tags').toString()).toBe('eing');
        expect(vis.get('tags').length).toBe(4);
        expect(vis.get('tags').toString()).toBe("esto,va,a,fallar");
      });

      it("shouldn't set table layers tags if there are at least 2 data layers", function() {
        vis.map.layers.add(new cdb.admin.CartoDBLayer({ table_name: 'test_table'}));

        vis.map.layers.each(function(l){
          if (l.table)
            l.table.set('tags', ['i', 'love', 'this', 'game']);
        });

        vis.set({
          "type": "derived"
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        spyOn(cartodb_layer.table, 'sync');

        vis.set('tags', ['ciruelo']);

        expect(vis.map.getLayerAt(1).table.get('tags').length).not.toBe(1);
        expect(vis.map.getLayerAt(2).table.get('tags').length).not.toBe(1);
        expect(cartodb_layer.table.sync).not.toHaveBeenCalled();
      });
    });

    /* privacy specs */
    describe('> privacy', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("should set table layer privacy if there is only one data layer <==> table", function() {
        cartodb_layer.table.set('privacy', 'PUBLIC');
        vis.set({
          'derived': false
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({
            privacy: 'PRIVATE'
          }, 200);
        }

        vis.set('privacy', 'PRIVATE');
        expect(cartodb_layer.table.get('privacy').toLowerCase()).toBe('private');
      });

      it("shouldn't set table layer privacy if sync fails", function() {
        cartodb_layer.table.set('privacy', 'PUBLIC');
        vis.set({
          'derived': false
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        vis.set('privacy', 'PRIVATE');

        expect(cartodb_layer.table.get('privacy').toLowerCase()).toBe('public');
        expect(vis.get('privacy').toLowerCase()).toBe('public');
      });

      it("shouldn't set table layers privacy if there are at least 2 data layers", function() {
        vis.map.layers.add(new cdb.admin.CartoDBLayer({ table_name: 'test_table'}));

        vis.map.layers.each(function(l){
          if (l.table)
            l.table.set('privacy', 'PUBLIC');
        });

        vis.set({
          'type': 'derived'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        spyOn(cartodb_layer.table, 'sync');

        vis.set('privacy', 'PRIVATE');

        expect(vis.map.getLayerAt(1).table.get('privacy').toLowerCase()).toBe('public');
        expect(vis.get('privacy').toLowerCase()).toBe('private');
        expect(cartodb_layer.table.sync).not.toHaveBeenCalled();
      });
    });
  });
<commit_msg>vis spec init function fixed<commit_after>
  //==================================================
  //
  //
  //
  //  THOSE TEST ARE **REALLY** IMPORTANT SO IF SOME
  //  OF THEM IS BROKEN, PLEASE, TAKE CARE OF IT
  //
  //  Visualization model specs
  //
  //==================================================

  describe("Visualization model", function() {
    var vis, table, table_server;

    beforeEach(function() {

      cdb.admin.CartoDBLayer.updateCartoCss = function() {};

      // Visualization model
      vis = new cdb.admin.Visualization({
        map_id:           96,
        active_layer_id:  null,
        name:             "test_table",
        description:      "Visualization description",
        tags:             ["jamon","probando","test"],
        privacy:          "PUBLIC",
        updated_at:       "2013-03-04T18:09:34+01:00",
        type:             "table"
      });
    });

    /* map_id specs */
    describe('> map_id', function() {});

    /* isVisualization spec */
    describe('> isVisualization', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("should return if the model is a visualization or a table", function() {
        expect(vis.isVisualization()).toBeFalsy();
        vis.set({
          "type": "derived"
        });

        vis.map.layers.reset([
          new cdb.geo.MapLayer()
        ]);

        expect(vis.isVisualization()).toBeTruthy();
      });
    });

    /* transform to visualization specs */
    describe('> transform to visualization', function() {
      var cartodb_layer, map_payload;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});
        map_payload = {
            id: 69,
            bounding_box_ne: "[]",
            bounding_box_sw: "[]",
            view_bounds_ne: "[]",
            view_bounds_sw: "[]",
            center: "[]"
        };

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("should copy correctly", function() {
        var map_id = null;
        cdb.geo.Map.prototype.sync = function(method, m, opts) {
          map_id = m.id
          opts.success(map_payload, 200);
        }

        cdb.admin.CartoDBLayer.prototype.sync = function(method, m, opts){};
        cdb.geo.MapLayer.prototype.sync = function(method, m, opts) {};
        cdb.admin.Map.prototype.saveLayers = function(a) {
          a.success();
        }

        var callbacks = {
          success_: function(new_vis) {
            expect(new_vis.get('map_id')).not.toBe(vis.get('map_id'));
            expect(new_vis.get('name')).toBe(vis.get('name'));
            expect(new_vis.get('description')).toBe(vis.get('description'));
            expect(new_vis.get('tags').length).toBe(3);
            expect(new_vis.get('privacy')).toBe(vis.get('privacy'));
            expect(new_vis.get('updated_at')).toBe(vis.get('updated_at'));
            expect(new_vis.get('derived')).toBeTruthy();
            expect(new_vis.map.cid).not.toBe(vis.map.cid);
            expect(map_id).toBe(null);
          },
          error_: function() {}
        }

        vis.copy(callbacks.success_, callbacks.error_);
      });

    });

    /* name specs */
    describe('> name', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("should set table layer name if there is only one data layer <==> table", function() {
        vis.set({
          'derived': false
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({
            name: 'this_is_a_name'
          }, 200);
        }

        vis.map.layers.each(function(l){
          l.updateCartoCss = function() {};
        });

        vis.set('name', 'this is a name');
        expect(cartodb_layer.get('table_name')).toBe('this_is_a_name');
        expect(vis.get('name')).toBe('this_is_a_name');
      });

      it("shouldn't set table layer name if sync fails", function() {
        vis.set({
          'type': 'table'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        vis.map.layers.each(function(l){
          l.updateCartoCss = function() {};
        });

        vis.set('name', 'a visualization name');
        expect(cartodb_layer.get('table_name')).toBe('test_table');
        expect(vis.get('name')).toBe('test_table');
      });

      it("should set name of the table when the visualization name changes", function() {
        vis.set({ 'type': 'table' },{ silent: true });
        vis.set('name', 'ola_ke_ase');
        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({'name': 'ola_ke_ase'}, 200);
        }
        expect(cartodb_layer.table.get('name')).toEqual('ola_ke_ase');

      });

      it("shouldn't set name if it is a visualization", function() {
        vis.map.layers.add(new cdb.admin.CartoDBLayer({ table_name: 'jam'}));

        vis.set({
          'type': 'derived'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        spyOn(cartodb_layer.table, 'sync');

        vis.set('name', 'another name');

        expect(vis.map.getLayerAt(1).table.get('name')).not.toBe('another name');
        expect(vis.map.getLayerAt(2).table.get('name')).not.toBe('another name');
        expect(cartodb_layer.table.sync).not.toHaveBeenCalled();
      });
    });

    /* description specs */
    describe('> description', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("shouldn't set description layer name in any case", function() {
        cartodb_layer.table.set('description', 'hey description');
        vis.set({
          'type': 'table'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({
            description: 'this is a description'
          }, 200);
        }

        vis.set('description', 'this is a description');
        expect(vis.map.getLayerAt(1).table.get('description')).not.toBe('this is a description');
      });

      it("shouldn't set table layer description if sync fails", function() {
        cartodb_layer.table.set('description', 'Visualization description');
        vis.set('type', 'table');

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        vis.set('description', 'this is a description');
        expect(vis.map.getLayerAt(1).table.get('description')).toBe('Visualization description');
        expect(vis.get('description')).toBe('this is a description');
      });

      it("shouldn't set itself if there are at least 2 data layers", function() {
        vis.map.layers.add(new cdb.admin.CartoDBLayer({ table_name: 'test_table'}));

        vis.map.layers.each(function(l){
          if (l.table)
            l.table.set('description', 'desc example');
        });

        vis.set({
          'type': 'derived'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        spyOn(cartodb_layer.table, 'sync');

        vis.set('description', 'another world');

        expect(vis.map.getLayerAt(1).table.get('description')).not.toBe('another world');
        expect(vis.map.getLayerAt(2).table.get('description')).not.toBe('another world');
        expect(cartodb_layer.table.sync).not.toHaveBeenCalled();
      });
    });

    /* tags specs */
    describe('> tags', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("shouldn't set table layer tags if there is only one data layer <==> table", function() {
        cartodb_layer.table.set('tags', ['zurullo']);
        vis.set({
          'type': 'table'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({
            tags: ['zurullo']
          }, 200);
        }

        vis.set('tags', ['zurullo']);
        expect(vis.map.getLayerAt(1).table.get('tags').length).toBe(1);
        expect(vis.map.getLayerAt(1).table.get('tags')[0]).toBe('zurullo');
      });

      it("shouldn't set table layer tags if sync fails", function() {
        cartodb_layer.table.set('tags', ['eing']);
        vis.set({
          'type': 'table'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        vis.set('tags', ['esto', 'va', 'a', 'fallar']);

        expect(vis.map.getLayerAt(1).table.get('tags').length).toBe(1);
        expect(vis.map.getLayerAt(1).table.get('tags').toString()).toBe('eing');
        expect(vis.get('tags').length).toBe(4);
        expect(vis.get('tags').toString()).toBe("esto,va,a,fallar");
      });

      it("shouldn't set table layers tags if there are at least 2 data layers", function() {
        vis.map.layers.add(new cdb.admin.CartoDBLayer({ table_name: 'test_table'}));

        vis.map.layers.each(function(l){
          if (l.table)
            l.table.set('tags', ['i', 'love', 'this', 'game']);
        });

        vis.set({
          "type": "derived"
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        spyOn(cartodb_layer.table, 'sync');

        vis.set('tags', ['ciruelo']);

        expect(vis.map.getLayerAt(1).table.get('tags').length).not.toBe(1);
        expect(vis.map.getLayerAt(2).table.get('tags').length).not.toBe(1);
        expect(cartodb_layer.table.sync).not.toHaveBeenCalled();
      });
    });

    /* privacy specs */
    describe('> privacy', function() {
      var cartodb_layer;

      beforeEach(function() {
        cartodb_layer = new cdb.admin.CartoDBLayer({ table_name: 'test_table'});

        vis.map.layers.reset([
          new cdb.geo.MapLayer(),
          cartodb_layer
        ]);
      });

      it("should set table layer privacy if there is only one data layer <==> table", function() {
        cartodb_layer.table.set('privacy', 'PUBLIC');
        vis.set({
          'derived': false
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.success({
            privacy: 'PRIVATE'
          }, 200);
        }

        vis.set('privacy', 'PRIVATE');
        expect(cartodb_layer.table.get('privacy').toLowerCase()).toBe('private');
      });

      it("shouldn't set table layer privacy if sync fails", function() {
        cartodb_layer.table.set('privacy', 'PUBLIC');
        vis.set({
          'derived': false
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        vis.set('privacy', 'PRIVATE');

        expect(cartodb_layer.table.get('privacy').toLowerCase()).toBe('public');
        expect(vis.get('privacy').toLowerCase()).toBe('public');
      });

      it("shouldn't set table layers privacy if there are at least 2 data layers", function() {
        vis.map.layers.add(new cdb.admin.CartoDBLayer({ table_name: 'test_table'}));

        vis.map.layers.each(function(l){
          if (l.table)
            l.table.set('privacy', 'PUBLIC');
        });

        vis.set({
          'type': 'derived'
        },{
          silent: true
        });

        cartodb_layer.table.sync = function(a, b, opts) {
          opts.error({}, 404);
        }

        spyOn(cartodb_layer.table, 'sync');

        vis.set('privacy', 'PRIVATE');

        expect(vis.map.getLayerAt(1).table.get('privacy').toLowerCase()).toBe('public');
        expect(vis.get('privacy').toLowerCase()).toBe('private');
        expect(cartodb_layer.table.sync).not.toHaveBeenCalled();
      });
    });
  });
<|endoftext|>


<commit_before>var util = require('util'),
    EventEmitter = require('events').EventEmitter,
    net = require('net'),
    Parser = require('./Parser'),
    Message = require('./Message'),
    packageInfo = require('../package.json'),
    proxyParser = require('./proxyParser')

/**
 * Handles relp client communication
 * Will emit new messages and connection closes for the consumer to act on
 * If a connection closes, non ack'd messages should be dropped since they will be retransmitted
 *
 * @param {Object} options Configuration options for the server
 * @param {Number} options.port The port to listen for new connections
 * @param {String} [options.host] The host to listen for new connections on
 * @param {Server} [options.server] Configure your own server and provide it here
 *      You must call listen on the provided server object
 * @param {String} [options.connectionEvent='connection'] Change the event name for new connections.
 *      This is especially useful for TLS support since a TLS server emits 'secureConnection` events when
 *      a socket is ready
 * @param {Boolean} [options.proxyProtocol=false] True to expect PROXY protocol, false if not
 * @param {Object} [options.parser=Parser] A custom parser, if you must
 * @param {Object} [options.proxyParser=proxyParser] A custom PROXY protocol parser, if you must
 * @param {Object} [options.logger] A logger to use in place of console
 *
 * @constructor
 */
var Server = function (options) {
    Server.super_.call(this)

    var self = this

    self.options = options
    self.logger = options.logger || console
    self.sockets = {}
    self._nextSocketId = 1

    self.paused = false
    self.server = options.server || net.createServer()

    self.server.on(options.connectionEvent || 'connection', function (socket) {
        self._setupConnection(socket)
    })

    self.server.on('error', function (error) {
        console.log('ACK', error.stack)
    })

    if (!options.server) {
        self.server.listen(options.port, options.host)
    }
}

Server.RELP_VERSION = '0'
Server.RELP_SOFTWARE = 'node-' + packageInfo.name + ',' + packageInfo.version + ',' + packageInfo.repository.url
Server.RELP_COMMANDS = ['open', 'close', 'syslog']

util.inherits(Server, EventEmitter)
module.exports = Server

/**
 * Acknowledges the receipt of a relp message to the relp client
 * Messages with a transaction id below 1 will not be ack'd
 * Messages that have already been ack'd will not be re-ack'd
 *
 * @param {Message} message A relp message to ack
 * @param {String} [data='200 OK'] Data to send
 * @param {boolean} [close=false] Whether or not to close the socket after sending
 *
 * @returns {boolean} True if the message was ack'd, false if not
 */
Server.prototype.ack = function (message, data, close) {
    var self = this
      , socket = self.sockets[message.socketId]
      , actualData = data || '200 OK'

    if (!socket || message.transactionId < 1 || message.acked) {
        return false
    }

    var ackMessage = new Message(message.transactionId, 'rsp', actualData)
      , buffer = socket.parser.serialize(ackMessage)

    if (close) {
        socket.end(buffer)
    } else {
        socket.write(buffer)
    }

    message.acked = true
    return true
}

/**
 * Pauses all existing and future connections
 * You must resume before seeing anymore messages
 *
 * @return {boolean} True on success, false if already paused
 */
Server.prototype.pause = function () {
    if (this.paused) {
        return false
    }

    for (var socketId in this.sockets) {
        this.sockets[socketId].pause()
    }

    this.paused = true
    return true
}

/**
 * Resumes a previously paused server
 * Expect a flood of messages!
 *
 * @returns {boolean} True on success, false if not already paused
 */
Server.prototype.resume = function () {
    if (!this.paused) {
        return false
    }

    for (var socketId in this.sockets) {
        this.sockets[socketId].resume()
    }

    this.paused = false
    return true
}

/**
 * Closes each open connection and finally the server
 */
Server.prototype.close = function () {
    this.server.close()

    for (var socketId in this.sockets) {
        this.sockets[socketId].end()
    }
}

/**
 * Dispatches a message to the proper handler
 *
 * @param {Message} message The message to handle
 *
 * @private
 */
Server.prototype._handleMessage = function (message) {
    var self = this,
        socket = this.sockets[message.socketId]

    switch (message.command) {
        case 'syslog':
            //Are we getting messages before the handshake?
            if (socket.relpHandshakeComplete === false) {
                //TODO: emit?
                self.logger.error(
                    'Connection id', message.socketId, 'sent a syslog message before the handshake, disconnecting'
                )

                return socket.destroy()
            }

            this.emit('message', message)
            break

        case 'open':
            this._handleOpen(message)
            break

        case 'close':
            this._handleClose(message)
            break

        default:
            //TODO: emit?
            self.logger.error('Connection id', message.socketId, 'sent an unknown command:', message.command)
            return socket.destroy()
    }
}

/**
 * Handles the open message
 * This is what starts a relp session
 *
 * @param {Message} message The open message to work from
 *
 * @private
 */
Server.prototype._handleOpen = function (message) {
    var self = this
      , rawOffers = message.body.split('\n')
      , offers = {}
      , body

    rawOffers.forEach(function (rawOffer) {
        var parts = rawOffer.split('=')
        offers[parts[0]] = parts[1]
    })

    if (offers['relp_version'] !== Server.RELP_VERSION) {
        body = '500 Insufficient version\n'
            + Server.RELP_VERSION + ' required, ' + offers['relp_version'] + ' provided'
        return self.ack(message, body, true)
    }

    if (!offers['commands']) {
        body = '500 No commands provided'
        return self.ack(message, body, true)
    }

    offers['commands'].split(',').some(function (command) {
        if (Server.RELP_COMMANDS.indexOf(command.trim().toLowerCase()) === -1) {
            body = '500 Invalid command\n'
                + command + ' is not supported'

            return self.ack(message, body, true)
        }
    })

    body = '200 OK '
        + 'relp_version=' + Server.RELP_VERSION + '\n'
        + 'relp_software=' + Server.RELP_SOFTWARE + '\n'
        + 'commands=' + offers['commands']

    self.ack(message, body)
    self.sockets[message.socketId].relpHandshakeComplete = true
}

/**
 * Handles a close message
 * Acks the message and closes the socket
 *
 * @param {Message} message The close message to work from
 *
 * @private
 */
Server.prototype._handleClose = function (message) {
    this.ack(message, '', true)
}

/**
 * Sets up a connection to be managed
 * Handles new messages, socket closes/opens, etc
 *
 * @param {Socket} socket The socket to handle events for
 *
 * @private
 */
Server.prototype._setupConnection = function (socket) {
    if (this.paused) {
        socket.pause()
    }

    var self = this,
        useParser = self.options.parser || Parser,
        useProxyParser = self.options.proxyParser || proxyParser

    socket.socketId = self._nextSocketId++
    self.sockets[socket.socketId] = socket

    socket.parser = new useParser()

    socket.proxyHandshakeComplete = false
    socket.relpHandshakeComplete = false

    socket.parser.on('error', function (error) {
        self.logger.error('Connection #', socket.socketId, 'had a parse error, disconnecting.', error)
        socket.destroy()
    })

    socket.parser.on('message', function (message) {
        message.socketId = socket.socketId
        message.remoteAddress = socket.remoteAddress
        message.proxyDetails = socket.proxyDetails
        self._handleMessage(message)
    })

    socket.on('data', function (data) {
        if (self.options.proxyProtocol && socket.proxyHandshakeComplete === false) {
            var result = useProxyParser.consume(data)
            socket.proxyHandshakeComplete = true
            socket.proxyDetails = result.proxy
            data = result.buffer
        }

        socket.parser.consume(data)
    })

    socket.on('error', function (error) {
        self.logger.error('Connection #', socket.socketId, 'had an error:', error)
        socket.destroy()
    })

    socket.on('close', function () {
        /**
         * Notifies consumers that a client connection has closed and is about to be unreferenced
         *
         * @event Server#connectionClosed
         * @type {Socket} The socket that closed
         * @property {Number} socketId The id of the socket that was closed, any non ack'd message with this
         *      socketId should stop processing to avoid duplicates
         */
        self.emit('connectionClosed', socket)

        delete self.sockets[socket.socketId]
    })

    /**
     * Notifies consumers that a new client connection has opened
     *
     * @event Server#connectionOpened
     * @type {Socket} The socket that opened
     * @property {Number} socketId the id of the socket that just opened, all messages received on this connection
     *      will contain this socketId
     */
    self.emit('connectionOpened', socket)
}
<commit_msg>Add nack for ease of use<commit_after>var util = require('util'),
    EventEmitter = require('events').EventEmitter,
    net = require('net'),
    Parser = require('./Parser'),
    Message = require('./Message'),
    packageInfo = require('../package.json'),
    proxyParser = require('./proxyParser')

/**
 * Handles relp client communication
 * Will emit new messages and connection closes for the consumer to act on
 * If a connection closes, non ack'd messages should be dropped since they will be retransmitted
 *
 * @param {Object} options Configuration options for the server
 * @param {Number} options.port The port to listen for new connections
 * @param {String} [options.host] The host to listen for new connections on
 * @param {Server} [options.server] Configure your own server and provide it here
 *      You must call listen on the provided server object
 * @param {String} [options.connectionEvent='connection'] Change the event name for new connections.
 *      This is especially useful for TLS support since a TLS server emits 'secureConnection` events when
 *      a socket is ready
 * @param {Boolean} [options.proxyProtocol=false] True to expect PROXY protocol, false if not
 * @param {Object} [options.parser=Parser] A custom parser, if you must
 * @param {Object} [options.proxyParser=proxyParser] A custom PROXY protocol parser, if you must
 * @param {Object} [options.logger] A logger to use in place of console
 *
 * @constructor
 */
var Server = function (options) {
    Server.super_.call(this)

    var self = this

    self.options = options
    self.logger = options.logger || console
    self.sockets = {}
    self._nextSocketId = 1

    self.paused = false
    self.server = options.server || net.createServer()

    self.server.on(options.connectionEvent || 'connection', function (socket) {
        self._setupConnection(socket)
    })

    self.server.on('error', function (error) {
        console.log('ACK', error.stack)
    })

    if (!options.server) {
        self.server.listen(options.port, options.host)
    }
}

Server.RELP_VERSION = '0'
Server.RELP_SOFTWARE = 'node-' + packageInfo.name + ',' + packageInfo.version + ',' + packageInfo.repository.url
Server.RELP_COMMANDS = ['open', 'close', 'syslog']

util.inherits(Server, EventEmitter)
module.exports = Server

/**
 * Acknowledges the receipt of a relp message to the relp client
 * Messages with a transaction id below 1 will not be ack'd
 * Messages that have already been ack'd will not be re-ack'd
 *
 * @param {Message} message A relp message to ack
 * @param {String} [data='200 OK'] Data to send
 * @param {boolean} [close=false] Whether or not to close the socket after sending
 *
 * @returns {boolean} True if the message was ack'd, false if not
 */
Server.prototype.ack = function (message, data, close) {
    var self = this
      , socket = self.sockets[message.socketId]
      , actualData = data || '200 OK'

    if (!socket || message.transactionId < 1 || message.acked) {
        return false
    }

    var ackMessage = new Message(message.transactionId, 'rsp', actualData)
      , buffer = socket.parser.serialize(ackMessage)

    if (close) {
        socket.end(buffer)
    } else {
        socket.write(buffer)
    }

    message.acked = true
    return true
}

/**
 * Negatively acknowledges the receipt of a relp message to the relp client
 * This should result in receiving the message again from the client
 *
 * @param {Message} message A relp message to ack
 * @param {String} [error='Error'] Error message to send
 * @param {boolean} [close=false] Whether or not to close the socket after sending
 *
 * @returns {boolean} True if the message was nack'd, false if not
 */
Server.prototype.nack = function (message, error, close) {
    var useError = error || 'Error'
    return this.ack(message, '500 ' + useError, close)
}

/**
 * Pauses all existing and future connections
 * You must resume before seeing anymore messages
 *
 * @return {boolean} True on success, false if already paused
 */
Server.prototype.pause = function () {
    if (this.paused) {
        return false
    }

    for (var socketId in this.sockets) {
        this.sockets[socketId].pause()
    }

    this.paused = true
    return true
}

/**
 * Resumes a previously paused server
 * Expect a flood of messages!
 *
 * @returns {boolean} True on success, false if not already paused
 */
Server.prototype.resume = function () {
    if (!this.paused) {
        return false
    }

    for (var socketId in this.sockets) {
        this.sockets[socketId].resume()
    }

    this.paused = false
    return true
}

/**
 * Closes each open connection and finally the server
 */
Server.prototype.close = function () {
    this.server.close()

    for (var socketId in this.sockets) {
        this.sockets[socketId].end()
    }
}

/**
 * Dispatches a message to the proper handler
 *
 * @param {Message} message The message to handle
 *
 * @private
 */
Server.prototype._handleMessage = function (message) {
    var self = this,
        socket = this.sockets[message.socketId]

    switch (message.command) {
        case 'syslog':
            //Are we getting messages before the handshake?
            if (socket.relpHandshakeComplete === false) {
                //TODO: emit?
                self.logger.error(
                    'Connection id', message.socketId, 'sent a syslog message before the handshake, disconnecting'
                )

                return socket.destroy()
            }

            this.emit('message', message)
            break

        case 'open':
            this._handleOpen(message)
            break

        case 'close':
            this._handleClose(message)
            break

        default:
            //TODO: emit?
            self.logger.error('Connection id', message.socketId, 'sent an unknown command:', message.command)
            return socket.destroy()
    }
}

/**
 * Handles the open message
 * This is what starts a relp session
 *
 * @param {Message} message The open message to work from
 *
 * @private
 */
Server.prototype._handleOpen = function (message) {
    var self = this
      , rawOffers = message.body.split('\n')
      , offers = {}
      , body

    rawOffers.forEach(function (rawOffer) {
        var parts = rawOffer.split('=')
        offers[parts[0]] = parts[1]
    })

    if (offers['relp_version'] !== Server.RELP_VERSION) {
        body = '500 Insufficient version\n'
            + Server.RELP_VERSION + ' required, ' + offers['relp_version'] + ' provided'
        return self.ack(message, body, true)
    }

    if (!offers['commands']) {
        body = '500 No commands provided'
        return self.ack(message, body, true)
    }

    offers['commands'].split(',').some(function (command) {
        if (Server.RELP_COMMANDS.indexOf(command.trim().toLowerCase()) === -1) {
            body = '500 Invalid command\n'
                + command + ' is not supported'

            return self.ack(message, body, true)
        }
    })

    body = '200 OK '
        + 'relp_version=' + Server.RELP_VERSION + '\n'
        + 'relp_software=' + Server.RELP_SOFTWARE + '\n'
        + 'commands=' + offers['commands']

    self.ack(message, body)
    self.sockets[message.socketId].relpHandshakeComplete = true
}

/**
 * Handles a close message
 * Acks the message and closes the socket
 *
 * @param {Message} message The close message to work from
 *
 * @private
 */
Server.prototype._handleClose = function (message) {
    this.ack(message, '', true)
}

/**
 * Sets up a connection to be managed
 * Handles new messages, socket closes/opens, etc
 *
 * @param {Socket} socket The socket to handle events for
 *
 * @private
 */
Server.prototype._setupConnection = function (socket) {
    if (this.paused) {
        socket.pause()
    }

    var self = this,
        useParser = self.options.parser || Parser,
        useProxyParser = self.options.proxyParser || proxyParser

    socket.socketId = self._nextSocketId++
    self.sockets[socket.socketId] = socket

    socket.parser = new useParser()

    socket.proxyHandshakeComplete = false
    socket.relpHandshakeComplete = false

    socket.parser.on('error', function (error) {
        self.logger.error('Connection #', socket.socketId, 'had a parse error, disconnecting.', error)
        socket.destroy()
    })

    socket.parser.on('message', function (message) {
        message.socketId = socket.socketId
        message.remoteAddress = socket.remoteAddress
        message.proxyDetails = socket.proxyDetails
        self._handleMessage(message)
    })

    socket.on('data', function (data) {
        if (self.options.proxyProtocol && socket.proxyHandshakeComplete === false) {
            var result = useProxyParser.consume(data)
            socket.proxyHandshakeComplete = true
            socket.proxyDetails = result.proxy
            data = result.buffer
        }

        socket.parser.consume(data)
    })

    socket.on('error', function (error) {
        self.logger.error('Connection #', socket.socketId, 'had an error:', error)
        socket.destroy()
    })

    socket.on('close', function () {
        /**
         * Notifies consumers that a client connection has closed and is about to be unreferenced
         *
         * @event Server#connectionClosed
         * @type {Socket} The socket that closed
         * @property {Number} socketId The id of the socket that was closed, any non ack'd message with this
         *      socketId should stop processing to avoid duplicates
         */
        self.emit('connectionClosed', socket)

        delete self.sockets[socket.socketId]
    })

    /**
     * Notifies consumers that a new client connection has opened
     *
     * @event Server#connectionOpened
     * @type {Socket} The socket that opened
     * @property {Number} socketId the id of the socket that just opened, all messages received on this connection
     *      will contain this socketId
     */
    self.emit('connectionOpened', socket)
}
<|endoftext|>


<commit_before>var makeMap = function(stateColors) {
  var map = L.map('map');

  var mesh_layer; //Rendered map
  var last_update_bounds;
  var last_update_centroid;

  var tileLayer = L.tileLayer('http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png', {
      attribution: '© OpenStreetMap contributors'
  }).addTo(map);

  $('#address_search_form').submit(function(event) {
    event.preventDefault();
    FitPcode($('#address_search').val());
  });

  var FitPcode = function(pcode) {
    $.getJSON('/pcode_get_bounds?pcode=' + pcode, function(json) {
      if (json) {
      map.fitBounds([[json.swlat,json.swlng],[json.nelat,json.nelng]])
    }
    else {alert("Postcode not found");}
    });
  };

  var FindLocation = function() {
    var lat = Cookies.get("lat");
    var lng = Cookies.get("lng");
    var pcode = Cookies.get("postcode");
    if (lat && lng) {
      var zoom = Cookies.set("zoom");
        if (zoom) {
        map.setView([lat,lng],zoom)
        }
        else {
          map.setView([lat,lng],15)
        }
    }
    else if (pcode) {
      FitPcode(pcode)
    }
    else {
      var australia_coord = [-29.8650, 131.2094];
      map.setView(australia_coord, 5);
    }
  };

  FindLocation();

  function addGeoJsonProperties(json) {

  var layer = L.geoJson(json,{
    style: function(feature) {
        switch (feature.properties.claim_status) {
        case 'claimed_by_you': return stateColors.claimed_by_you
        case 'claimed': return stateColors.claimed
        case 'quarantine': return stateColors.quarantine
        default: return stateColors.unclaimed
        }
      },
    onEachFeature: function(feature, featureLayer) {
      featureLayer._leaflet_id = feature.properties.slug;

      function downloadmesh (mesh_id) {
        var base64str = $.get('https://4oqtu02x7f.execute-api.ap-southeast-2.amazonaws.com/prod/map?slug=' + mesh_id, function(base64str) {
          if (base64str.message == "Internal server error") {
            return alert("This area cannot be downloaded due to a pdf rendering error, please try another area.");
          };
          //decode base64 string
          var binary = atob(base64str.base64.replace(/\s/g, ''));
          var len = binary.length;
          var buffer = new ArrayBuffer(len);
          var view = new Uint8Array(buffer);
          for (var i = 0; i < len; i++) {
            view[i] = binary.charCodeAt(i);
          }

          //create the blob object
          var blob = new Blob([view], {type: "application/pdf"});

          //create clickable URL for download
          var url = window.URL.createObjectURL(blob);
          var a = document.createElement('a');
          a.href = url;
          a.download = mesh_id + '.pdf';
          a.click();
          $('#load').addClass('hidden');
        })
      };

      this.btnClaim = function (featureLayer) {
        var leaflet_id = this._leaflet_id;
        $.post("/claim_meshblock/" + leaflet_id);
        $('.unclaim').removeClass('hidden');
        $('.download').removeClass('hidden');
        $('.claim').addClass('hidden');
        this.setStyle(stateColors.claimed_by_you)
        $('#load').removeClass('hidden');
        downloadmesh(leaflet_id);
      }

      this.btnUnclaim = function (featureLayer) {
        $.post("/unclaim_meshblock/" + this._leaflet_id);
        this.setStyle(stateColors.unclaimed)
          $('.unclaim').addClass('hidden');
          $('.download').addClass('hidden');
          $('.claim').removeClass('hidden');
      }

      this.btnDownload = function (featureLayer) {
        var leaflet_id = this._leaflet_id;
        $('#load').removeClass('hidden');
        downloadmesh(leaflet_id);
      }

      function create_popup_btn(container, div_class, btn_text_inner, faq_text_inner) {
          var grpdiv = L.DomUtil.create('div', 'popupgrp hidden ' + div_class, container)
          var txtdiv = L.DomUtil.create('div', 'popuptxt txt' + div_class, grpdiv)
            txtdiv.innerHTML = faq_text_inner
          var btndiv = L.DomUtil.create('div', 'popupbutton btn' + div_class, grpdiv)
          var btn = L.DomUtil.create('button', '', btndiv)
            btn.setAttribute('type', 'button')
            btn.innerHTML = btn_text_inner
          var btndom = L.DomEvent
            btndom.addListener(btn, 'click', L.DomEvent.stopPropagation)
            btndom.addListener(btn, 'click', L.DomEvent.preventDefault)
            return { grpdiv, btn, btndom }
        };

      var container = L.DomUtil.create('div')
      var claimout = create_popup_btn(container, 'claim','Claim + Download',
        'Click to claim area and download PDF of addresses to doorknock.
');
        claimout.btndom.addListener(claimout.btn, 'click', this.btnClaim, featureLayer);
      var unclaimout = create_popup_btn(container, 'unclaim','Unclaim',
        'Click to remove your claim on this area.
');
        unclaimout.btndom.addListener(unclaimout.btn, 'click', this.btnUnclaim, featureLayer);
      var downloadout = create_popup_btn(container, 'download','Download',
        'Click to download your claimed area.
')
        downloadout.btndom.addListener(downloadout.btn, 'click', this.btnDownload, featureLayer);
      var otherstxtcontainer = L.DomUtil.create('div', 'popuptxt hidden otherstext', container)
        otherstxtcontainer.innerHTML = 'This area is claimed by someone else and is unable to be claimed.'
      var quarantinetxtcontainer = L.DomUtil.create('div', 'popuptxt hidden quarantinetext', container)
        quarantinetxtcontainer.innerHTML = 'This area is coordinated by a central event. ' +
        'Click here to find it.';
      if (feature.properties.claim_status === 'claimed_by_you') {
        L.DomUtil.removeClass(unclaimout.grpdiv, 'hidden');
        L.DomUtil.removeClass(downloadout.grpdiv, 'hidden');
        var popup = L.popup({},featureLayer).setContent(container);
      }
      else if (feature.properties.claim_status === 'claimed') {
        L.DomUtil.removeClass(otherstxtcontainer, 'hidden');
        var popup = L.popup({},featureLayer).setContent(container);
      }
      else if (feature.properties.claim_status === 'quarantine') {
        L.DomUtil.removeClass(quarantinetxtcontainer, 'hidden');
        var popup = L.popup({},featureLayer).setContent(container);
      }
      else {
        L.DomUtil.removeClass(claimout.grpdiv, 'hidden');
        var popup = L.popup({},featureLayer).setContent(container);
      }
      featureLayer.bindPopup(popup)

    }
  });

    return layer;
  };

  function getMeshblockCallback(json) {
    if (mesh_layer) {map.removeLayer(mesh_layer)};
    mesh_layer = addGeoJsonProperties(json);
    mesh_layer.addTo(map);
    $('#load').addClass('hidden');
  };

  var instruct = L.control();
  instruct.onAdd = function(map) {
    this._div = L.DomUtil.create('div', 'instruct');
    this._div.innerHTML = "Zoom in further to load doorknockable areas.";
    this.update();
    return this._div;
  }

  instruct.update = function() {
    $(".instruct").toggleClass('hidden', map.getZoom() > 14)
  }

  instruct.addTo(map);

  function updateMap() {
    var lat_lng_bnd = map.getBounds();
    var lat_lng_centroid = map.getCenter();
    Cookies.set("lat",lat_lng_centroid.lat);
    Cookies.set("lng",lat_lng_centroid.lng);
    if (last_update_centroid) {
      var distance_moved = lat_lng_centroid.distanceTo(last_update_centroid);
    }
    else {var distance_moved = 201};
    var zoom = map.getZoom();
    if (zoom > 14) {
      var reload_dist = 1000/(zoom-14);
      }
      else {
        var reload_dist = 0;
      };
    Cookies.set("zoom",zoom);
    var swlat = lat_lng_bnd.getSouthWest().lat;
    var swlng = lat_lng_bnd.getSouthWest().lng;
    var nelat = lat_lng_bnd.getNorthEast().lat;
    var nelng = lat_lng_bnd.getNorthEast().lng;
    //Reload map if zoom not too high
    //Distance moved is not short
    //and
    //there is no last_update or the current map bounds are not within the last update's
    if(zoom > 14 && (!last_update_bounds || distance_moved > reload_dist) &&
      (!last_update_bounds || !last_update_bounds.contains(lat_lng_bnd))) {
      $('#load').removeClass('hidden');
      var url = '/meshblocks_bounds?swlat=' + swlat + '&swlng=' + swlng
      + '&nelat=' + nelat + '&nelng=' + nelng;
      $.getJSON(url, function(json) {
        getMeshblockCallback(json);
        last_update_bounds = map.getBounds();
        last_update_centroid = map.getCenter();
      })
      .fail( function() {
        $('#load').addClass('hidden');
      } );
    }
    instruct.update();
  };

  map.on('moveend', function() {
    updateMap();
  });

  var legend = L.control({position: 'bottomright'});

  legend.onAdd = function(map) {
    var div = L.DomUtil.create('div', 'legend');
    div.innerHTML = [
      '
My area
',
      '
Claimed
',
      '
Organised door knocking event
',
      '
Yet to be claimed!
'
    ].join('');
    return div;
  }

  legend.addTo(map);
  map.whenReady(updateMap);
}

var windowHeight = function(){
    if(window.innerHeight != undefined){
        return window.innerHeight;
    }
    else{
        var B= document.body, D= document.documentElement;
        return Math.max(D.clientHeight, B.clientHeight);
    }
}

$('#map').height(windowHeight() - $('.header').height());
$('#map').width("100%");

var stateColors =  {
  claimed_by_you: {"fillColor": "#9d5fa7", "color": "#111111",
    "weight": 1, "opacity": 0.65, "fillOpacity": 0.8}, //Purple
  unclaimed: {"fillColor": "#ffc746", "color": "#111111",
    "weight": 1, "opacity": 0.65, "fillOpacity": 0.2}, //Yellow
  claimed: {"fillColor": "#d5545a", "color": "#111111",
    "weight": 1, "opacity": 0.65, "fillOpacity": 0.8}, //Red
  quarantine: {"fillColor": "#6dbd4b", "color": "#111111",
    "weight": 1, "opacity": 0.65, "fillOpacity": 0.8}, //Green
};

function openfaq(){
  $("#dialog").dialog({minWidth: 800, height: 500,
    beforeClose: function(e,ui){$("#dialog").addClass('hidden');}});
  $("#dialog").removeClass('hidden');
};

$('#faqlink').click(function() {
  openfaq();
});

var map = makeMap(stateColors);

openfaq();
<commit_msg>faq mobile readability improvements<commit_after>var makeMap = function(stateColors) {
  var map = L.map('map');

  var mesh_layer; //Rendered map
  var last_update_bounds;
  var last_update_centroid;

  var tileLayer = L.tileLayer('http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png', {
      attribution: '© OpenStreetMap contributors'
  }).addTo(map);

  $('#address_search_form').submit(function(event) {
    event.preventDefault();
    FitPcode($('#address_search').val());
  });

  var FitPcode = function(pcode) {
    $.getJSON('/pcode_get_bounds?pcode=' + pcode, function(json) {
      if (json) {
      map.fitBounds([[json.swlat,json.swlng],[json.nelat,json.nelng]])
    }
    else {alert("Postcode not found");}
    });
  };

  var FindLocation = function() {
    var lat = Cookies.get("lat");
    var lng = Cookies.get("lng");
    var pcode = Cookies.get("postcode");
    if (lat && lng) {
      var zoom = Cookies.set("zoom");
        if (zoom) {
        map.setView([lat,lng],zoom)
        }
        else {
          map.setView([lat,lng],15)
        }
    }
    else if (pcode) {
      FitPcode(pcode)
    }
    else {
      var australia_coord = [-29.8650, 131.2094];
      map.setView(australia_coord, 5);
    }
  };

  FindLocation();

  function addGeoJsonProperties(json) {

  var layer = L.geoJson(json,{
    style: function(feature) {
        switch (feature.properties.claim_status) {
        case 'claimed_by_you': return stateColors.claimed_by_you
        case 'claimed': return stateColors.claimed
        case 'quarantine': return stateColors.quarantine
        default: return stateColors.unclaimed
        }
      },
    onEachFeature: function(feature, featureLayer) {
      featureLayer._leaflet_id = feature.properties.slug;

      function downloadmesh (mesh_id) {
        var base64str = $.get('https://4oqtu02x7f.execute-api.ap-southeast-2.amazonaws.com/prod/map?slug=' + mesh_id, function(base64str) {
          if (base64str.message == "Internal server error") {
            return alert("This area cannot be downloaded due to a pdf rendering error, please try another area.");
          };
          //decode base64 string
          var binary = atob(base64str.base64.replace(/\s/g, ''));
          var len = binary.length;
          var buffer = new ArrayBuffer(len);
          var view = new Uint8Array(buffer);
          for (var i = 0; i < len; i++) {
            view[i] = binary.charCodeAt(i);
          }

          //create the blob object
          var blob = new Blob([view], {type: "application/pdf"});

          //create clickable URL for download
          var url = window.URL.createObjectURL(blob);
          var a = document.createElement('a');
          a.href = url;
          a.download = mesh_id + '.pdf';
          a.click();
          $('#load').addClass('hidden');
        })
      };

      this.btnClaim = function (featureLayer) {
        var leaflet_id = this._leaflet_id;
        $.post("/claim_meshblock/" + leaflet_id);
        $('.unclaim').removeClass('hidden');
        $('.download').removeClass('hidden');
        $('.claim').addClass('hidden');
        this.setStyle(stateColors.claimed_by_you)
        $('#load').removeClass('hidden');
        downloadmesh(leaflet_id);
      }

      this.btnUnclaim = function (featureLayer) {
        $.post("/unclaim_meshblock/" + this._leaflet_id);
        this.setStyle(stateColors.unclaimed)
          $('.unclaim').addClass('hidden');
          $('.download').addClass('hidden');
          $('.claim').removeClass('hidden');
      }

      this.btnDownload = function (featureLayer) {
        var leaflet_id = this._leaflet_id;
        $('#load').removeClass('hidden');
        downloadmesh(leaflet_id);
      }

      function create_popup_btn(container, div_class, btn_text_inner, faq_text_inner) {
          var grpdiv = L.DomUtil.create('div', 'popupgrp hidden ' + div_class, container)
          var txtdiv = L.DomUtil.create('div', 'popuptxt txt' + div_class, grpdiv)
            txtdiv.innerHTML = faq_text_inner
          var btndiv = L.DomUtil.create('div', 'popupbutton btn' + div_class, grpdiv)
          var btn = L.DomUtil.create('button', '', btndiv)
            btn.setAttribute('type', 'button')
            btn.innerHTML = btn_text_inner
          var btndom = L.DomEvent
            btndom.addListener(btn, 'click', L.DomEvent.stopPropagation)
            btndom.addListener(btn, 'click', L.DomEvent.preventDefault)
            return { grpdiv, btn, btndom }
        };

      var container = L.DomUtil.create('div')
      var claimout = create_popup_btn(container, 'claim','Claim + Download',
        'Click to claim area and download PDF of addresses to doorknock.
');
        claimout.btndom.addListener(claimout.btn, 'click', this.btnClaim, featureLayer);
      var unclaimout = create_popup_btn(container, 'unclaim','Unclaim',
        'Click to remove your claim on this area.
');
        unclaimout.btndom.addListener(unclaimout.btn, 'click', this.btnUnclaim, featureLayer);
      var downloadout = create_popup_btn(container, 'download','Download',
        'Click to download your claimed area.
')
        downloadout.btndom.addListener(downloadout.btn, 'click', this.btnDownload, featureLayer);
      var otherstxtcontainer = L.DomUtil.create('div', 'popuptxt hidden otherstext', container)
        otherstxtcontainer.innerHTML = 'This area is claimed by someone else and is unable to be claimed.'
      var quarantinetxtcontainer = L.DomUtil.create('div', 'popuptxt hidden quarantinetext', container)
        quarantinetxtcontainer.innerHTML = 'This area is coordinated by a central event. ' +
        'Click here to find it.';
      if (feature.properties.claim_status === 'claimed_by_you') {
        L.DomUtil.removeClass(unclaimout.grpdiv, 'hidden');
        L.DomUtil.removeClass(downloadout.grpdiv, 'hidden');
        var popup = L.popup({},featureLayer).setContent(container);
      }
      else if (feature.properties.claim_status === 'claimed') {
        L.DomUtil.removeClass(otherstxtcontainer, 'hidden');
        var popup = L.popup({},featureLayer).setContent(container);
      }
      else if (feature.properties.claim_status === 'quarantine') {
        L.DomUtil.removeClass(quarantinetxtcontainer, 'hidden');
        var popup = L.popup({},featureLayer).setContent(container);
      }
      else {
        L.DomUtil.removeClass(claimout.grpdiv, 'hidden');
        var popup = L.popup({},featureLayer).setContent(container);
      }
      featureLayer.bindPopup(popup)

    }
  });

    return layer;
  };

  function getMeshblockCallback(json) {
    if (mesh_layer) {map.removeLayer(mesh_layer)};
    mesh_layer = addGeoJsonProperties(json);
    mesh_layer.addTo(map);
    $('#load').addClass('hidden');
  };

  var instruct = L.control();
  instruct.onAdd = function(map) {
    this._div = L.DomUtil.create('div', 'instruct');
    this._div.innerHTML = "Zoom in further to load doorknockable areas.";
    this.update();
    return this._div;
  }

  instruct.update = function() {
    $(".instruct").toggleClass('hidden', map.getZoom() > 14)
  }

  instruct.addTo(map);

  function updateMap() {
    var lat_lng_bnd = map.getBounds();
    var lat_lng_centroid = map.getCenter();
    Cookies.set("lat",lat_lng_centroid.lat);
    Cookies.set("lng",lat_lng_centroid.lng);
    if (last_update_centroid) {
      var distance_moved = lat_lng_centroid.distanceTo(last_update_centroid);
    }
    else {var distance_moved = 201};
    var zoom = map.getZoom();
    if (zoom > 14) {
      var reload_dist = 1000/(zoom-14);
      }
      else {
        var reload_dist = 0;
      };
    Cookies.set("zoom",zoom);
    var swlat = lat_lng_bnd.getSouthWest().lat;
    var swlng = lat_lng_bnd.getSouthWest().lng;
    var nelat = lat_lng_bnd.getNorthEast().lat;
    var nelng = lat_lng_bnd.getNorthEast().lng;
    //Reload map if zoom not too high
    //Distance moved is not short
    //and
    //there is no last_update or the current map bounds are not within the last update's
    if(zoom > 14 && (!last_update_bounds || distance_moved > reload_dist) &&
      (!last_update_bounds || !last_update_bounds.contains(lat_lng_bnd))) {
      $('#load').removeClass('hidden');
      var url = '/meshblocks_bounds?swlat=' + swlat + '&swlng=' + swlng
      + '&nelat=' + nelat + '&nelng=' + nelng;
      $.getJSON(url, function(json) {
        getMeshblockCallback(json);
        last_update_bounds = map.getBounds();
        last_update_centroid = map.getCenter();
      })
      .fail( function() {
        $('#load').addClass('hidden');
      } );
    }
    instruct.update();
  };

  map.on('moveend', function() {
    updateMap();
  });

  var legend = L.control({position: 'bottomright'});

  legend.onAdd = function(map) {
    var div = L.DomUtil.create('div', 'legend');
    div.innerHTML = [
      '
My area
',
      '
Claimed
',
      '
Organised door knocking event
',
      '
Yet to be claimed!
'
    ].join('');
    return div;
  }

  legend.addTo(map);
  map.whenReady(updateMap);
}

var windowHeight = function(){
    if(window.innerHeight != undefined){
        return window.innerHeight;
    }
    else{
        var B= document.body, D= document.documentElement;
        return Math.max(D.clientHeight, B.clientHeight);
    }
}

$('#map').height(windowHeight() - $('.header').height());
$('#map').width("100%");

var stateColors =  {
  claimed_by_you: {"fillColor": "#9d5fa7", "color": "#111111",
    "weight": 1, "opacity": 0.65, "fillOpacity": 0.8}, //Purple
  unclaimed: {"fillColor": "#ffc746", "color": "#111111",
    "weight": 1, "opacity": 0.65, "fillOpacity": 0.2}, //Yellow
  claimed: {"fillColor": "#d5545a", "color": "#111111",
    "weight": 1, "opacity": 0.65, "fillOpacity": 0.8}, //Red
  quarantine: {"fillColor": "#6dbd4b", "color": "#111111",
    "weight": 1, "opacity": 0.65, "fillOpacity": 0.8}, //Green
};

function openfaq(){
  $("#dialog").dialog({minWidth: 800, width: 800, height: 500,
    beforeClose: function(e,ui){$("#dialog").addClass('hidden');}});
  $("#dialog").removeClass('hidden');
};

$('#faqlink').click(function() {
  openfaq();
});

var map = makeMap(stateColors);

openfaq();
<|endoftext|>