<commit_before>/*
  A.hasOne(B)                 => B.aId
  A.belongsTo(B)              => A.bId
  A.hasMany(B)                => B.aId
  A.hasMany(B) + B.hasMany(A) => AB.aId + AB.bId
*/

exports.SequelizeTable = function(Sequelize, sequelize, tableName, attributes, options) {
  options = options || {}
  options.classMethods = options.classMethods || {}
  options.instanceMethods = options.instanceMethods || {}

  var table = function(values) {
    var self = this,
        defaults = {}

    // read all default values ...
    Sequelize.Helper.Hash.forEach(table.attributes, function(options, key) {
      if(typeof options.default != 'undefined') defaults[key] = options.default
    })

    // and merge them into the passed one
    Sequelize.Helper.Hash.merge(defaults, values)

    // now iterate over the values and assign them the current object
    Sequelize.Helper.Hash.forEach(values, function(value, key) {
      if(attributes[key]) {
        if(attributes[key].type.indexOf(Sequelize.BOOLEAN) > -1)
          self[key] = ((value == 1) || (value == true)) ? true : false
        else if(attributes[key].type.indexOf(Sequelize.DATE) > -1)
          self[key] = (value instanceof Date) ? value : new Date(Date.parse(value))
        else
          self[key] = value
      }
    })

    this.id = null // specify id as null to declare this object as unsaved and as not present in the database
    this.table = table
  }

  // class methods
  var classMethods = {
    associations: [],
    attributes: attributes,
    tableName: tableName,

    prepareAssociations: function(callback) {
      table.associations.forEach(function(association) {
        switch(association.type) {
          case 'hasMany':
            if(association.backAssociationName) {
              // many to many relation
              var _attributes = {}
              _attributes[table.identifier] = Sequelize.INTEGER
              _attributes[association.table.identifier] = Sequelize.INTEGER
              sequelize.define(Sequelize.Helper.SQL.manyToManyTableName(association.name, association.backAssociationName), _attributes)
            } else {
              // one to many relation
              association.table.attributes[table.identifier] = {type: Sequelize.INTEGER}
            }
            break
          case 'hasOne':
            // e.g. assocTable.myTableId = Sequelize.INTEGER
            association.table.attributes[table.identifier] = {type: Sequelize.INTEGER}
            break
          case 'belongsTo':
            // e.g. table.dayId = Sequelize.INTEGER
            table.attributes[association.table.identifier] = {type :Sequelize.INTEGER}
            break
        }
      })
      if(callback) callback()
    },

    /* returns: table, error */
    sync: function(callback) {
      var fields = ["id INT NOT NULL auto_increment PRIMARY KEY"]
      Sequelize.Helper.Hash.forEach(table.attributes, function(options, name) {
        fields.push(name + " " + options.type)
      })

      sequelize.query(
        Sequelize.sqlQueryFor( 'create', { table: table.tableName, fields: fields.join(', ') } ),
        function(_, _, err) { if(callback) callback(table, err) }
      )
    },

    drop: function(callback) {
      sequelize.query(
        Sequelize.sqlQueryFor('drop', { table: table.tableName }),
        function(_, _, err) { if(callback) callback(table, err) }
      )
    },

    findAll: function(options, callback) {
      // use the first param as callback if it is no object (hash)
      var _callback = (typeof options == 'object') ? callback : options
      var queryOptions = (typeof options == 'object')
        ? Sequelize.Helper.Hash.merge(options, { table: table.tableName })
        : { table: table.tableName }

      sequelize.query(
        Sequelize.sqlQueryFor('select', queryOptions),
        function(result) {
          var objects = Sequelize.Helper.Array.map(result, function(r) { return table.sqlResultToObject(r) })
          if(_callback) _callback(objects)
        }
      )
    },

    find: function(conditions, callback) {
      sequelize.query(
        Sequelize.sqlQueryFor('select', {
          table: table.tableName,
          where: Sequelize.Helper.SQL.hashToWhereConditions(conditions, table.attributes),
          order: 'id DESC',
          limit: 1
        }), function(result) {
          var _result = result[0] ? table.sqlResultToObject(result[0]) : null
          if (callback) callback(_result)
        }
      )
    },

    sqlResultToObject: function(result) {
      if(typeof result == undefined) return null

      var object = new table(result)
      object.id = result.id
      return object
    },

    hasMany: function(assocName, _table, backAssocName) {
      var Factory = new require("./Factory").Factory(Sequelize, sequelize)

      table.associations.push({
        name: assocName,
        backAssociationName: backAssocName,
        table: _table,
        type: 'hasMany'
      })

      // don't check inside of method to increase performance
      if(backAssocName) {
        table.prototype[assocName] = Factory.createManyToManyGetter(table, _table, assocName, backAssocName)
        _table.prototype[backAssocName] = Factory.createManyToManyGetter(_table, table, backAssocName, assocName)
        table.prototype[Sequelize.Helper.SQL.addPrefix('set', assocName)] = Factory.createManyToManySetter(table, _table, assocName, backAssocName)
        _table.prototype[Sequelize.Helper.SQL.addPrefix('set', backAssocName)] = Factory.createManyToManySetter(_table, table, backAssocName, assocName)
      } else {
        table.prototype[assocName] = Factory.createOneToManyGetter(table, _table)
        table.prototype[Sequelize.Helper.SQL.addPrefix('set', assocName)] = Factory.createOneToManySetter(table)
      }

      return table
    },

    hasOne: function(assocName, _table) {
      table.associations.push({
        name: assocName,
        table: _table,
        type: 'hasOne'
      })

      table.prototype[assocName] = function(callback) {
        var whereConditions = {}
        whereConditions[table.identifier] = this.id
        _table.find(whereConditions, callback)
      }

      table.prototype[Sequelize.Helper.SQL.addPrefix('set', assocName)] = function(object, callback) {
        var self = this

        this[assocName](function(currentAssociation) {
          var attr = {}
          if(currentAssociation == null) {
            attr[table.identifier] = self.id
            object.updateAttributes(attr, callback)
          } else {
            if(object.id == currentAssociation.id) callback()
            else {
              attr[table.identifier] = null
              currentAssociation.updateAttributes(attr, function() {
                attr[table.identifier] = self.id
                object.updateAttributes(attr, callback)
              })
            }
          }
        })
      }

      return table
    },

    belongsTo: function(assocName, _table) {
      table.associations.push({
        name: assocName,
        table: _table,
        type: 'belongsTo'
      })

      table.prototype[assocName] = function(callback) {
        if((this[_table.identifier] == null)||(isNaN(this[_table.identifier])))
          callback([])
        else
          _table.find(this[_table.identifier], callback)
      }

      table.prototype[Sequelize.Helper.SQL.addPrefix('set', assocName)] = function(object, callback) {
        var attr = {}; attr[object.table.identifier] = object.id
        var self = this
        this.updateAttributes(attr, function() {
          self[assocName](callback)
        })
      }

      return table
    }
  }
  // don't put this into the hash!
  classMethods.identifier = Sequelize.Helper.SQL.asTableIdentifier(classMethods.tableName)

  // instance methods
  table.prototype = {
    get values() {
      var result = {}
      var self = this

      Sequelize.Helper.Hash.keys(table.attributes).forEach(function(attribute) {
        result[attribute] = (typeof self[attribute] == "undefined") ? null : self[attribute]
      })

      return result
    },

    get invalidFields() {
      var result  = [],
          self    = this

      Sequelize.Helper.Hash.forEach(table.attributes, function(options, attribute) {
        if(['createdAt', 'updatedAt'].indexOf(attribute) > -1) return

        var allowsNull = ((typeof options.allowNull == 'undefined') || (options.allowNull !== false))
        var hasDefault = (typeof options.default != 'undefined')

        if(!allowsNull && !hasDefault && (typeof self[attribute] == 'undefined'))
          result.push({ field: attribute, reason: 'The field does not allow NULL values and has no default!'})
      })

      return result
    },

    get isValid() {
      return this.invalidFields.length == 0
    },

    save: function(callback) {
      var query = null,
          self = this

      if(!this.isValid) {
        var errorText = "The object is not valid! Invalid fields: " + Sequelize.Helper.Array.map(this.invalidFields, function(fieldHash) { return fieldHash.field }).join(", ")
        throw new Error(errorText)
      }

      this.updatedAt = new Date()
      if(this.id == null) {
        this.createdAt = new Date()

        query = Sequelize.sqlQueryFor('insert', {
          table: table.tableName,
          fields: Sequelize.Helper.SQL.fieldsForInsertQuery(this),
          values: Sequelize.Helper.SQL.valuesForInsertQuery(this)
        })
      } else {
        query = Sequelize.sqlQueryFor('update', { table: table.tableName, values: Sequelize.Helper.SQL.valuesForUpdate(this), id: this.id })
      }

      sequelize.query(query, function(result, stats) {
        self.id = self.id || stats.insert_id
        if(callback) callback(self)
      })
    },

    updateAttributes: function(newValues, callback) {
      var self = this
      Sequelize.Helper.Hash.keys(table.attributes).forEach(function(attribute) {
        if(typeof newValues[attribute] != 'undefined')
          self[attribute] = newValues[attribute]
      })

      this.save(callback)
    },

    destroy: function(callback) {
      sequelize.query(
        Sequelize.sqlQueryFor('delete', { table: table.tableName, where: ['id', this.id].join("=") }),
        callback
      )
    }
  }

  // merge classMethods + passed classMethods
  Sequelize.Helper.Hash.merge(options.classMethods, classMethods)
  Sequelize.Helper.Hash.forEach(classMethods, function(method, methodName) {
    table[methodName] = method
  })

  // merge passed instanceMethods
  Sequelize.Helper.Hash.forEach(options.instanceMethods, function(method, methodName) {
    table.prototype[methodName] = method
  })

  return table
}<commit_msg>use assocName and backAssocName for key generation of n-to-m assocs<commit_after>/*
  A.hasOne(B)                 => B.aId
  A.belongsTo(B)              => A.bId
  A.hasMany(B)                => B.aId
  A.hasMany(B) + B.hasMany(A) => AB.aId + AB.bId
*/

exports.SequelizeTable = function(Sequelize, sequelize, tableName, attributes, options) {
  options = options || {}
  options.classMethods = options.classMethods || {}
  options.instanceMethods = options.instanceMethods || {}

  var table = function(values) {
    var self = this,
        defaults = {}

    // read all default values ...
    Sequelize.Helper.Hash.forEach(table.attributes, function(options, key) {
      if(typeof options.default != 'undefined') defaults[key] = options.default
    })

    // and merge them into the passed one
    Sequelize.Helper.Hash.merge(defaults, values)

    // now iterate over the values and assign them the current object
    Sequelize.Helper.Hash.forEach(values, function(value, key) {
      if(attributes[key]) {
        if(attributes[key].type.indexOf(Sequelize.BOOLEAN) > -1)
          self[key] = ((value == 1) || (value == true)) ? true : false
        else if(attributes[key].type.indexOf(Sequelize.DATE) > -1)
          self[key] = (value instanceof Date) ? value : new Date(Date.parse(value))
        else
          self[key] = value
      }
    })

    this.id = null // specify id as null to declare this object as unsaved and as not present in the database
    this.table = table
  }

  // class methods
  var classMethods = {
    associations: [],
    attributes: attributes,
    tableName: tableName,

    prepareAssociations: function(callback) {
      table.associations.forEach(function(association) {
        switch(association.type) {
          case 'hasMany':
            if(association.backAssociationName) {
              // many to many relation
              var _attributes = {}
              _attributes[Sequelize.Helper.SQL.asTableIdentifier(association.name)] = Sequelize.INTEGER
              _attributes[Sequelize.Helper.SQL.asTableIdentifier(association.backAssociationName)] = Sequelize.INTEGER
              sequelize.define(Sequelize.Helper.SQL.manyToManyTableName(association.name, association.backAssociationName), _attributes)
            } else {
              // one to many relation
              association.table.attributes[table.identifier] = {type: Sequelize.INTEGER}
            }
            break
          case 'hasOne':
            // e.g. assocTable.myTableId = Sequelize.INTEGER
            association.table.attributes[table.identifier] = {type: Sequelize.INTEGER}
            break
          case 'belongsTo':
            // e.g. table.dayId = Sequelize.INTEGER
            table.attributes[association.table.identifier] = {type :Sequelize.INTEGER}
            break
        }
      })
      if(callback) callback()
    },

    /* returns: table, error */
    sync: function(callback) {
      var fields = ["id INT NOT NULL auto_increment PRIMARY KEY"]
      Sequelize.Helper.Hash.forEach(table.attributes, function(options, name) {
        fields.push(name + " " + options.type)
      })

      sequelize.query(
        Sequelize.sqlQueryFor( 'create', { table: table.tableName, fields: fields.join(', ') } ),
        function(_, _, err) { if(callback) callback(table, err) }
      )
    },

    drop: function(callback) {
      sequelize.query(
        Sequelize.sqlQueryFor('drop', { table: table.tableName }),
        function(_, _, err) { if(callback) callback(table, err) }
      )
    },

    findAll: function(options, callback) {
      // use the first param as callback if it is no object (hash)
      var _callback = (typeof options == 'object') ? callback : options
      var queryOptions = (typeof options == 'object')
        ? Sequelize.Helper.Hash.merge(options, { table: table.tableName })
        : { table: table.tableName }

      sequelize.query(
        Sequelize.sqlQueryFor('select', queryOptions),
        function(result) {
          var objects = Sequelize.Helper.Array.map(result, function(r) { return table.sqlResultToObject(r) })
          if(_callback) _callback(objects)
        }
      )
    },

    find: function(conditions, callback) {
      sequelize.query(
        Sequelize.sqlQueryFor('select', {
          table: table.tableName,
          where: Sequelize.Helper.SQL.hashToWhereConditions(conditions, table.attributes),
          order: 'id DESC',
          limit: 1
        }), function(result) {
          var _result = result[0] ? table.sqlResultToObject(result[0]) : null
          if (callback) callback(_result)
        }
      )
    },

    sqlResultToObject: function(result) {
      if(typeof result == undefined) return null

      var object = new table(result)
      object.id = result.id
      return object
    },

    hasMany: function(assocName, _table, backAssocName) {
      var Factory = new require("./Factory").Factory(Sequelize, sequelize)

      table.associations.push({
        name: assocName,
        backAssociationName: backAssocName,
        table: _table,
        type: 'hasMany'
      })

      // don't check inside of method to increase performance
      if(backAssocName) {
        table.prototype[assocName] = Factory.createManyToManyGetter(table, _table, assocName, backAssocName)
        _table.prototype[backAssocName] = Factory.createManyToManyGetter(_table, table, backAssocName, assocName)
        table.prototype[Sequelize.Helper.SQL.addPrefix('set', assocName)] = Factory.createManyToManySetter(table, _table, assocName, backAssocName)
        _table.prototype[Sequelize.Helper.SQL.addPrefix('set', backAssocName)] = Factory.createManyToManySetter(_table, table, backAssocName, assocName)
      } else {
        table.prototype[assocName] = Factory.createOneToManyGetter(table, _table)
        table.prototype[Sequelize.Helper.SQL.addPrefix('set', assocName)] = Factory.createOneToManySetter(table)
      }

      return table
    },

    hasOne: function(assocName, _table) {
      table.associations.push({
        name: assocName,
        table: _table,
        type: 'hasOne'
      })

      table.prototype[assocName] = function(callback) {
        var whereConditions = {}
        whereConditions[table.identifier] = this.id
        _table.find(whereConditions, callback)
      }

      table.prototype[Sequelize.Helper.SQL.addPrefix('set', assocName)] = function(object, callback) {
        var self = this

        this[assocName](function(currentAssociation) {
          var attr = {}
          if(currentAssociation == null) {
            attr[table.identifier] = self.id
            object.updateAttributes(attr, callback)
          } else {
            if(object.id == currentAssociation.id) callback()
            else {
              attr[table.identifier] = null
              currentAssociation.updateAttributes(attr, function() {
                attr[table.identifier] = self.id
                object.updateAttributes(attr, callback)
              })
            }
          }
        })
      }

      return table
    },

    belongsTo: function(assocName, _table) {
      table.associations.push({
        name: assocName,
        table: _table,
        type: 'belongsTo'
      })

      table.prototype[assocName] = function(callback) {
        if((this[_table.identifier] == null)||(isNaN(this[_table.identifier])))
          callback([])
        else
          _table.find(this[_table.identifier], callback)
      }

      table.prototype[Sequelize.Helper.SQL.addPrefix('set', assocName)] = function(object, callback) {
        var attr = {}; attr[object.table.identifier] = object.id
        var self = this
        this.updateAttributes(attr, function() {
          self[assocName](callback)
        })
      }

      return table
    }
  }
  // don't put this into the hash!
  classMethods.identifier = Sequelize.Helper.SQL.asTableIdentifier(classMethods.tableName)

  // instance methods
  table.prototype = {
    get values() {
      var result = {}
      var self = this

      Sequelize.Helper.Hash.keys(table.attributes).forEach(function(attribute) {
        result[attribute] = (typeof self[attribute] == "undefined") ? null : self[attribute]
      })

      return result
    },

    get invalidFields() {
      var result  = [],
          self    = this

      Sequelize.Helper.Hash.forEach(table.attributes, function(options, attribute) {
        if(['createdAt', 'updatedAt'].indexOf(attribute) > -1) return

        var allowsNull = ((typeof options.allowNull == 'undefined') || (options.allowNull !== false))
        var hasDefault = (typeof options.default != 'undefined')

        if(!allowsNull && !hasDefault && (typeof self[attribute] == 'undefined'))
          result.push({ field: attribute, reason: 'The field does not allow NULL values and has no default!'})
      })

      return result
    },

    get isValid() {
      return this.invalidFields.length == 0
    },

    save: function(callback) {
      var query = null,
          self = this

      if(!this.isValid) {
        var errorText = "The object is not valid! Invalid fields: " + Sequelize.Helper.Array.map(this.invalidFields, function(fieldHash) { return fieldHash.field }).join(", ")
        throw new Error(errorText)
      }

      this.updatedAt = new Date()
      if(this.id == null) {
        this.createdAt = new Date()

        query = Sequelize.sqlQueryFor('insert', {
          table: table.tableName,
          fields: Sequelize.Helper.SQL.fieldsForInsertQuery(this),
          values: Sequelize.Helper.SQL.valuesForInsertQuery(this)
        })
      } else {
        query = Sequelize.sqlQueryFor('update', { table: table.tableName, values: Sequelize.Helper.SQL.valuesForUpdate(this), id: this.id })
      }

      sequelize.query(query, function(result, stats) {
        self.id = self.id || stats.insert_id
        if(callback) callback(self)
      })
    },

    updateAttributes: function(newValues, callback) {
      var self = this
      Sequelize.Helper.Hash.keys(table.attributes).forEach(function(attribute) {
        if(typeof newValues[attribute] != 'undefined')
          self[attribute] = newValues[attribute]
      })

      this.save(callback)
    },

    destroy: function(callback) {
      sequelize.query(
        Sequelize.sqlQueryFor('delete', { table: table.tableName, where: ['id', this.id].join("=") }),
        callback
      )
    }
  }

  // merge classMethods + passed classMethods
  Sequelize.Helper.Hash.merge(options.classMethods, classMethods)
  Sequelize.Helper.Hash.forEach(classMethods, function(method, methodName) {
    table[methodName] = method
  })

  // merge passed instanceMethods
  Sequelize.Helper.Hash.forEach(options.instanceMethods, function(method, methodName) {
    table.prototype[methodName] = method
  })

  return table
}<|endoftext|>


<commit_before>'use strict';
var addon = require('./../erizoAPI/build/Release/addon');
var licodeConfig = require('./../licode_config');
var mediaConfig = require('./../rtp_media_config');
var logger = require('./logger').logger;
var log = logger.getLogger('NativeClient');

GLOBAL.config = licodeConfig || {};
GLOBAL.mediaConfig = mediaConfig || {};

exports.ErizoNativeConnection = function (spec){
    var that = {},
    wrtc,
    initWebRtcConnection,
    syntheticInput,
    externalInput,
    oneToMany,
    externalOutput;

    var threadPool = new addon.ThreadPool(1);
    threadPool.start();

    var CONN_INITIAL = 101,
        // CONN_STARTED = 102,
        CONN_GATHERED = 103,
        CONN_READY = 104,
        // CONN_FINISHED = 105,
        CONN_CANDIDATE = 201,
        CONN_SDP = 202,
        CONN_FAILED = 500;

    var generatePLIs = function(){
        externalOutput.interval = setInterval ( function(){
            wrtc.generatePLIPacket();
        },1000);
    };

    log.info('NativeConnection constructor', spec);
    initWebRtcConnection = function (callback) {
        wrtc.init( function (newStatus, mess){
            log.info('webrtc Addon status ', newStatus);
            switch(newStatus) {
                case CONN_INITIAL:
                    if (spec.video && spec.video.file && !externalInput){
                        that.prepareVideo(spec.video.file);
                    } else if (spec.video && spec.video.recording && !externalOutput){
                        that.prepareRecording(spec.video.recording);
                    } else if (spec.video && spec.video.synthetic && !syntheticInput) {
                      that.prepareSynthetic(spec.video.synthetic);
                    } else {
                      that.prepareNull();
                    }
                    callback('callback', {type: 'started'});
                    break;

                case CONN_SDP:
                case CONN_GATHERED:
                    setTimeout(function(){
                        callback('callback', {type: 'offer', sdp: mess});
                    }, 100);
                    break;

                case CONN_CANDIDATE:
                    callback('callback', {type:'candidate', sdp: mess});
                    break;

                case CONN_FAILED:
                    log.warn('Connection failed the ICE process');
                    //   callback('callback', {type: 'failed', sdp: mess});
                    break;

                case CONN_READY:
                    log.info('Connection ready');
                    if (externalInput !== undefined){
                        log.info('Will start External Input');
                        externalInput.init();
                    }
                    if (syntheticInput !== undefined){
                        log.info('Will start Synthetic Input');
                        syntheticInput.init();
                    }
                    if (externalOutput !== undefined){
                        log.info('Will start External Output');
                        externalOutput.init();
                        generatePLIs();
                    }
                    break;
            }
        });
    };

    wrtc = new addon.WebRtcConnection(threadPool, 'spine',
                                      GLOBAL.config.erizo.stunserver,
                                      GLOBAL.config.erizo.stunport,
                                      GLOBAL.config.erizo.minport,
                                      GLOBAL.config.erizo.maxport,
                                      false,
                                      JSON.stringify(GLOBAL.mediaConfig),
                                      GLOBAL.config.erizo.turnserver,
                                      GLOBAL.config.erizo.turnport,
                                      GLOBAL.config.erizo.turnusername,
                                      GLOBAL.config.erizo.turnpass);

    that.createOffer = function () {

    };

    that.prepareNull = function () {
        log.info('Preparing null output');
        oneToMany = new addon.OneToManyProcessor();
        wrtc.setVideoReceiver(oneToMany);
        wrtc.setAudioReceiver(oneToMany);
        oneToMany.setPublisher(wrtc);
    };

    that.prepareVideo = function (url) {
        log.info('Preparing video', url);
        externalInput = new addon.ExternalInput(url);
        externalInput.setAudioReceiver(wrtc);
        externalInput.setVideoReceiver(wrtc);
    };

    that.prepareSynthetic = function (config) {
        log.info('Preparing synthetic video', config);
        syntheticInput = new addon.SyntheticInput(threadPool,
                                                  config.audioBitrate,
                                                  config.minVideoBitrate,
                                                  config.maxVideoBitrate);
        syntheticInput.setAudioReceiver(wrtc);
        syntheticInput.setVideoReceiver(wrtc);
        syntheticInput.setFeedbackSource(wrtc);
    };

    that.prepareRecording = function (url) {
        log.info('Preparing Recording', url);
        externalOutput = new addon.ExternalOutput(url);
        wrtc.setVideoReceiver(externalOutput);
        wrtc.setAudioReceiver(externalOutput);
    };

    that.setRemoteDescription = function () {
        log.info('RemoteDescription');
    };

    that.processSignallingMessage = function(msg) {
        log.info('Receiving message', msg.type);
        if (msg.type === 'started'){
            initWebRtcConnection(function(mess, info){
                log.info('Message from wrtc', info.type);
                if (info.type === 'offer') {
                     spec.callback({type:info.type, sdp: info.sdp});
                }
            }, {});

            var audioEnabled = true;
            var videoEnabled = true;
            var bundle = true;
            wrtc.createOffer(audioEnabled, videoEnabled, bundle);
        } else if (msg.type === 'answer'){
            setTimeout(function(){
                log.info('Passing delayed answer');
                wrtc.setRemoteSdp(msg.sdp);
                that.onaddstream({stream:{active:true}});
            }, 10);
        }
    };

    that.close = function() {
        if (externalOutput){
            externalOutput.close();
            if(externalOutput.interval)
                clearInterval(externalOutput.interval);
        }
        if (externalInput!==undefined){
            externalInput.close();
        }
        if (syntheticInput!==undefined){
            syntheticInput.close();
        }
        wrtc.close();
    };

    return that;
};
<commit_msg>Fix spine with new erizo api and nicer (#921)<commit_after>'use strict';
var addon = require('./../erizoAPI/build/Release/addon');
var licodeConfig = require('./../licode_config');
var mediaConfig = require('./../rtp_media_config');
var logger = require('./logger').logger;
var log = logger.getLogger('NativeClient');

GLOBAL.config = licodeConfig || {};
GLOBAL.mediaConfig = mediaConfig || {};

exports.ErizoNativeConnection = function (spec){
    var that = {},
    wrtc,
    initWebRtcConnection,
    syntheticInput,
    externalInput,
    oneToMany,
    externalOutput;

    var threadPool = new addon.ThreadPool(1);
    threadPool.start();

    var ioThreadPool = new addon.IOThreadPool(1);
    if (GLOBAL.config.erizo.useNicer) {
      ioThreadPool.start();
    }

    var CONN_INITIAL = 101,
        // CONN_STARTED = 102,
        CONN_GATHERED = 103,
        CONN_READY = 104,
        // CONN_FINISHED = 105,
        CONN_CANDIDATE = 201,
        CONN_SDP = 202,
        CONN_FAILED = 500;

    var generatePLIs = function(){
        externalOutput.interval = setInterval ( function(){
            wrtc.generatePLIPacket();
        },1000);
    };

    log.info('NativeConnection constructor', spec);
    initWebRtcConnection = function (callback) {
        wrtc.init( function (newStatus, mess){
            log.info('webrtc Addon status ', newStatus);
            switch(newStatus) {
                case CONN_INITIAL:
                    if (spec.video && spec.video.file && !externalInput){
                        that.prepareVideo(spec.video.file);
                    } else if (spec.video && spec.video.recording && !externalOutput){
                        that.prepareRecording(spec.video.recording);
                    } else if (spec.video && spec.video.synthetic && !syntheticInput) {
                      that.prepareSynthetic(spec.video.synthetic);
                    } else {
                      that.prepareNull();
                    }
                    callback('callback', {type: 'started'});
                    break;

                case CONN_SDP:
                case CONN_GATHERED:
                    setTimeout(function(){
                        callback('callback', {type: 'offer', sdp: mess});
                    }, 100);
                    break;

                case CONN_CANDIDATE:
                    callback('callback', {type:'candidate', sdp: mess});
                    break;

                case CONN_FAILED:
                    log.warn('Connection failed the ICE process');
                    //   callback('callback', {type: 'failed', sdp: mess});
                    break;

                case CONN_READY:
                    log.info('Connection ready');
                    if (externalInput !== undefined){
                        log.info('Will start External Input');
                        externalInput.init();
                    }
                    if (syntheticInput !== undefined){
                        log.info('Will start Synthetic Input');
                        syntheticInput.init();
                    }
                    if (externalOutput !== undefined){
                        log.info('Will start External Output');
                        externalOutput.init();
                        generatePLIs();
                    }
                    break;
            }
        });
    };

    wrtc = new addon.WebRtcConnection(threadPool, ioThreadPool, 'spine',
                                      GLOBAL.config.erizo.stunserver,
                                      GLOBAL.config.erizo.stunport,
                                      GLOBAL.config.erizo.minport,
                                      GLOBAL.config.erizo.maxport,
                                      false,
                                      JSON.stringify(GLOBAL.mediaConfig),
                                      GLOBAL.config.erizo.useNicer,
                                      GLOBAL.config.erizo.turnserver,
                                      GLOBAL.config.erizo.turnport,
                                      GLOBAL.config.erizo.turnusername,
                                      GLOBAL.config.erizo.turnpass,
                                      GLOBAL.config.erizo.networkinterface);

    that.createOffer = function () {

    };

    that.prepareNull = function () {
        log.info('Preparing null output');
        oneToMany = new addon.OneToManyProcessor();
        wrtc.setVideoReceiver(oneToMany);
        wrtc.setAudioReceiver(oneToMany);
        oneToMany.setPublisher(wrtc);
    };

    that.prepareVideo = function (url) {
        log.info('Preparing video', url);
        externalInput = new addon.ExternalInput(url);
        externalInput.setAudioReceiver(wrtc);
        externalInput.setVideoReceiver(wrtc);
    };

    that.prepareSynthetic = function (config) {
        log.info('Preparing synthetic video', config);
        syntheticInput = new addon.SyntheticInput(threadPool,
                                                  config.audioBitrate,
                                                  config.minVideoBitrate,
                                                  config.maxVideoBitrate);
        syntheticInput.setAudioReceiver(wrtc);
        syntheticInput.setVideoReceiver(wrtc);
        syntheticInput.setFeedbackSource(wrtc);
    };

    that.prepareRecording = function (url) {
        log.info('Preparing Recording', url);
        externalOutput = new addon.ExternalOutput(url);
        wrtc.setVideoReceiver(externalOutput);
        wrtc.setAudioReceiver(externalOutput);
    };

    that.setRemoteDescription = function () {
        log.info('RemoteDescription');
    };

    that.processSignallingMessage = function(msg) {
        log.info('Receiving message', msg.type);
        if (msg.type === 'started'){
            initWebRtcConnection(function(mess, info){
                log.info('Message from wrtc', info.type);
                if (info.type === 'offer') {
                     spec.callback({type:info.type, sdp: info.sdp});
                }
            }, {});

            var audioEnabled = true;
            var videoEnabled = true;
            var bundle = true;
            wrtc.createOffer(audioEnabled, videoEnabled, bundle);
        } else if (msg.type === 'answer'){
            setTimeout(function(){
                log.info('Passing delayed answer');
                wrtc.setRemoteSdp(msg.sdp);
                that.onaddstream({stream:{active:true}});
            }, 10);
        }
    };

    that.close = function() {
        if (externalOutput){
            externalOutput.close();
            if(externalOutput.interval)
                clearInterval(externalOutput.interval);
        }
        if (externalInput!==undefined){
            externalInput.close();
        }
        if (syntheticInput!==undefined){
            syntheticInput.close();
        }
        wrtc.close();
    };

    return that;
};
<|endoftext|>


<commit_before>let debug = require('debug')('taskcluster-lib-monitor');
let events = require('events');
let _ = require('lodash');
let Promise = require('bluebird');
let AWS = require('aws-sdk');

// These numbers come from the Firehose docs.
const MAX_RECORD_SIZE = 1000 * 1000;

// This is just a number I picked.
const MAX_RETRIES = 5;

class FirehoseLog extends events.EventEmitter {

  constructor({aws, logName, reportAuditLogErrors, resourceInterval, crashTimeout}) {
    super();
    if (!logName || !aws) {
      throw new Error('Must specify both aws credentials and stream name to have structured logs!');
    }

    this._flushInterval = resourceInterval * 1000;
    this._crashTimeout = crashTimeout;
    this._firehose = new AWS.Firehose(aws);
    this._logName = logName;
    this._records = [];
    this._flushTimer = null;
    this._flushTimer = setTimeout(this.flush.bind(this), this._flushInterval);
    this._reportErrors = reportAuditLogErrors;
  }

  async setup() {
    let stat = await this._firehose.describeDeliveryStream({
      DeliveryStreamName: this._logName,
    }).promise();
    if (stat.DeliveryStreamDescription.DeliveryStreamStatus !== 'ACTIVE') {
      throw new Error(`Delivery Stream ${this._logName} is not yet active!`);
    }
  }

  log(record) {
    let line = JSON.stringify(record) + '\n';
    let length = Buffer.byteLength(line, 'utf-8');

    // Each line can have up to 1MB
    if (length > MAX_RECORD_SIZE) {
      let msg = `Tried to log too-long line! (${length} bytes > 1MB)`;
      console.error(msg);
      if (this._reportErrors) {
        this.emit('error', new Error(msg));
      }
      return;
    }
    this._records.push({line, retries: 0, length});
    this._scheduleFlush();
  }

  _scheduleFlush() {
    if (!this._flushTimer) {
      this._flushTimer = setTimeout(this.flush.bind(this), this._flushInterval);
    }
  }

  async flush() {
    if (this._flushTimer) {
      clearTimeout(this._flushTimer);
      this._flushTimer = null;
    }

    if (!this._records.length) {
      return;
    }

    let chunks = [[]];
    let c = 0;

    // First break up the records into chunks that kinesis will accept
    this._records.forEach(rec => {
      if (chunks[c].reduce((a, r) => a + r.length, 0) + rec.length  > MAX_RECORD_SIZE) {
        chunks[++c] = [];
      }
      chunks[c].push(rec);
    });
    this._records = [];

    // Now submit the chunks
    await Promise.map(chunks, async records => {
      let res;
      try {
        res = await this._firehose.putRecord({
          DeliveryStreamName: this._logName,
          Record: {Data: records.map(line => line.line).join('')},
        }).promise();
      } catch (err) {
        if (!err.statusCode) {
          throw err;
        } else if (!err.retryable) {
          // We screwed up somehow and we should not attempt to resubmit these lines
          if (this._reportErrors) {
            this.emit('error', err);
          }
          return;
        }
        // If this was a server-side error, we'll queue these records back up
        // and try to submit them again
        records.forEach(record => {
          debug('Failed to write record ' + JSON.stringify(record) + '. Reason: ' + err.ErrorMessage);
          if (record.retries > MAX_RETRIES) {
            let msg = `Record failed during submission more than ${MAX_RETRIES} times. Rejecting.`;
            console.error(msg);
            if (this._reportErrors) {
              this.emit('error', new Error(msg));
            }
            return;
          }
          record.retries += 1;
          this._records.push(record);
        });
      }

      if (this._records.length) {
        this._scheduleFlush();
      }
    });
  }
}

class NoopLog extends events.EventEmitter {

  async setup() {
  }

  log(record) {
    console.log(record);
  }

  async flush() {
  }
}

module.exports = {NoopLog, FirehoseLog};
<commit_msg>Streamline and log better the chunking of audit logs<commit_after>let debug = require('debug')('taskcluster-lib-monitor');
let events = require('events');
let _ = require('lodash');
let Promise = require('bluebird');
let AWS = require('aws-sdk');

// These numbers come from the Firehose docs.
const MAX_RECORD_SIZE = 1000 * 1000;

// This is just a number I picked.
const MAX_RETRIES = 5;

class FirehoseLog extends events.EventEmitter {

  constructor({aws, logName, reportAuditLogErrors, resourceInterval, crashTimeout}) {
    super();
    if (!logName || !aws) {
      throw new Error('Must specify both aws credentials and stream name to have structured logs!');
    }

    this._flushInterval = resourceInterval * 1000;
    this._crashTimeout = crashTimeout;
    this._firehose = new AWS.Firehose(aws);
    this._logName = logName;
    this._records = [];
    this._flushTimer = setTimeout(this.flush.bind(this), this._flushInterval);
    this._reportErrors = reportAuditLogErrors;
  }

  async setup() {
    let stat = await this._firehose.describeDeliveryStream({
      DeliveryStreamName: this._logName,
    }).promise();
    if (stat.DeliveryStreamDescription.DeliveryStreamStatus !== 'ACTIVE') {
      throw new Error(`Delivery Stream ${this._logName} is not yet active!`);
    }
  }

  log(record) {
    let line = JSON.stringify(record) + '\n';
    let size = Buffer.byteLength(line, 'utf-8');

    // Each line can have up to 1MB
    if (size > MAX_RECORD_SIZE) {
      let msg = `Tried to log too-long line! (${size} bytes > 1MB)`;
      console.error(msg);
      if (this._reportErrors) {
        this.emit('error', new Error(msg));
      }
      return;
    }
    this._records.push({line, retries: 0, size});
    this._scheduleFlush();
  }

  _scheduleFlush() {
    if (!this._flushTimer) {
      this._flushTimer = setTimeout(this.flush.bind(this), this._flushInterval);
    }
  }

  async flush() {
    if (this._flushTimer) {
      clearTimeout(this._flushTimer);
      this._flushTimer = null;
    }

    if (!this._records.length) {
      return;
    }

    let chunks = [{chunkSize: 0, records: []}];
    let c = 0;
    let totalSize = 0;

    // First break up the records into chunks that kinesis will accept
    this._records.forEach(rec => {
      if (chunks[c].chunkSize + rec.size > MAX_RECORD_SIZE) {
        chunks[++c] = {chunkSize: 0, records: []};
      }
      totalSize += rec.size;
      chunks[c].chunkSize += rec.size;
      chunks[c].records.push(rec);
    });
    debug(`Audit log contained ${this._records.length} records with size of ${totalSize} bytes in ${c+1} chunks`);
    this._records = [];

    // Now submit the chunks
    await Promise.map(chunks, async chunk => {
      let {records} = chunk;
      let res;
      try {
        res = await this._firehose.putRecord({
          DeliveryStreamName: this._logName,
          Record: {Data: records.map(line => line.line).join('')},
        }).promise();
      } catch (err) {
        if (!err.statusCode) {
          throw err;
        } else if (!err.retryable) {
          // We screwed up somehow and we should not attempt to resubmit these lines
          if (this._reportErrors) {
            this.emit('error', err);
          }
          return;
        }
        // If this was a server-side error, we'll queue these records back up
        // and try to submit them again
        records.forEach(record => {
          debug('Failed to write record ' + JSON.stringify(record) + '. Reason: ' + err.ErrorMessage);
          if (record.retries > MAX_RETRIES) {
            let msg = `Record failed during submission more than ${MAX_RETRIES} times. Rejecting.`;
            console.error(msg);
            if (this._reportErrors) {
              this.emit('error', new Error(msg));
            }
            return;
          }
          record.retries += 1;
          this._records.push(record);
        });
      }

      if (this._records.length) {
        this._scheduleFlush();
      }
    });
  }
}

class NoopLog extends events.EventEmitter {

  async setup() {
  }

  log(record) {
    console.log(record);
  }

  async flush() {
  }
}

module.exports = {NoopLog, FirehoseLog};
<|endoftext|>


<commit_before>'use strict'

// TODO: Require JSON Schema here
const Ajv = require('ajv');
const schema = require('./questionnaire.schema.json');
const ajv = Ajv(); // options can be passed, e.g. {allErrors: true}
const validate = ajv.compile(schema);
//var valid = validate(data);
//if (!valid) console.log(validate.errors);

module.exports = (dbUrl,usedDatabase) => {

  const bl = require('./bl.js')(dbUrl,usedDatabase);

  function createUuid(){
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    });
  }

  /*
  Check if given UUID is valid UUID
  */
  function TestUuid(uuid){
    let pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    let isValid =  pattern.test(uuid);
    return isValid;
  }

  async function getQuestionnaires() {
    let questionnaires = await bl.getQuestionnaires();
    let validQuestionnaires = new Array();
    for(let i = 0;i<questionnaires.length;i++){
      let valid = validate(questionnaires[i]);
      if (!valid){
        console.log("INVALID QUESTIONNAIRE in database, uuid: "+questionnaires[i].uuid);
        console.log(validate.errors);
  	  } else {
  	    validQuestionnaires.push(questionnaires[i]);
  	  }
    }
    return validQuestionnaires;
  }

  async function putQuestionnaire(payload) {
  	payload = bl.generateUuidsAndTimestamps(payload)

  	let valid = validate(payload);
  	let responseFromBl = false;
  	if (valid){
  	  payload["enabled"] = false;
  	  responseFromBl = await bl.putQuestionnaire(payload);
  	  if(!responseFromBl){
  	    console.log("PATH ALREADY EXISTS in putQuestionnaire");
  	  }
  	} else {
  	  console.log("INVALID QUESTIONNAIRE in putQuestionnaire");
  	  console.log(validate.errors);
  	}
    return responseFromBl;
  }

  async function deleteQuestionnaire(uuid){
  	let isValidUuid = TestUuid(uuid);
  	let responseFromBl = false;
  	if(isValidUuid){
      responseFromBl = await bl.deleteQuestionnaire(uuid);
    } else {
      console.log("INVALID UUID in deleteQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function getQuestionnaire(uuid){
  	let isValidUuid = TestUuid(uuid);
  	let responseFromBl = false;
  	if(isValidUuid){
      responseFromBl = await bl.getQuestionnaire(uuid);
      if(responseFromBl == uuid){
        console.log("NOT FOUND in getQuestionnaire, uuid: "+uuid);
        responseFromBl = false;
      } else {
        let valid = validate(responseFromBl);
        if(!valid){
          console.log("INVALID QUESTIONNAIRE in database, uuid: "+uuid);
          console.log(validate.errors);
          responseFromBl = false;
        }
      }
    } else {
      console.log("INVALID UUID in getQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function deployQuestionnaire(uuid){
    let isValidUuid = TestUuid(uuid);
    let responseFromBl = false;
    if(isValidUuid){
      responseFromBl = await bl.deployQuestionnaire(uuid);
      if(responseFromBl == uuid){
        console.log("NOT FOUND in deployQuestionnaire, uuid: "+uuid);
        responseFromBl = false;
      } else {
        responseFromBl = true;
      }
    } else {
      console.log("INVALID UUID in deployQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function closeQuestionnaire(uuid){
    let isValidUuid = TestUuid(uuid);
    let responseFromBl = false;
    if(isValidUuid){
      responseFromBl = await bl.closeQuestionnaire(uuid);
      if(responseFromBl == uuid){
        console.log("NOT FOUND in closeQuestionnaire, uuid: "+uuid);
        responseFromBl = false;
      } else {
        responseFromBl = true;
      }
    } else {
      console.log("INVALID UUID in closeQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function updateQuestionnaire(uuid,payload){
    let isValidUuid = TestUuid(uuid);
    if(payload.uuid !== uuid){
      isValidUuid = false;
    }
    let responseFromBl = false;
    if(isValidUuid){
      bl.generateUuidsAndTimestamps(payload); // Generate UUIDs for new elements
      let valid = validate(payload);
      if(!valid){
        console.log("INVALID QUESTIONNAIRE in putQuestionnaire");
        console.log(validate.errors);
      } else {
        responseFromBl = await bl.updateQuestionnaire(uuid,payload);
        if(responseFromBl == uuid){
          console.log("NOT FOUND in updateQuestionnaire, uuid: "+uuid);
          responseFromBl = false;
        }
      }
    } else {
      console.log("INVALID UUID in updateQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function getQuestions(lang,path){
    let responseFromBl = false;
    responseFromBl = await bl.getQuestions(lang,path);
    return responseFromBl;
  }

  return {
    getQuestionnaires: getQuestionnaires,
    putQuestionnaire: putQuestionnaire,
    deleteQuestionnaire: deleteQuestionnaire,
    getQuestionnaire: getQuestionnaire,
    deployQuestionnaire: deployQuestionnaire,
    closeQuestionnaire: closeQuestionnaire,
    updateQuestionnaire: updateQuestionnaire,
    getQuestions: getQuestions
  };
}
<commit_msg>Fix typo in generateUuids.. function<commit_after>'use strict'

// TODO: Require JSON Schema here
const Ajv = require('ajv');
const schema = require('./questionnaire.schema.json');
const ajv = Ajv(); // options can be passed, e.g. {allErrors: true}
const validate = ajv.compile(schema);
//var valid = validate(data);
//if (!valid) console.log(validate.errors);

module.exports = (dbUrl,usedDatabase) => {

  const bl = require('./bl.js')(dbUrl,usedDatabase);

  function createUuid(){
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    });
  }

  /*
  Check if given UUID is valid UUID
  */
  function TestUuid(uuid){
    let pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    let isValid =  pattern.test(uuid);
    return isValid;
  }

  async function getQuestionnaires() {
    let questionnaires = await bl.getQuestionnaires();
    let validQuestionnaires = new Array();
    for(let i = 0;i<questionnaires.length;i++){
      let valid = validate(questionnaires[i]);
      if (!valid){
        console.log("INVALID QUESTIONNAIRE in database, uuid: "+questionnaires[i].uuid);
        console.log(validate.errors);
  	  } else {
  	    validQuestionnaires.push(questionnaires[i]);
  	  }
    }
    return validQuestionnaires;
  }

  async function putQuestionnaire(payload) {
  	payload = generateUuidsAndTimestamps(payload)

  	let valid = validate(payload);
  	let responseFromBl = false;
  	if (valid){
  	  payload["enabled"] = false;
  	  responseFromBl = await bl.putQuestionnaire(payload);
  	  if(!responseFromBl){
  	    console.log("PATH ALREADY EXISTS in putQuestionnaire");
  	  }
  	} else {
  	  console.log("INVALID QUESTIONNAIRE in putQuestionnaire");
  	  console.log(validate.errors);
  	}
    return responseFromBl;
  }

  async function deleteQuestionnaire(uuid){
  	let isValidUuid = TestUuid(uuid);
  	let responseFromBl = false;
  	if(isValidUuid){
      responseFromBl = await bl.deleteQuestionnaire(uuid);
    } else {
      console.log("INVALID UUID in deleteQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function getQuestionnaire(uuid){
  	let isValidUuid = TestUuid(uuid);
  	let responseFromBl = false;
  	if(isValidUuid){
      responseFromBl = await bl.getQuestionnaire(uuid);
      if(responseFromBl == uuid){
        console.log("NOT FOUND in getQuestionnaire, uuid: "+uuid);
        responseFromBl = false;
      } else {
        let valid = validate(responseFromBl);
        if(!valid){
          console.log("INVALID QUESTIONNAIRE in database, uuid: "+uuid);
          console.log(validate.errors);
          responseFromBl = false;
        }
      }
    } else {
      console.log("INVALID UUID in getQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function deployQuestionnaire(uuid){
    let isValidUuid = TestUuid(uuid);
    let responseFromBl = false;
    if(isValidUuid){
      responseFromBl = await bl.deployQuestionnaire(uuid);
      if(responseFromBl == uuid){
        console.log("NOT FOUND in deployQuestionnaire, uuid: "+uuid);
        responseFromBl = false;
      } else {
        responseFromBl = true;
      }
    } else {
      console.log("INVALID UUID in deployQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function closeQuestionnaire(uuid){
    let isValidUuid = TestUuid(uuid);
    let responseFromBl = false;
    if(isValidUuid){
      responseFromBl = await bl.closeQuestionnaire(uuid);
      if(responseFromBl == uuid){
        console.log("NOT FOUND in closeQuestionnaire, uuid: "+uuid);
        responseFromBl = false;
      } else {
        responseFromBl = true;
      }
    } else {
      console.log("INVALID UUID in closeQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function updateQuestionnaire(uuid,payload){
    let isValidUuid = TestUuid(uuid);
    if(payload.uuid !== uuid){
      isValidUuid = false;
    }
    let responseFromBl = false;
    if(isValidUuid){
      generateUuidsAndTimestamps(payload); // Generate UUIDs for new elements
      let valid = validate(payload);
      if(!valid){
        console.log("INVALID QUESTIONNAIRE in putQuestionnaire");
        console.log(validate.errors);
      } else {
        responseFromBl = await bl.updateQuestionnaire(uuid,payload);
        if(responseFromBl == uuid){
          console.log("NOT FOUND in updateQuestionnaire, uuid: "+uuid);
          responseFromBl = false;
        }
      }
    } else {
      console.log("INVALID UUID in updateQuestionnaire, requested uuid: "+uuid);
    }
    return responseFromBl;
  }

  async function getQuestions(lang,path){
    let responseFromBl = false;
    responseFromBl = await bl.getQuestions(lang,path);
    return responseFromBl;
  }

  return {
    getQuestionnaires: getQuestionnaires,
    putQuestionnaire: putQuestionnaire,
    deleteQuestionnaire: deleteQuestionnaire,
    getQuestionnaire: getQuestionnaire,
    deployQuestionnaire: deployQuestionnaire,
    closeQuestionnaire: closeQuestionnaire,
    updateQuestionnaire: updateQuestionnaire,
    getQuestions: getQuestions
  };
}
<|endoftext|>


<commit_before>/*
 *  @author Carol Alexandru
 *  @author Silvan Troxler
 *
 *  Copyright 2013 University of Zurich
 *
 *  Licensed below the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed below the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations below the License.
 *
 */

$(document).ready(function() {

  // check whether or not to show the introduction
  var cookieFound = (document.cookie.indexOf("introduction=1") !== -1);
  try {
    var jsonHash = JSON.parse(top.location.hash.slice(1));
  } catch (e) {}
  var forceIntro = (jsonHash != null && jsonHash.intro != null);
  if (cookieFound && !forceIntro) {
    // we do not have to show the introduction
    return false;
  }
  var url = document.URL.split("/");
  var loc = url[0] + "//" + url[2];
  var locGraph = '' + loc + '/graph';
  var locResources = '' + loc + '/resources';

  // define the introduction selectors and texts
  var properties = {
    "#title":
      "Welcome! This is a guided tour through the user interface of the Signal/Collect Console.
" +
      "Navigate these steps by using the arrow keys or your mouse and hit ESC or click 'Skip' to cancel the tour. " +
      "You can revisit the tour at any time by clicking on Tour in the lower right corner of the screen.",
    "#modes":
      "The Console Server consists of two main views: The Graph View and the Resource View. " +
      "You can switch between the views using these buttons, or you may access a single view under " + locGraph +
      " or " + locResources + " without loading the other.",
    "#cVertexSelection":
      "In the Graph view you can view and navigate the graph of your current computation. You start out with an empty " +
      "canvas onto which you add the vertices you're interested in. When the view becomes cluttered, you can remove vertices you don't " +
      "need anymore.",
    "#gp_container":
      "The choices you make here may have an impact on how quickly your query completes and on how smoothly the graph will be " +
      "displayed.
" +
      "

The hop radius determines the maximum distance travelled from the source vertex when loading its vicinity. Raising it is only useful in graphs where all vertices have a very low degree.
" +
      "
computing incoming edges is somewhat more expensive than only computing outgoing ones when loading vicinities
" +
      "
The refresh rate determines how often the graph auto-reloads when running a computation continuously
" +
      "
The maximum vertex count sets an upper cap on how many vertices are displayed. If there are too many, those which have been added earliest will be removed to make way for the new ones.
" +
      "
Exposing vertex details allows you to view any information a vertex is exposing through its 'expose' function.

",
    "#gs_container":
      "Add vertices..." +
      "

by enabling an option to expand vertices on hover
" +
      "
by their exceptional properties
" +
      "
by searching for vertices with an ID containing the given string
" +
      "
by double-clicking on a vertex to expand its vicinity
" +
      "
by clicking on 'Selected vertices' and then using the mouse to draw a rectangle around several vertices to expand

" +
      "Signal/Collect Console remembers which vertices have been previously added to the canvas even beyond a restart of the server or your browser.",
    "#gd_container":
      "Clear the entire canvas or remove vertices by drawing a rectangle around them. You can also remove all vertices except the ones most recently added to the canvas, or any vertices that don't have any edges (orphans).",
    "#cGraphDesign":
      "Choose how the vertices in the graph should look like. You can make the vertex size, fill color and outline color correspond to " +
      "certain properties of the vertex.",
    "#cGraphControl":
      "Select a vertex and add break conditions to halt the computation when certain events occur.",
    "#controls":
      "If your graph computation is using the interactive execution mode, then you can use these buttons to control it. ",
    "#reset":
      "Reset the computation to restore all vertices to their initial state",
    "#pause":
      " Pause the computation if it is running",
    "#step":
      " Performing a partial step will walk you through all the states of a single iteration, pausing before each of them: " +
               "signalling, condition checks after signalling, collecting, condition checks after collecting, and global termination check.",
    "#collect":
      " Performing a full step continues the iteration and stops before the next signal step.",
    "#continue":
      " When clicking continue, the computation will run until you pause it or until a break condition fires.",
    "#terminate":
      " Click terminate to end the computation and quit Signal/Collect.",
    "#stateContainer":
      " The current state and the current iteration are displayed here",
    "#mode_resources":
      "To change to the 'Resources', just click on it.",
    "#crs_statistics h1":
      "First you will see statistics about your infrastructure, the computation that
 is running, the graph parameters," +
      " and estimations about the graph size.",
    "#cResourceComputation":
      "Besides statistics, you can also look at log messages and several different charts.",
    "#crs_nodecharts h1":
      "

A chart draws three different lines over time. The blue line" +
      " visualizes the lowest value over
 all nodes or workers, the red" +
      " line is the highest value, and the black line is the average value.

The controls in the upper" +
      " right can be used to collectively zoom, shift, and reset all charts.

",
    "#cResourceProblems":
      "Should you ever have a problem with a computation, we try to help in this section. For every problem or" +
      " question, we try to explain why this could have happened and show information that might help in solving" +
      " a problem or answer a question.",
    "#tour":
      "This is the end of the tour. We hope you enjoy using this tool. " +
      "You can revisit the tour at any time by clicking on Tour in the lower right corner."
  };

  // add the introduction information to the DOM
  var i = 1;
  $.each(properties, function(selector, val) {
    $(selector).attr("data-step", i++).attr("data-intro", val);
  });
  $("#controls,#reset,#pause,#step,#collect,#continue,#terminate,#stateContainer, " +
    "#cGraphDesign,#cGraphControl").attr("data-position", "top");

  // show the introduction
  var intro = introJs();
  var view = "";
  intro.onchange(function(targetElement) {
    var currentDataStep = parseInt($(targetElement).attr("data-step"));
    if (currentDataStep >= parseInt($("#cResourceComputation").attr("data-step"))) {
      scc.lib.resources.show_section("nodecharts");
      view = ',"main":{"view":"resources"},"resources":{"section":"nodecharts"}';
    }
    else if (currentDataStep >= parseInt($("#mode_resources").attr("data-step"))) {
      $("#mode_resources").click();
      view = "";
    } else {
      view = "";
    }
    if (targetElement.id == "cResourceComputation") {
      $("#mode_resources").click();
    }
    if (targetElement.id == "stateContainer") {
      $("#mode_graph").click();
    }
    top.location.hash = '{"intro":' + $(targetElement).attr('data-step') + view + "}";
  });
  if (forceIntro) {
    intro.goToStep(jsonHash.intro);
  } else {
    top.location.hash = "";
    $("#mode_graph").click();
  }
  var finishIntro = function() {
    $("#mode_graph").click();
    top.location.hash = "";
  };
  intro.onexit(finishIntro);
  intro.oncomplete(finishIntro);
  intro.start();

  // set a cookie for one year
  document.cookie = "introduction=1;max-age=31536000"
});
<commit_msg>Revert position of last tooltip in tour<commit_after>/*
 *  @author Carol Alexandru
 *  @author Silvan Troxler
 *
 *  Copyright 2013 University of Zurich
 *
 *  Licensed below the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed below the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations below the License.
 *
 */

$(document).ready(function() {

  // check whether or not to show the introduction
  var cookieFound = (document.cookie.indexOf("introduction=1") !== -1);
  try {
    var jsonHash = JSON.parse(top.location.hash.slice(1));
  } catch (e) {}
  var forceIntro = (jsonHash != null && jsonHash.intro != null);
  if (cookieFound && !forceIntro) {
    // we do not have to show the introduction
    return false;
  }
  var url = document.URL.split("/");
  var loc = url[0] + "//" + url[2];
  var locGraph = '' + loc + '/graph';
  var locResources = '' + loc + '/resources';

  // define the introduction selectors and texts
  var properties = {
    "#title":
      "Welcome! This is a guided tour through the user interface of the Signal/Collect Console.
" +
      "Navigate these steps by using the arrow keys or your mouse and hit ESC or click 'Skip' to cancel the tour. " +
      "You can revisit the tour at any time by clicking on Tour in the lower right corner of the screen.",
    "#modes":
      "The Console Server consists of two main views: The Graph View and the Resource View. " +
      "You can switch between the views using these buttons, or you may access a single view under " + locGraph +
      " or " + locResources + " without loading the other.",
    "#cVertexSelection":
      "In the Graph view you can view and navigate the graph of your current computation. You start out with an empty " +
      "canvas onto which you add the vertices you're interested in. When the view becomes cluttered, you can remove vertices you don't " +
      "need anymore.",
    "#gp_container":
      "The choices you make here may have an impact on how quickly your query completes and on how smoothly the graph will be " +
      "displayed.
" +
      "

The hop radius determines the maximum distance travelled from the source vertex when loading its vicinity. Raising it is only useful in graphs where all vertices have a very low degree.
" +
      "
computing incoming edges is somewhat more expensive than only computing outgoing ones when loading vicinities
" +
      "
The refresh rate determines how often the graph auto-reloads when running a computation continuously
" +
      "
The maximum vertex count sets an upper cap on how many vertices are displayed. If there are too many, those which have been added earliest will be removed to make way for the new ones.
" +
      "
Exposing vertex details allows you to view any information a vertex is exposing through its 'expose' function.

",
    "#gs_container":
      "Add vertices..." +
      "

by enabling an option to expand vertices on hover
" +
      "
by their exceptional properties
" +
      "
by searching for vertices with an ID containing the given string
" +
      "
by double-clicking on a vertex to expand its vicinity
" +
      "
by clicking on 'Selected vertices' and then using the mouse to draw a rectangle around several vertices to expand

" +
      "Signal/Collect Console remembers which vertices have been previously added to the canvas even beyond a restart of the server or your browser.",
    "#gd_container":
      "Clear the entire canvas or remove vertices by drawing a rectangle around them. You can also remove all vertices except the ones most recently added to the canvas, or any vertices that don't have any edges (orphans).",
    "#cGraphDesign":
      "Choose how the vertices in the graph should look like. You can make the vertex size, fill color and outline color correspond to " +
      "certain properties of the vertex.",
    "#cGraphControl":
      "Select a vertex and add break conditions to halt the computation when certain events occur.",
    "#controls":
      "If your graph computation is using the interactive execution mode, then you can use these buttons to control it. ",
    "#reset":
      "Reset the computation to restore all vertices to their initial state",
    "#pause":
      " Pause the computation if it is running",
    "#step":
      " Performing a partial step will walk you through all the states of a single iteration, pausing before each of them: " +
               "signalling, condition checks after signalling, collecting, condition checks after collecting, and global termination check.",
    "#collect":
      " Performing a full step continues the iteration and stops before the next signal step.",
    "#continue":
      " When clicking continue, the computation will run until you pause it or until a break condition fires.",
    "#terminate":
      " Click terminate to end the computation and quit Signal/Collect.",
    "#stateContainer":
      " The current state and the current iteration are displayed here",
    "#mode_resources":
      "To change to the 'Resources', just click on it.",
    "#crs_statistics h1":
      "First you will see statistics about your infrastructure, the computation that
 is running, the graph parameters," +
      " and estimations about the graph size.",
    "#cResourceComputation":
      "Besides statistics, you can also look at log messages and several different charts.",
    "#crs_nodecharts h1":
      "

A chart draws three different lines over time. The blue line" +
      " visualizes the lowest value over
 all nodes or workers, the red" +
      " line is the highest value, and the black line is the average value.

The controls in the upper" +
      " right can be used to collectively zoom, shift, and reset all charts.

",
    "#cResourceProblems":
      "Should you ever have a problem with a computation, we try to help in this section. For every problem or" +
      " question, we try to explain why this could have happened and show information that might help in solving" +
      " a problem or answer a question.",
    "#title img":
      "This is the end of the tour. We hope you enjoy using this tool. " +
      "You can revisit the tour at any time by clicking on Tour in the lower right corner."
  };

  // add the introduction information to the DOM
  var i = 1;
  $.each(properties, function(selector, val) {
    $(selector).attr("data-step", i++).attr("data-intro", val);
  });
  $("#controls,#reset,#pause,#step,#collect,#continue,#terminate,#stateContainer, " +
    "#cGraphDesign,#cGraphControl").attr("data-position", "top");

  // show the introduction
  var intro = introJs();
  var view = "";
  intro.onchange(function(targetElement) {
    var currentDataStep = parseInt($(targetElement).attr("data-step"));
    if (currentDataStep >= parseInt($("#cResourceComputation").attr("data-step"))) {
      scc.lib.resources.show_section("nodecharts");
      view = ',"main":{"view":"resources"},"resources":{"section":"nodecharts"}';
    }
    else if (currentDataStep >= parseInt($("#mode_resources").attr("data-step"))) {
      $("#mode_resources").click();
      view = "";
    } else {
      view = "";
    }
    if (targetElement.id == "cResourceComputation") {
      $("#mode_resources").click();
    }
    if (targetElement.id == "stateContainer") {
      $("#mode_graph").click();
    }
    top.location.hash = '{"intro":' + $(targetElement).attr('data-step') + view + "}";
  });
  if (forceIntro) {
    intro.goToStep(jsonHash.intro);
  } else {
    top.location.hash = "";
    $("#mode_graph").click();
  }
  var finishIntro = function() {
    $("#mode_graph").click();
    top.location.hash = "";
  };
  intro.onexit(finishIntro);
  intro.oncomplete(finishIntro);
  intro.start();

  // set a cookie for one year
  document.cookie = "introduction=1;max-age=31536000"
});
<|endoftext|>


<commit_before>import template from '../templates/button.html';
import { severity } from '../meta';

function unCapitalizeFirstLetter(string) {
    return string.charAt(0).toLowerCase() + string.slice(1);
}

export default Marionette.View.extend({
    template: Handlebars.compile(template),

    attributes() {
        return Object.assign({ tabindex: 0 }, this.model.get('title') ? { title: this.model.get('title') } : {});
    },

    className() {
        const severityLevel = this.model.get('severity');
        const severityItem = severity[severityLevel] || severity.None;
        const type = this.model.get('type');
        const typeClass = typeof type === 'string' ? unCapitalizeFirstLetter(type) : 'withoutType';

        return `${severityItem.class} ${this.model.get('class') || ''} toolbar-btn_${typeClass}`;
    }
});
<commit_msg>title -> description<commit_after>import template from '../templates/button.html';
import { severity } from '../meta';

function unCapitalizeFirstLetter(string) {
    return string.charAt(0).toLowerCase() + string.slice(1);
}

export default Marionette.View.extend({
    template: Handlebars.compile(template),

    attributes() {
        return Object.assign({ tabindex: 0 }, this.model.get('description') ? { title: this.model.get('description') } : {});
    },

    className() {
        const severityLevel = this.model.get('severity');
        const severityItem = severity[severityLevel] || severity.None;
        const type = this.model.get('type');
        const typeClass = typeof type === 'string' ? unCapitalizeFirstLetter(type) : 'withoutType';

        return `${severityItem.class} ${this.model.get('class') || ''} toolbar-btn_${typeClass}`;
    }
});
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {formatDate} from '../../../DateUtils';
import { _t } from '../../../languageHandler';
import filesize from "filesize";
import AccessibleButton from "./AccessibleButton";
import Modal from "../../../Modal";
import * as sdk from "../../../index";
import {Key} from "../../../Keyboard";
import FocusLock from "react-focus-lock";

export default class ImageView extends React.Component {
    static propTypes = {
        src: PropTypes.string.isRequired, // the source of the image being displayed
        name: PropTypes.string, // the main title ('name') for the image
        link: PropTypes.string, // the link (if any) applied to the name of the image
        width: PropTypes.number, // width of the image src in pixels
        height: PropTypes.number, // height of the image src in pixels
        fileSize: PropTypes.number, // size of the image src in bytes
        onFinished: PropTypes.func.isRequired, // callback when the lightbox is dismissed

        // the event (if any) that the Image is displaying. Used for event-specific stuff like
        // redactions, senders, timestamps etc.  Other descriptors are taken from the explicit
        // properties above, which let us use lightboxes to display images which aren't associated
        // with events.
        mxEvent: PropTypes.object,
    };

    constructor(props) {
        super(props);
        this.state = {
            rotation: 0,
            zoom: 100,
            translationX: 0,
            translationY: 0,
            moving: false,
        };
    }

    initX = 0;
    initY = 0;
    lastX = 0;
    lastY = 0;
    minZoom = 100;
    maxZoom = 300;

    componentDidMount() {
        /* We have to use addEventListener() because the listener
         * needs to be passive in order to work with Chromium */
        this.focusLock.addEventListener('wheel', this.onWheel, { passive: false });
    }

    componentWillUnmount() {
        this.focusLock.removeEventListener('wheel', this.onWheel);
    }

    onKeyDown = (ev) => {
        if (ev.key === Key.ESCAPE) {
            ev.stopPropagation();
            ev.preventDefault();
            this.props.onFinished();
        }
    };

    onWheel = (ev) => {
        ev.stopPropagation();
        ev.preventDefault();
        const newZoom =this.state.zoom - ev.deltaY;

        if (newZoom <= this.minZoom) {
            this.setState({
                zoom: this.minZoom,
                translationX: 0,
                translationY: 0,
            });
            return;
        }
        if (newZoom >= this.maxZoom) {
            this.setState({zoom: this.maxZoom});
            return;
        }

        this.setState({
            zoom: newZoom,
        });
    }

    onRedactClick = () => {
        const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
        Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
            onFinished: (proceed) => {
                if (!proceed) return;
                this.props.onFinished();
                MatrixClientPeg.get().redactEvent(
                    this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(),
                ).catch(function(e) {
                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                    // display error message stating you couldn't delete this.
                    const code = e.errcode || e.statusCode;
                    Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
                        title: _t('Error'),
                        description: _t('You cannot delete this image. (%(code)s)', {code: code}),
                    });
                });
            },
        });
    };

    getName() {
        let name = this.props.name;
        if (name && this.props.link) {
            name = { name };
        }
        return name;
    }

    onRotateCounterClockwiseClick = () => {
        const cur = this.state.rotation;
        const rotationDegrees = (cur - 90) % 360;
        this.setState({ rotation: rotationDegrees });
    };

    onRotateClockwiseClick = () => {
        const cur = this.state.rotation;
        const rotationDegrees = (cur + 90) % 360;
        this.setState({ rotation: rotationDegrees });
    };

    onZoomInClick = () => {
        if (this.state.zoom >= this.maxZoom) {
            this.setState({zoom: this.maxZoom});
            return;
        }

        this.setState({
            zoom: this.state.zoom + 10,
        });
    };

    onZoomOutClick = () => {
        if (this.state.zoom <= this.minZoom) {
            this.setState({
                zoom: this.minZoom,
                translationX: 0,
                translationY: 0,
            });
            return;
        }
        this.setState({
            zoom: this.state.zoom - 10,
        });
    }

    onZoomClick = () => {
        if (this.state.zoom <= this.minZoom) {
            this.setState({zoom: this.maxZoom});
        } else {
            this.setState({
                zoom: this.minZoom,
                translationX: 0,
                translationY: 0,
            });
        }
    }

    onStartMoving = ev => {
        ev.stopPropagation();
        ev.preventDefault();

        this.setState({moving: true});
        this.initX = ev.pageX - this.lastX;
        this.initY = ev.pageY - this.lastY;
    }

    onMoving = ev => {
        ev.stopPropagation();
        ev.preventDefault();

        if (!this.state.moving) return false;

        this.lastX = ev.pageX - this.initX;
        this.lastY = ev.pageY - this.initY;
        this.setState({
            translationX: this.lastX,
            translationY: this.lastY,
        });
    }

    onEndMoving = ev => {
        this.setState({moving: false});
    }

    render() {
        let mayRedact = false;
        const showEventMeta = !!this.props.mxEvent;

        let res;
        if (this.props.width && this.props.height) {
            res = this.props.width + "x" + this.props.height + "px";
        }

        let size;
        if (this.props.fileSize) {
            size = filesize(this.props.fileSize);
        }

        let sizeRes;
        if (size && res) {
            sizeRes = size + ", " + res;
        } else {
            sizeRes = size || res;
        }

        let metadata;
        if (showEventMeta) {
            // Figure out the sender, defaulting to mxid
            let sender = this.props.mxEvent.getSender();
            const cli = MatrixClientPeg.get();
            const room = cli.getRoom(this.props.mxEvent.getRoomId());
            if (room) {
                mayRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId);
                const member = room.getMember(sender);
                if (member) sender = member.name;
            }

            metadata = (

                { _t('Uploaded on %(date)s by %(user)s', {
                    date: formatDate(new Date(this.props.mxEvent.getTs())),
                    user: sender,
                }) }

);
        }

        let redactButton;
        if (mayRedact) {
            redactButton = (
                <AccessibleButton className="mx_ImageView_button" title={_t("Remove")} onClick={ this.onRedactClick }>

                </AccessibleButton>
            );
        }

        const rotationDegrees = this.state.rotation + "deg";
        const zoomPercentage = this.state.zoom/100;
        const translatePixelsX = this.state.translationX + "px";
        const translatePixelsY = this.state.translationY + "px";
        /* The order of the values is important!
         * First, we translate and only then we rotate, otherwise
         * we would apply the translation to an already rotated
         * image causing it translate in the wrong direction. */
        const style = {
            transform: `translateX(${translatePixelsX})
                        translateY(${translatePixelsY})
                        scale(${zoomPercentage})
                        rotate(${rotationDegrees})`,
        };

        return (
            <FocusLock
                returnFocus={true}
                lockProps={{
                    onKeyDown: this.onKeyDown,
                    role: "dialog",
                }}
                className="mx_ImageView"
                ref={ref => this.focusLock = ref}
            >

                            { this.getName() }

                        { sizeRes }
                        { metadata }

                            { redactButton }
                            <AccessibleButton className="mx_ImageView_button" title={_t("Rotate Right")} onClick={ this.onRotateClockwiseClick }>

                            </AccessibleButton>
                            <AccessibleButton className="mx_ImageView_button" title={_t("Rotate Left")} onClick={ this.onRotateCounterClockwiseClick }>

                            </AccessibleButton>
                            <AccessibleButton className="mx_ImageView_button" title={_t("Zoom out")} onClick={ this.onZoomOutClick }>

                            </AccessibleButton>
                            <AccessibleButton className="mx_ImageView_button" title={_t("Zoom in")} onClick={ this.onZoomInClick }>

                            </AccessibleButton>

                            <AccessibleButton className="mx_ImageView_button" title={_t("Close")} onClick={ this.props.onFinished }>

                            </AccessibleButton>

            </FocusLock>
        );
    }
}
<commit_msg>Remove name<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {formatDate} from '../../../DateUtils';
import { _t } from '../../../languageHandler';
import filesize from "filesize";
import AccessibleButton from "./AccessibleButton";
import Modal from "../../../Modal";
import * as sdk from "../../../index";
import {Key} from "../../../Keyboard";
import FocusLock from "react-focus-lock";

export default class ImageView extends React.Component {
    static propTypes = {
        src: PropTypes.string.isRequired, // the source of the image being displayed
        name: PropTypes.string, // the main title ('name') for the image
        link: PropTypes.string, // the link (if any) applied to the name of the image
        width: PropTypes.number, // width of the image src in pixels
        height: PropTypes.number, // height of the image src in pixels
        fileSize: PropTypes.number, // size of the image src in bytes
        onFinished: PropTypes.func.isRequired, // callback when the lightbox is dismissed

        // the event (if any) that the Image is displaying. Used for event-specific stuff like
        // redactions, senders, timestamps etc.  Other descriptors are taken from the explicit
        // properties above, which let us use lightboxes to display images which aren't associated
        // with events.
        mxEvent: PropTypes.object,
    };

    constructor(props) {
        super(props);
        this.state = {
            rotation: 0,
            zoom: 100,
            translationX: 0,
            translationY: 0,
            moving: false,
        };
    }

    initX = 0;
    initY = 0;
    lastX = 0;
    lastY = 0;
    minZoom = 100;
    maxZoom = 300;

    componentDidMount() {
        /* We have to use addEventListener() because the listener
         * needs to be passive in order to work with Chromium */
        this.focusLock.addEventListener('wheel', this.onWheel, { passive: false });
    }

    componentWillUnmount() {
        this.focusLock.removeEventListener('wheel', this.onWheel);
    }

    onKeyDown = (ev) => {
        if (ev.key === Key.ESCAPE) {
            ev.stopPropagation();
            ev.preventDefault();
            this.props.onFinished();
        }
    };

    onWheel = (ev) => {
        ev.stopPropagation();
        ev.preventDefault();
        const newZoom =this.state.zoom - ev.deltaY;

        if (newZoom <= this.minZoom) {
            this.setState({
                zoom: this.minZoom,
                translationX: 0,
                translationY: 0,
            });
            return;
        }
        if (newZoom >= this.maxZoom) {
            this.setState({zoom: this.maxZoom});
            return;
        }

        this.setState({
            zoom: newZoom,
        });
    }

    onRedactClick = () => {
        const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
        Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
            onFinished: (proceed) => {
                if (!proceed) return;
                this.props.onFinished();
                MatrixClientPeg.get().redactEvent(
                    this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(),
                ).catch(function(e) {
                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                    // display error message stating you couldn't delete this.
                    const code = e.errcode || e.statusCode;
                    Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
                        title: _t('Error'),
                        description: _t('You cannot delete this image. (%(code)s)', {code: code}),
                    });
                });
            },
        });
    };

    onRotateCounterClockwiseClick = () => {
        const cur = this.state.rotation;
        const rotationDegrees = (cur - 90) % 360;
        this.setState({ rotation: rotationDegrees });
    };

    onRotateClockwiseClick = () => {
        const cur = this.state.rotation;
        const rotationDegrees = (cur + 90) % 360;
        this.setState({ rotation: rotationDegrees });
    };

    onZoomInClick = () => {
        if (this.state.zoom >= this.maxZoom) {
            this.setState({zoom: this.maxZoom});
            return;
        }

        this.setState({
            zoom: this.state.zoom + 10,
        });
    };

    onZoomOutClick = () => {
        if (this.state.zoom <= this.minZoom) {
            this.setState({
                zoom: this.minZoom,
                translationX: 0,
                translationY: 0,
            });
            return;
        }
        this.setState({
            zoom: this.state.zoom - 10,
        });
    }

    onZoomClick = () => {
        if (this.state.zoom <= this.minZoom) {
            this.setState({zoom: this.maxZoom});
        } else {
            this.setState({
                zoom: this.minZoom,
                translationX: 0,
                translationY: 0,
            });
        }
    }

    onStartMoving = ev => {
        ev.stopPropagation();
        ev.preventDefault();

        this.setState({moving: true});
        this.initX = ev.pageX - this.lastX;
        this.initY = ev.pageY - this.lastY;
    }

    onMoving = ev => {
        ev.stopPropagation();
        ev.preventDefault();

        if (!this.state.moving) return false;

        this.lastX = ev.pageX - this.initX;
        this.lastY = ev.pageY - this.initY;
        this.setState({
            translationX: this.lastX,
            translationY: this.lastY,
        });
    }

    onEndMoving = ev => {
        this.setState({moving: false});
    }

    render() {
        let mayRedact = false;
        const showEventMeta = !!this.props.mxEvent;

        let res;
        if (this.props.width && this.props.height) {
            res = this.props.width + "x" + this.props.height + "px";
        }

        let size;
        if (this.props.fileSize) {
            size = filesize(this.props.fileSize);
        }

        let sizeRes;
        if (size && res) {
            sizeRes = size + ", " + res;
        } else {
            sizeRes = size || res;
        }

        let metadata;
        if (showEventMeta) {
            // Figure out the sender, defaulting to mxid
            let sender = this.props.mxEvent.getSender();
            const cli = MatrixClientPeg.get();
            const room = cli.getRoom(this.props.mxEvent.getRoomId());
            if (room) {
                mayRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId);
                const member = room.getMember(sender);
                if (member) sender = member.name;
            }

            metadata = (

                { _t('Uploaded on %(date)s by %(user)s', {
                    date: formatDate(new Date(this.props.mxEvent.getTs())),
                    user: sender,
                }) }

);
        }

        let redactButton;
        if (mayRedact) {
            redactButton = (
                <AccessibleButton className="mx_ImageView_button" title={_t("Remove")} onClick={ this.onRedactClick }>

                </AccessibleButton>
            );
        }

        const rotationDegrees = this.state.rotation + "deg";
        const zoomPercentage = this.state.zoom/100;
        const translatePixelsX = this.state.translationX + "px";
        const translatePixelsY = this.state.translationY + "px";
        /* The order of the values is important!
         * First, we translate and only then we rotate, otherwise
         * we would apply the translation to an already rotated
         * image causing it translate in the wrong direction. */
        const style = {
            transform: `translateX(${translatePixelsX})
                        translateY(${translatePixelsY})
                        scale(${zoomPercentage})
                        rotate(${rotationDegrees})`,
        };

        return (
            <FocusLock
                returnFocus={true}
                lockProps={{
                    onKeyDown: this.onKeyDown,
                    role: "dialog",
                }}
                className="mx_ImageView"
                ref={ref => this.focusLock = ref}
            >

                            { sizeRes }
                            { metadata }

                            { redactButton }
                            <AccessibleButton className="mx_ImageView_button" title={_t("Rotate Right")} onClick={ this.onRotateClockwiseClick }>

                            </AccessibleButton>
                            <AccessibleButton className="mx_ImageView_button" title={_t("Rotate Left")} onClick={ this.onRotateCounterClockwiseClick }>

                            </AccessibleButton>
                            <AccessibleButton className="mx_ImageView_button" title={_t("Zoom out")} onClick={ this.onZoomOutClick }>

                            </AccessibleButton>
                            <AccessibleButton className="mx_ImageView_button" title={_t("Zoom in")} onClick={ this.onZoomInClick }>

                            </AccessibleButton>

                            <AccessibleButton className="mx_ImageView_button" title={_t("Close")} onClick={ this.props.onFinished }>

                            </AccessibleButton>

            </FocusLock>
        );
    }
}
<|endoftext|>