<commit_before>var root = require('root')
var from = require('from2')
var fs = require('fs')
var mkdirp = require('mkdirp')
var path = require('path')
var pump = require('pump')
var JSONStream = require('JSONStream')
var through = require('through2')
var levelup = require('levelup')
var leveldown = require('leveldown')
var sublevel = require('level-sublevel')
var memdown = require('memdown')
var thunky = require('thunky')
var tar = require('tar-stream')
var xtend = require('xtend')
var zlib = require('zlib')

module.exports = function(opts) {
  if (!opts) opts = {}

  var server = root()
  var cwd = opts.cwd || 'docker-registry'
  var layers = path.join(cwd, 'layers')
  var db

  // setup db

  var setup = thunky(function(cb) {
    mkdirp(layers, function() {
      db = sublevel(levelup(path.join(cwd, 'db'), {valueEncoding:'json', db: process.env.MEMDOWN ? memdown : leveldown}))

      db.images = db.sublevel('images')
      db.images.checksums = db.images.sublevel('checksums')

      db.repositories = db.sublevel('repositories')
      db.repositories.tags = db.repositories.sublevel('tags')

      server.emit('setup')
      cb()
    })
  })

  server.all(function(req, res, next) {
    setup(next)
  })

  // library paths

  server.all('/v1/repositories/{name}', '/v1/repositories/library/{name}')
  server.all('/v1/repositories/{name}/images', '/v1/repositories/library/{name}/images')
  server.all('/v1/repositories/{name}/tags/*', '/v1/repositories/library/{name}/tags/{*}')

  // images

  var ancestry = function(parent) {
    return from.obj(function(size, next) {
      if (!parent) return next(null, null)
      db.images.get(parent, function(err, data) {
        if (err) return next(err)
        parent = data.parent
        next(null, data.id || null)
      })
    })
  }

  var emit = function(type, data) {
    server.emit(type, data)
    server.emit('event', xtend({type:type}, data))
  }

  server.setMaxListeners(0)

  // non official file api
  server.get('/v1/files/{id}/*', function(req, res) {
    var id = req.params.id
    var filename = req.params.glob

    var search = function(id) {
      var layer = fs.createReadStream(path.join(layers, id))
      var extract = tar.extract()
      var found = false

      var ondone = function() {
        if (found) return
        db.images.get(id, function(err, img) {
          if (err) return res.error(err)
          if (!img.parent) return res.error(404, filename+' not found')
          search(img.parent)
        })
      }

      extract.on('entry', function(header, stream, next) {
        if (header.type !== 'file' || header.name !== filename) return next()

        found = true
        pump(stream, res, function() {
          extract.destroy()
        })
      })

      pump(layer, zlib.createGunzip(), extract, ondone)
    }

    search(id)
  })

  // non official events api
  server.get('/v1/events', function(req, res) {
    res.setTimeout(0) // not perfect but lets just rely on this for now

    var onevent = function(e) {
      if (e.type === 'image') e = {type:'image', id:e.id, parent:e.parent} // dont send too much data
      res.write(JSON.stringify(e)+'\n')
    }

    res.on('close', function() {
      server.removeListener('event', onevent)
    })

    server.on('event', onevent)
  })

  server.get('/v1/images/{id}/ancestry', function(req, res) {
    var id = req.params.id

    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    pump(
      ancestry(id),
      JSONStream.stringify(),
      res
    )
  })

  server.get('/v1/images/{id}/json', function(req, res) {
    var id = req.params.id

    db.images.checksums.get(id, function(err, checksum) {
      if (err) return res.error(err)
      res.setHeader('X-Docker-Checksum', checksum)
      db.images.get(id, function(err, data) {
        if (err) return res.error(err)
        res.send(data)
      })
    })
  })

  server.put('/v1/images/{id}/json', function(req, res) {
    var id = req.params.id

    req.on('json', function(data) {
      db.images.put(id, data, function(err) {
        if (err) return res.error(err)
        emit('image', data)
        res.end()
      })
    })
  })

  server.put('/v1/images/{id}/checksum', function(req, res) {
    var id = req.params.id
    var sum = req.headers['x-docker-checksum-payload'] || null

    if (!sum) return res.error(400, 'checksum is required')
    db.images.checksums.put(id, sum, function(err) {
      if (err) return res.error(err)
      emit('checksum', {id:id, hash:sum})
      res.end()
    })
  })

  server.put('/v1/images/{id}/layer', function(req, res) {
    var id = req.params.id
    var p = path.join(layers, id)
    var layer = fs.createWriteStream(p)

    pump(req, layer, function(err) {
      if (err) return res.error(err)
      emit('layer', {id:id, path:p})
      res.end()
    })
  })

  server.get('/v1/images/{id}/layer', function(req, res) {
    var id = req.params.id
    var layer = fs.createReadStream(path.join(layers, id))

    pump(layer, res)
  })

  // repo stuff

  server.put('/v1/repositories/{namespace}/{name}', function(req, res) {
    req.on('json', function(data) {
      res.setHeader('WWW-Authenticate', 'Token signature=123abc,repository="test",access=write')
      res.setHeader('X-Docker-Token', 'signature=123abc,repository="test",access=write')
      res.setHeader('X-Docker-Endpoints', req.headers.host)
      res.end()
    })
  })

  server.del('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var namespace = req.params.namespace
    var name = req.params.name
    var tag = req.params.tag
    var key = namespace+'/'+name+'@'+tag

    db.repositories.tags.get(key, function(_, id) {
      db.repositories.tags.del(key, function(err) {
        if (err) return res.error(err)
        if (id) emit('untag', {id:id, namespace:namespace, name:name, tag:tag})
        res.end()
      })
    })
  })

  server.put('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var namespace = req.params.namespace
    var name = req.params.name
    var tag = req.params.tag
    var key = namespace+'/'+name+'@'+tag

    req.on('json', function(id) {
      db.repositories.tags.put(key, id, function(err) {
        if (err) return res.error(err)
        emit('tag', {id:id, namespace:namespace, name:name, tag:tag})
        res.end()
      })
    })
  })

  server.get('/v1/repositories/{namespace}/{name}/tags', function(req, res) {
    var id = req.params.namespace+'/'+req.params.name

    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    pump(
      db.repositories.tags.createReadStream({start:id+'@', end:id+'@~'}),
      through.obj(function(data, enc, cb) {
        cb(null, [data.key.split('@').pop(), data.value])
      }),
      JSONStream.stringifyObject(),
      res
    )
  })

  server.get('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var tag = req.params.namespace+'/'+req.params.name+'/'+req.params.tag

    db.repositories.tags.get(tag, function(err, id) {
      if (err) return res.error(err)
      res.send(id)
    })
  })

  server.put('/v1/repositories/{namespace}/{name}/images', function(req, res) {
    var id = req.params.namespace+'/'+req.params.name

    req.on('json', function(list) {
      db.images.put(id, list, function(err) {
        if (err) return res.error(err)
        res.statusCode = 204
        res.end()
      })
    })
  })

  server.get('/v1/repositories/{namespace}/{name}/images', function(req, res) {
    var id = req.params.namespace+'/'+req.params.name

    db.images.get(id, function(err, list) {
      if (err) return res.error(err)
      res.send(list)
    })
  })

  server.get('/', function(req, res) {
    res.send({
      service: 'docker-registry',
      version: require('./package').version
    })
  })

  // misc stuff

  server.get('/v1/_ping', function(req, res) {
    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    res.send('true')
  })

  server.error(function(req, res, err) {
    res.send({
      status: res.statusCode,
      error: err.message.trim()
    })
  })

  return server
}<commit_msg>add content-length and x-docker-size<commit_after>var root = require('root')
var from = require('from2')
var fs = require('fs')
var mkdirp = require('mkdirp')
var path = require('path')
var pump = require('pump')
var JSONStream = require('JSONStream')
var through = require('through2')
var levelup = require('levelup')
var leveldown = require('leveldown')
var sublevel = require('level-sublevel')
var memdown = require('memdown')
var thunky = require('thunky')
var tar = require('tar-stream')
var xtend = require('xtend')
var zlib = require('zlib')

module.exports = function(opts) {
  if (!opts) opts = {}

  var server = root()
  var cwd = opts.cwd || 'docker-registry'
  var layers = path.join(cwd, 'layers')
  var db

  // setup db

  var setup = thunky(function(cb) {
    mkdirp(layers, function() {
      db = sublevel(levelup(path.join(cwd, 'db'), {valueEncoding:'json', db: process.env.MEMDOWN ? memdown : leveldown}))

      db.images = db.sublevel('images')
      db.images.checksums = db.images.sublevel('checksums')
      db.images.blobs = db.sublevel('blobs')

      db.repositories = db.sublevel('repositories')
      db.repositories.tags = db.repositories.sublevel('tags')

      server.emit('setup')
      cb()
    })
  })

  server.all(function(req, res, next) {
    setup(next)
  })

  // library paths

  server.all('/v1/repositories/{name}', '/v1/repositories/library/{name}')
  server.all('/v1/repositories/{name}/images', '/v1/repositories/library/{name}/images')
  server.all('/v1/repositories/{name}/tags/*', '/v1/repositories/library/{name}/tags/{*}')

  // images

  var ancestry = function(parent) {
    return from.obj(function(size, next) {
      if (!parent) return next(null, null)
      db.images.get(parent, function(err, data) {
        if (err) return next(err)
        parent = data.parent
        next(null, data.id || null)
      })
    })
  }

  var emit = function(type, data) {
    server.emit(type, data)
    server.emit('event', xtend({type:type}, data))
  }

  server.setMaxListeners(0)

  // non official events api
  server.get('/v1/events', function(req, res) {
    res.setTimeout(0) // not perfect but lets just rely on this for now

    var onevent = function(e) {
      if (e.type === 'image') e = {type:'image', id:e.id, parent:e.parent} // dont send too much data
      res.write(JSON.stringify(e)+'\n')
    }

    res.on('close', function() {
      server.removeListener('event', onevent)
    })

    server.on('event', onevent)
  })

  // non official file api
  server.get('/v1/images/{id}/blob/*', function(req, res) {
    var id = req.params.id
    var filename = req.params.glob

    var search = function(id) {
      var layer = fs.createReadStream(path.join(layers, id))
      var extract = tar.extract()
      var found = false

      var ondone = function() {
        if (found) return
        db.images.get(id, function(err, img) {
          if (err) return res.error(err)
          if (!img.parent) return res.error(404, filename+' not found')
          search(img.parent)
        })
      }

      extract.on('entry', function(header, stream, next) {
        if (header.type !== 'file' || header.name !== filename) {
          stream.resume()
          return next()
        }

        found = true
        pump(stream, res, function() {
          extract.destroy()
        })
      })

      pump(layer, zlib.createGunzip(), extract, ondone)
    }

    search(id)
  })

  server.get('/v1/images/{id}/ancestry', function(req, res) {
    var id = req.params.id

    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    pump(
      ancestry(id),
      JSONStream.stringify(),
      res
    )
  })

  server.get('/v1/images/{id}/json', function(req, res) {
    var id = req.params.id
    var file = path.join(layers, id)

    db.images.checksums.get(id, function(err, checksum) {
      if (err) return res.error(err)
      res.setHeader('X-Docker-Checksum', checksum)
      fs.stat(file, function(_, stat) {
        if (stat) res.setHeader('X-Docker-Size', stat.size)
        db.images.get(id, function(err, data) {
          if (err) return res.error(err)
          res.send(data)
        })
      })
    })
  })

  server.put('/v1/images/{id}/json', function(req, res) {
    var id = req.params.id

    req.on('json', function(data) {
      db.images.put(id, data, function(err) {
        if (err) return res.error(err)
        emit('image', data)
        res.end()
      })
    })
  })

  server.put('/v1/images/{id}/checksum', function(req, res) {
    var id = req.params.id
    var sum = req.headers['x-docker-checksum-payload'] || null

    if (!sum) return res.error(400, 'checksum is required')
    db.images.checksums.put(id, sum, function(err) {
      if (err) return res.error(err)
      emit('checksum', {id:id, hash:sum})
      res.end()
    })
  })

  server.put('/v1/images/{id}/layer', function(req, res) {
    var id = req.params.id
    var file = path.join(layers, id)
    var layer = fs.createWriteStream(file)

    pump(req, layer, function(err) {
      if (err) return res.error(err)
      emit('layer', {id:id, path:file})
      res.end()
    })
  })

  server.get('/v1/images/{id}/layer', function(req, res) {
    var id = req.params.id
    var file = path.join(layers, id)

    fs.stat(file, function(err, stat) {
      if (err) return res.error(404)
      res.setHeader('Content-Length', stat.size)
      pump(fs.createReadStream(file), res)
    })
  })

  // repo stuff

  server.put('/v1/repositories/{namespace}/{name}', function(req, res) {
    req.on('json', function(data) {
      res.setHeader('WWW-Authenticate', 'Token signature=123abc,repository="test",access=write')
      res.setHeader('X-Docker-Token', 'signature=123abc,repository="test",access=write')
      res.setHeader('X-Docker-Endpoints', req.headers.host)
      res.end()
    })
  })

  server.del('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var namespace = req.params.namespace
    var name = req.params.name
    var tag = req.params.tag
    var key = namespace+'/'+name+'@'+tag

    db.repositories.tags.get(key, function(_, id) {
      db.repositories.tags.del(key, function(err) {
        if (err) return res.error(err)
        if (id) emit('untag', {id:id, namespace:namespace, name:name, tag:tag})
        res.end()
      })
    })
  })

  server.put('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var namespace = req.params.namespace
    var name = req.params.name
    var tag = req.params.tag
    var key = namespace+'/'+name+'@'+tag

    req.on('json', function(id) {
      db.repositories.tags.put(key, id, function(err) {
        if (err) return res.error(err)
        emit('tag', {id:id, namespace:namespace, name:name, tag:tag})
        res.end()
      })
    })
  })

  server.get('/v1/repositories/{namespace}/{name}/tags', function(req, res) {
    var id = req.params.namespace+'/'+req.params.name

    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    pump(
      db.repositories.tags.createReadStream({start:id+'@', end:id+'@~'}),
      through.obj(function(data, enc, cb) {
        cb(null, [data.key.split('@').pop(), data.value])
      }),
      JSONStream.stringifyObject(),
      res
    )
  })

  server.get('/v1/repositories/{namespace}/{name}/tags/{tag}', function(req, res) {
    var tag = req.params.namespace+'/'+req.params.name+'@'+req.params.tag

    db.repositories.tags.get(tag, function(err, id) {
      if (err) return res.error(err)
      res.setHeader('Content-Length', Buffer.byteLength(id))
      res.end(id)
    })
  })

  server.put('/v1/repositories/{namespace}/{name}/images', function(req, res) {
    var id = req.params.namespace+'/'+req.params.name

    req.on('json', function(list) {
      db.images.put(id, list, function(err) {
        if (err) return res.error(err)
        res.statusCode = 204
        res.end()
      })
    })
  })

  server.get('/v1/repositories/{namespace}/{name}/images', function(req, res) {
    var id = req.params.namespace+'/'+req.params.name

    db.images.get(id, function(err, list) {
      if (err) return res.error(err)
      res.send(list)
    })
  })

  server.get('/', function(req, res) {
    res.send({
      service: 'docker-registry',
      version: require('./package').version
    })
  })

  // misc stuff

  server.get('/v1/_ping', function(req, res) {
    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    res.send('true')
  })

  server.error(function(req, res, err) {
    res.send({
      status: res.statusCode,
      error: err.message.trim()
    })
  })

  return server
}<|endoftext|>


<commit_before>/**
 * MIT License
 *
 * Copyright (c) 2017 Alisdair Smyth
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
**/
var ambi = require('node-ambiclimate');
var Service, Characteristic;

module.exports = function(homebridge) {
    Service        = homebridge.hap.Service;
    Characteristic = homebridge.hap.Characteristic;
    homebridge.registerAccessory("homebridge-ambiclimate-thermostat", "AmbiClimateThermostat", AmbiClimate);
}

function AmbiClimate(log, config) {
    this.log                    = log;
    this.name                   = config.name;
    this.settings               = {};
    this.settings.room_name     = config.roomName;
    this.settings.location_name = config.locationName;

    this.client = new ambi(config.clientId, config.clientSecret, config.username, config.password);

    // Object to maintain the state of the device.  This is done as there is
    // not official API to get this information.  If the Ambi Climate state is
    // changed through an alternative channel, that change will not be reflected
    // within Homekit
    this.state              = {};
    this.state.on           = false;

    this.thermostatService  = new Service.Thermostat(this.name);
    this.informationService = new Service.AccessoryInformation();
}

AmbiClimate.prototype = {
    getCurrentHeatingCoolingState: function(callback) {
        //
        // Map retrieved Ambi Climate mode to Homekit Heating, Cooling state.
        // Comfort mode can be heating or cooling, but as there is no
        // indicator of operating state this Ambi Climate mode is statically
        // mapped to COOL.
        // Temperature mode is mapped by comparing current temperature with
        // the set temperature.
        //
        this.client.mode(this.settings, function(err, data) {
            if (err) {
                callback(err);
            }
            var mode     = data.mode;
            var modeTemp = data.value;

            this.log("getCurrentHeatingCoolingState: Current Ambi Climate mode is " + mode);
            switch(mode) {
                case "Comfort":
                case "Away_Temperature_Upper":
                    this.log("getCurrentHeatingCoolingState: Current state is COOL");
                    callback(err, Characteristic.CurrentHeatingCoolingState.COOL);
                    break;
                case "Away_Temperature_Lower":
                    this.log("getCurrentHeatingCoolingState: Current state is HEAT");
                    callback(err, Characteristic.CurrentHeatingCoolingState.HEAT);
                    break;
                case "Temperature":
                    this.client.sensor_temperature(accessory.settings, function (err, data) {
                        if (err) {
                          callback(err);
                        }
                        var temp = data[0].value;

                        if (temp > modeTemp) {
                          this.log("getCurrentHeatingCoolingState: Current state is COOL");
                          callback(err, Characteristic.CurrentHeatingCoolingState.COOL);
                        } else {
                          this.log("getCurrentHeatingCoolingState: Current state is HEAT");
                          callback(err, Characteristic.CurrentHeatingCoolingState.HEAT);
                        }
                    });
                    break;
                default:
                    this.log("getCurrentHeatingCoolingState: Current state is OFF");
                    callback(err, Characteristic.CurrentHeatingCoolingState.OFF);
                    break;
            }
        }).bind(this)
    },

    //
    // Target Heating Cooling State has the additional enumeration of AUTO.
    // If the Ambi Climate is in the mode of Comfort or Temperature, the
    // Target Heating Cooling State is set to AUTO.
    //
    getTargetHeatingCoolingState: function(callback) {
      this.client.mode(this.settings, function(err, data) {
          if (err) {
              callback(err);
          }
          var mode     = data.mode;
          var modeTemp = data.value;

          switch(mode) {
              case "Comfort":
              case "Temperature":
                  this.log("getTargetHeatingCoolingState: Current state is AUTO");
                  callback(err, Characteristic.TargetHeatingCoolingState.AUTO);
                  break;
              case "Away_Temperature_Upper":
                  this.log("getTargetHeatingCoolingState: Current state is COOL");
                  callback(err, Characteristic.TargetHeatingCoolingState.COOL);
                  break;
              case "Away_Temperature_Lower":
                  this.log("getTargetHeatingCoolingState: Current state is HEAT");
                  callback(err, Characteristic.TargetHeatingCoolingState.HEAT);
                  break;
              default:
                  this.log("getTargetHeatingCoolingState: Current state is OFF");
                  callback(err, Characteristic.TargetHeatingCoolingState.OFF);
                  break;
          }
      }).bind(this)
    },

    //
    // AUTO -> Comfort
    // COOL -> Away Temperature Upper
    // HEAT -> Away Temperature Lower
    //
    setTargetHeatingCoolingState: function(value, callback) {
        switch(value) {
            case Characteristic.TargetHeatingCoolingState.AUTO:
                this.log("setTargetHeatingCoolingState: Setting to Comfort");
                this.client.comfort(this.settings, function(err, data) {
                    callback(err);
                });
                break;
            case Characteristic.TargetHeatingCoolingState.COOL:
                this.log("setTargetHeatingCoolingState: Setting to Away Temperature Upper");
                this.client.away_temperature_upper(this.settings, function(err, data) {
                    callback(err);
                });
                break;
            case Characteristic.TargetHeatingCoolingState.HEAT:
                this.log("setTargetHeatingCoolingState: Setting to Away Temperature Lower");
                this.client.away_temperature_lower(this.settings, function(err, data) {
                    callback(err);
                });
                break;
            default:
                this.log("setTargetHeatingCoolingState: Setting to Off");
                this.client.off(this.settings, function(err, data) {
                    callback(err);
                });
                break;
        }
    },

    getCurrentTemperature: function(callback) {
        this.client.sensor_temperature(this.settings, function (err, data) {
            (err) ? callback(err, null) : callback(err, data[0].value);
        });
    },

    getTargetTemperature: function(callback) {
      this.client.mode(this.settings, function(err, data) {
          if (err) {
              callback(err);
          }
          var mode     = data.mode;
          var modeTemp = data.value;

          switch(mode) {
              case "Temperature":
              case "Away_Temperature_Upper":
              case "Away_Temperature_Lower":
                  callback(null, modeTemp);
                  break;
              default:
                  callback(null, 22);
                  break;
          }
      })
    },

    //
    // Set the target temperature using the current Ambi Climate mode. A
    // target temperature is only relevant for Temperature, and Away
    // Temperature modes.
    //
    setTargetTemperature: function(value, callback) {
        this.client.mode(this.settings, function(err, data) {
            if (err) {
                callback(err);
            }
            var mode     = data.mode;

            this.client.settings.value = value;
            switch(mode) {
                case "Temperature":
                    this.client.temperature(this.client.settings, function(err,data) {
                        callback(err);
                    });
                    break;
                case "Away_Temperature_Upper":
                    this.client.away_temperature_upper(this.client.settings, function(err,data) {
                        callback(err);
                    });
                    break;
                case "Away_Temperature_Lower":
                    this.client.away_temperature_lower(this.client.settings, function(err,data) {
                        callback(err);
                    });
                    break;
                default:
                    callback(null);
                    break;
            }
        });
    },

    getTemperatureDisplayUnits: function(callback) {
        callback(null, Characteristic.TemperatureDisplayUnits.CELSIUS);
    },

    getCurrentRelativeHumidity: function(callback) {
        this.client.sensor_humidity(this.settings, function (err, data) {
            (err) ? callback(err) : callback(err, data[0].value);
        });
    },

    //
    // Services
    //
    getServices: function () {
        this.thermostatService.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
            .on('get', this.getCurrentHeatingCoolingState.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState)
            .on('get', this.getTargetHeatingCoolingState.bind(this))
            .on('set', this.setTargetHeatingCoolingState.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.CurrentTemperature)
            .on('get', this.getCurrentTemperature.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.TargetTemperature)
            .on('get', this.getTargetTemperature.bind(this))
            .on('set', this.setTargetTemperature.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits)
            .on('get', this.getTemperatureDisplayUnits.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.CurrentRelativeHumidity)
            .on('get', this.getCurrentRelativeHumidity.bind(this));

     		this.informationService
    			.setCharacteristic(Characteristic.Manufacturer, "Ambi Labs")
    			.setCharacteristic(Characteristic.Model, "Ambi Climate Thermostat")
    			.setCharacteristic(Characteristic.SerialNumber, " ");

        return [this.thermostatService];
    }
}
<commit_msg>Moved to promises<commit_after>/**
 * MIT License
 *
 * Copyright (c) 2017 Alisdair Smyth
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
**/
var ambi = require('node-ambiclimate');
var Service, Characteristic;

module.exports = function(homebridge) {
    Service        = homebridge.hap.Service;
    Characteristic = homebridge.hap.Characteristic;
    homebridge.registerAccessory("homebridge-ambiclimate-thermostat", "AmbiClimateThermostat", AmbiClimate);
}

function AmbiClimate(log, config) {
    this.log                    = log;
    this.name                   = config.name;
    this.settings               = {};
    this.settings.room_name     = config.roomName;
    this.settings.location_name = config.locationName;

    this.client = new ambi(config.clientId, config.clientSecret, config.username, config.password);

    // Object to maintain the state of the device.  This is done as there is
    // not official API to get this information.  If the Ambi Climate state is
    // changed through an alternative channel, that change will not be reflected
    // within Homekit
    this.state              = {};
    this.state.on           = false;

    this.thermostatService  = new Service.Thermostat(this.name);
    this.informationService = new Service.AccessoryInformation();
}

AmbiClimate.prototype = {
    getCurrentHeatingCoolingState: function(callback) {
        //
        // Map retrieved Ambi Climate mode to Homekit Heating, Cooling state.
        // Comfort mode can be heating or cooling, but as there is no
        // indicator of operating state this Ambi Climate mode is statically
        // mapped to COOL.
        // Temperature mode is mapped by comparing current temperature with
        // the set temperature.
        //
        this.client.mode(this.settings)
        .then(  (data) => {
          var mode     = data.mode;
          var modeTemp = data.value;

          this.log("getCurrentHeatingCoolingState: Current Ambi Climate mode is " + mode);
          switch(mode) {
              case "Comfort":
              case "Away_Temperature_Upper":
                  this.log("getCurrentHeatingCoolingState: Current state is COOL");
                  callback(null, Characteristic.CurrentHeatingCoolingState.COOL);
                  break;
              case "Away_Temperature_Lower":
                  this.log("getCurrentHeatingCoolingState: Current state is HEAT");
                  callback(null, Characteristic.CurrentHeatingCoolingState.HEAT);
                  break;
              case "Temperature":
                  this.client.sensor_temperature(this.settings)
                  .then( (data) => {
                      var temp = data[0].value;

                      if (temp > modeTemp) {
                        this.log("getCurrentHeatingCoolingState: Current state is COOL");
                        callback(null, Characteristic.CurrentHeatingCoolingState.COOL);
                      } else {
                        this.log("getCurrentHeatingCoolingState: Current state is HEAT");
                        callback(null, Characteristic.CurrentHeatingCoolingState.HEAT);
                      }
                  })
                  .catch ( (reason) => {
                      callback(reason);
                  })
                  break;
              default:
                  this.log("getCurrentHeatingCoolingState: Current state is OFF");
                  callback(null, Characteristic.CurrentHeatingCoolingState.OFF);
                  break;
          }
        })
        .catch( (reason) => {
            callback(reason);
        })
    },

    //
    // Target Heating Cooling State has the additional enumeration of AUTO.
    // If the Ambi Climate is in the mode of Comfort or Temperature, the
    // Target Heating Cooling State is set to AUTO.
    //
    getTargetHeatingCoolingState: function(callback) {
      this.client.mode(this.settings)
      .then( (data) => {
          var mode     = data.mode;
          var modeTemp = data.value;

          switch(mode) {
              case "Comfort":
              case "Temperature":
                  this.log("getTargetHeatingCoolingState: Current state is AUTO");
                  callback(null, Characteristic.TargetHeatingCoolingState.AUTO);
                  break;
              case "Away_Temperature_Upper":
                  this.log("getTargetHeatingCoolingState: Current state is COOL");
                  callback(null, Characteristic.TargetHeatingCoolingState.COOL);
                  break;
              case "Away_Temperature_Lower":
                  this.log("getTargetHeatingCoolingState: Current state is HEAT");
                  callback(null, Characteristic.TargetHeatingCoolingState.HEAT);
                  break;
              default:
                  this.log("getTargetHeatingCoolingState: Current state is OFF");
                  callback(null, Characteristic.TargetHeatingCoolingState.OFF);
                  break;
          }
      })
      .catch( (reason) => {
          callback(reason);
      })
    },

    //
    // AUTO -> Comfort
    // COOL -> Away Temperature Upper
    // HEAT -> Away Temperature Lower
    //
    setTargetHeatingCoolingState: function(value, callback) {
        switch(value) {
            case Characteristic.TargetHeatingCoolingState.AUTO:
                this.log("setTargetHeatingCoolingState: Setting to Comfort");
                this.client.comfort(this.settings)
                .catch( (reason) => {
                    callback(reason);
                });
                break;
            case Characteristic.TargetHeatingCoolingState.COOL:
                this.log("setTargetHeatingCoolingState: Setting to Away Temperature Upper");
                this.client.away_temperature_upper(this.settings)
                .catch( (reason) => {
                    callback(reason);
                });
                break;
            case Characteristic.TargetHeatingCoolingState.HEAT:
                this.log("setTargetHeatingCoolingState: Setting to Away Temperature Lower");
                this.client.away_temperature_lower(this.settings)
                .catch( (reason) => {
                    callback(reason);
                });
                break;
            default:
                this.log("setTargetHeatingCoolingState: Setting to Off");
                this.client.off(this.settings)
                .catch( (reason) => {
                    callback(reason);
                });
                break;
        }
    },

    getCurrentTemperature: function(callback) {
        this.client.sensor_temperature(this.settings)
        .then( (data) => {
            callback(null, data[0].value)
        })
        .catch( (reason) => {
            callback(reason)
        });
    },

    getTargetTemperature: function(callback) {
      this.client.mode(this.settings)
      .then( (data) => {
          var mode     = data.mode;
          var modeTemp = data.value;

          switch(mode) {
              case "Temperature":
              case "Away_Temperature_Upper":
              case "Away_Temperature_Lower":
                  callback(null, modeTemp);
                  break;
              default:
                  callback(null, 22);
                  break;
          }
      })
      .catch( (reason) => {
          callback(reason);
      });
    },

    //
    // Set the target temperature using the current Ambi Climate mode. A
    // target temperature is only relevant for Temperature, and Away
    // Temperature modes.
    //
    setTargetTemperature: function(value, callback) {
        this.client.mode(this.settings)
        .then( (data) => {
          var mode     = data.mode;

          this.client.settings.value = value;
          switch(mode) {
              case "Temperature":
                  this.client.temperature(this.client.settings)
                  .catch( (reason) => {
                    callback(reason);
                  });
                  break;
              case "Away_Temperature_Upper":
                  this.client.away_temperature_upper(this.client.settings)
                  .catch( (reason) => {
                    callback(reason);
                  });
                  break;
              case "Away_Temperature_Lower":
                  this.client.away_temperature_lower(this.client.settings)
                  .catch( (reason) => {
                    callback(reason);
                  });
                  break;
              default:
                  callback(null);
                  break;
          }

        })
        .catch( (reason) => {
          callback(reason);
        });
    },

    getTemperatureDisplayUnits: function(callback) {
        callback(null, Characteristic.TemperatureDisplayUnits.CELSIUS);
    },

    getCurrentRelativeHumidity: function(callback) {
        this.client.sensor_humidity(this.settings)
        .then( (data) => {
          callback(null, data[0].value);
        })
        .catch ( (reason) => {
          callback(reason);
        });
    },

    //
    // Services
    //
    getServices: function () {
        this.thermostatService.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
            .on('get', this.getCurrentHeatingCoolingState.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState)
            .on('get', this.getTargetHeatingCoolingState.bind(this))
            .on('set', this.setTargetHeatingCoolingState.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.CurrentTemperature)
            .on('get', this.getCurrentTemperature.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.TargetTemperature)
            .on('get', this.getTargetTemperature.bind(this))
            .on('set', this.setTargetTemperature.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits)
            .on('get', this.getTemperatureDisplayUnits.bind(this));

        this.thermostatService.getCharacteristic(Characteristic.CurrentRelativeHumidity)
            .on('get', this.getCurrentRelativeHumidity.bind(this));

     		this.informationService
    			.setCharacteristic(Characteristic.Manufacturer, "Ambi Labs")
    			.setCharacteristic(Characteristic.Model, "Ambi Climate Thermostat")
    			.setCharacteristic(Characteristic.SerialNumber, " ");

        return [this.thermostatService];
    }
}
<|endoftext|>


<commit_before>'use strict';

require('colors');
var spawn = require('child_process').spawn;
var path = require('path');
var fs = require('fs');
var Q = require('q');

var yargs = require('yargs');
var argv = yargs
            .usage('Usage: $0 [-h|--help] [-p|--protractor-bin] [-t|--timeout] [-r|--retry-pause] [-m|--max-retries] [-f|--filter] [-v|--verbose [-v|--verbose]] -- your.js --protractor --args')
            .alias('p', 'protractor-bin')
            .default('protractor-bin', 'node_modules/protractor/bin/protractor')
            .count('verbose')
            .boolean('verbose')
            .alias('v', 'verbose')
            .alias('t', 'timeout')
            .default('timeout', 0)
            .alias('r', 'retry-pause')
            .default('retry-pause', 0)
            .alias('m', 'max-retries')
            .default('max-retries', 3)
            .alias('f', 'filter')
            .default('filter', null)
            .help('help')
            .alias('h', 'help')
            .argv;

var LOG_LEVEL = argv.verbose;
var PROTRACTOR_BIN = path.resolve(argv['protractor-bin']);
var RETRY_FILE = path.resolve('.protractor-retry-specs');

if(argv._.length <= 0) {
    console.log(yargs.help());
    process.exit(4);
}

function DEBUG() { if(LOG_LEVEL < 2) return; for(var i = 0; i < arguments.length; i++) { console.log('DEBUG\t'.cyan, arguments[i].toString()); } }
function INFO() { if(LOG_LEVEL < 1) return; for(var i = 0; i < arguments.length; i++) { console.log('INFO\t'.green, arguments[i].toString()); } }
function ERROR() { for(var i = 0; i < arguments.length; i++) { console.log('ERROR\t'.red, arguments[i].toString()); } }

var protractorArgs = argv._.concat(['--params.isRetryRun', 'true']);
var maxRetries = argv['max-retries'];

var filter;
if(argv.filter) {
    filter = require(path.resolve(argv.filter));
}

function parseOutput(stdout, stderr) {
    var outLines = stdout.toString().trim().split('\n');
    var errLines = stderr.toString().trim().split('\n');

    var failuresMarker = /^\*[\s]+Failures[\s]+\*/;
    var failedSpecMarker = /^[0-9]+\)[\s]+(.*)$/;
    var othersMarker = /^\*[\s]+[\w]+[\s]+\*/;

    var failedSpecs = [];

    if(outLines.length <= 0) {
        ERROR('No protractor output found');
        return null;
    }

    // For whatever reason there's always one empty error line...
    if(errLines.length > 1) {
        ERROR('There were errors on STDERR');
        errLines.forEach(function(err) {
            ERROR(err);
        });
        return null;
    }

    for(var i = 0; i < outLines.length; i++) {
        var line = outLines[i].trim();

        if(line.match(failuresMarker)) {
            DEBUG('Found failures marker at line ' + i + ':', line);

            for(i++; i < outLines.length; i++) {
                line = outLines[i].trim();

                if(line.match(othersMarker)) {
                    DEBUG('Found marker stopping the failure parsing:', line);
                    break;
                }

                var failedSpecData = line.match(failedSpecMarker);

                if(failedSpecData) {
                    var failedSpecName = failedSpecData[1];
                    INFO('Found failed spec: "' + failedSpecName + '"');
                    failedSpecs.push(failedSpecName);
                }
            }
        }
    }

    return failedSpecs;
}

function runProtractor() {
    if(!fs.existsSync(PROTRACTOR_BIN)) {
        ERROR('Could not find protractor binary at "' + PROTRACTOR_BIN + '"');
        process.exit(1);
        return;
    }

    var deferred = Q.defer();

    var child = spawn(PROTRACTOR_BIN, protractorArgs);
    var killTimeout;

    var stdout = [];
    var stderr = [];

    if(argv.timeout > 0) {
        killTimeout = setTimeout(function() {
            killTimeout = null;
            child.kill();
        }, argv.timeout * 1000);
    }

    child.stdout.on('data', function(data) {
        process.stdout.write(data.toString());
        stdout.push(data);
    });

    child.stderr.on('data', function(data) {
        process.stderr.write(data.toString());
        stderr.push(data);
    });

    child.on('exit', function(exitCode) {
        INFO('Protractor is done');
        DEBUG('Child exited with exitCode ' + exitCode);

        if(killTimeout) {
            DEBUG('Clearing kill timeout');
            clearTimeout(killTimeout);
        } else if(killTimeout === null) {
            ERROR('Protractor runtime exceeded the timeout of ' + argv.timeout + ' seconds and had to be killed.');
            deferred.fulfill(null);
            return;
        }

        if(exitCode !== 0 && exitCode !== 1) {
            deferred.fulfill(null);
        }

        deferred.fulfill(parseOutput(Buffer.concat(stdout), Buffer.concat(stderr)));
    });

    return deferred.promise;
}

function runTests() {
    maxRetries--;
    if(maxRetries < 0) {
        ERROR('Maximum number of retries (' + argv['max-retries'] + ') exceeded without success');
        DEBUG('Deleting ' + RETRY_FILE);
        fs.unlinkSync(RETRY_FILE);
        process.exit(2);
    }

    var nextRun = (argv['max-retries'] - maxRetries);

    var prerunPromise = Q();
    var failedSpecsInfo;

    if(filter && filter.prerun) {
        INFO('Running pre-run filter');
        var res = filter.prerun(nextRun);
        if(res && res.then) {
            prerunPromise = res;
        } else {
            prerunPromise = Q.when(res);
        }
    }

    prerunPromise.then(function(prerun) {
        INFO('Doing Protractor run #' + nextRun);
        return runProtractor();
    }).then(function(failedSpecs) {
        if(failedSpecs === null) {
            INFO('There was an error parsing the Protractor output. Retrying...');
        } else {
            INFO('Identified ' + failedSpecs.length + ' failed specs which will be retried');
        }

        failedSpecsInfo = failedSpecs;

        var postrunPromise = Q();
        if(filter && filter.postrun) {
            INFO('Running post-run filter');
            var res = filter.postrun(nextRun, failedSpecs);
            if(res && res.then) {
                postrunPromise = res;
            } else {
                postrunPromise = Q.when(res);
            }
        }

        return postrunPromise;
    }).then(function(postrun) {
        if(failedSpecsInfo.length === 0) {
            DEBUG('No failed specs found, Protractor run was successfull');
            DEBUG('Deleting ' + RETRY_FILE);
            fs.unlinkSync(RETRY_FILE);
            return;
        }

        DEBUG('Writing retry file at ' + RETRY_FILE);
        fs.writeFileSync(RETRY_FILE, failedSpecsInfo.join('\n'));

        if(argv['retry-pause'] > 0) {
            INFO('Waiting for ' + argv['retry-pause'] + ' seconds before the next run');
        }

        return Q.delay(argv['retry-pause']);
    }).then(function() {
        INFO('Retrying failed specs...');
        return runTests();
    }, function(err) {
        ERROR('An error was thrown');
        ERROR(err.stack);
        DEBUG('Deleting ' + RETRY_FILE);
        fs.unlinkSync(RETRY_FILE);
        process.exit(3);
    });
}

// This won't be in the same process as the above code!
function installJasmineSpecFilter() {
    var originalFilter = jasmine.getEnv().specFilter;
    var retrySpecs;
    try {
        retrySpecs = fs.readFileSync(RETRY_FILE).toString().split('\n');
    } catch(e) {
        // No such file
        if(browser.params.isRetryRun) {
            console.log('protractor-retry: No retrySpecs file found at ' + RETRY_FILE + ', allowing all specs');
        }
    }

    // This only enables specs which are listed in the RETRY_FILE file
    jasmine.getEnv().specFilter = function(spec) {
        if(!browser.params.isRetryRun || retrySpecs === undefined) {
            return originalFilter ? originalFilter(spec) : true;
        }

        for(var i = 0; i < retrySpecs.length; i++) {
            if(retrySpecs[i].toLowerCase() === spec.getFullName().toLowerCase()) {
                return originalFilter ? originalFilter(spec) : true;
            }
        }

        // console.log('protractor-retry: Removing spec ' + spec.getFullName() + ' from the list of active specs');

        return false;
    };
}

module.exports = {
    runWithRetry: runTests,
    installSpecFilter: installJasmineSpecFilter
};

<commit_msg>Fix failedSpecs functionality<commit_after>'use strict';

require('colors');
var spawn = require('child_process').spawn;
var path = require('path');
var fs = require('fs');
var Q = require('q');

var yargs = require('yargs');
var argv = yargs
            .usage('Usage: $0 [-h|--help] [-p|--protractor-bin] [-t|--timeout] [-r|--retry-pause] [-m|--max-retries] [-f|--filter] [-v|--verbose [-v|--verbose]] -- your.js --protractor --args')
            .alias('p', 'protractor-bin')
            .default('protractor-bin', 'node_modules/protractor/bin/protractor')
            .count('verbose')
            .boolean('verbose')
            .alias('v', 'verbose')
            .alias('t', 'timeout')
            .default('timeout', 0)
            .alias('r', 'retry-pause')
            .default('retry-pause', 0)
            .alias('m', 'max-retries')
            .default('max-retries', 3)
            .alias('f', 'filter')
            .default('filter', null)
            .help('help')
            .alias('h', 'help')
            .argv;

var LOG_LEVEL = argv.verbose;
var PROTRACTOR_BIN = path.resolve(argv['protractor-bin']);
var RETRY_FILE = path.resolve('.protractor-retry-specs');

if(argv._.length <= 0) {
    console.log(yargs.help());
    process.exit(4);
}

function DEBUG() { if(LOG_LEVEL < 2) return; for(var i = 0; i < arguments.length; i++) { console.log('DEBUG\t'.cyan, arguments[i].toString()); } }
function INFO() { if(LOG_LEVEL < 1) return; for(var i = 0; i < arguments.length; i++) { console.log('INFO\t'.green, arguments[i].toString()); } }
function ERROR() { for(var i = 0; i < arguments.length; i++) { console.log('ERROR\t'.red, arguments[i].toString()); } }

var protractorArgs = argv._.concat(['--params.isRetryRun', 'true']);
var maxRetries = argv['max-retries'];

var filter;
if(argv.filter) {
    filter = require(path.resolve(argv.filter));
}

function parseOutput(stdout, stderr) {
    var outLines = stdout.toString().trim().split('\n');
    var errLines = stderr.toString().trim().split('\n');

    var failuresMarker = /^\*[\s]+Failures[\s]+\*/;
    var failedSpecMarker = /^[0-9]+\)[\s]+(.*)$/;
    var othersMarker = /^\*[\s]+[\w]+[\s]+\*/;

    var failedSpecs = [];

    if(outLines.length <= 0) {
        ERROR('No protractor output found');
        return null;
    }

    // For whatever reason there's always one empty error line...
    if(errLines.length > 1) {
        ERROR('There were errors on STDERR');
        errLines.forEach(function(err) {
            ERROR(err);
        });
        return null;
    }

    for(var i = 0; i < outLines.length; i++) {
        var line = outLines[i].trim();

        if(line.match(failuresMarker)) {
            DEBUG('Found failures marker at line ' + i + ':', line);

            for(i++; i < outLines.length; i++) {
                line = outLines[i].trim();

                if(line.match(othersMarker)) {
                    DEBUG('Found marker stopping the failure parsing:', line);
                    break;
                }

                var failedSpecData = line.match(failedSpecMarker);

                if(failedSpecData) {
                    var failedSpecName = failedSpecData[1];
                    INFO('Found failed spec: "' + failedSpecName + '"');
                    failedSpecs.push(failedSpecName);
                }
            }
        }
    }

    return failedSpecs;
}

function runProtractor() {
    if(!fs.existsSync(PROTRACTOR_BIN)) {
        ERROR('Could not find protractor binary at "' + PROTRACTOR_BIN + '"');
        process.exit(1);
        return;
    }

    var deferred = Q.defer();

    var child = spawn(PROTRACTOR_BIN, protractorArgs);
    var killTimeout;

    var stdout = [];
    var stderr = [];

    if(argv.timeout > 0) {
        killTimeout = setTimeout(function() {
            killTimeout = null;
            child.kill();
        }, argv.timeout * 1000);
    }

    child.stdout.on('data', function(data) {
        process.stdout.write(data.toString());
        stdout.push(data);
    });

    child.stderr.on('data', function(data) {
        process.stderr.write(data.toString());
        stderr.push(data);
    });

    child.on('exit', function(exitCode) {
        INFO('Protractor is done');
        DEBUG('Child exited with exitCode ' + exitCode);

        if(killTimeout) {
            DEBUG('Clearing kill timeout');
            clearTimeout(killTimeout);
        } else if(killTimeout === null) {
            ERROR('Protractor runtime exceeded the timeout of ' + argv.timeout + ' seconds and had to be killed.');
            deferred.fulfill(null);
            return;
        }

        if(exitCode !== 0 && exitCode !== 1) {
            deferred.fulfill(null);
        }

        deferred.fulfill(parseOutput(Buffer.concat(stdout), Buffer.concat(stderr)));
    });

    return deferred.promise;
}

function runTests() {
    maxRetries--;
    if(maxRetries < 0) {
        ERROR('Maximum number of retries (' + argv['max-retries'] + ') exceeded without success');
        DEBUG('Deleting ' + RETRY_FILE);
        fs.unlinkSync(RETRY_FILE);
        process.exit(2);
    }

    var nextRun = (argv['max-retries'] - maxRetries);

    var prerunPromise = Q();

    if(filter && filter.prerun) {
        INFO('Running pre-run filter');
        var res = filter.prerun(nextRun);
        if(res && res.then) {
            prerunPromise = res;
        } else {
            prerunPromise = Q.when(res);
        }
    }

    prerunPromise.then(function(prerun) {
        INFO('Doing Protractor run #' + nextRun);
        return runProtractor();
    }).then(function(failedSpecs) {
        if(failedSpecs === null) {
            INFO('There was an error parsing the Protractor output. Retrying...');
        } else {
            INFO('Identified ' + failedSpecs.length + ' failed specs which will be retried');
        }

        var postrunPromise = Q();
        if(filter && filter.postrun) {
            INFO('Running post-run filter');
            var res = filter.postrun(nextRun, failedSpecs);
            if(res && res.then) {
                postrunPromise = res;
            } else {
                postrunPromise = Q.when(res);
            }
        }

        return postrunPromise.then(function(postrun) {
            if(failedSpecs.length === 0) {
                DEBUG('No failed specs found, Protractor run was successfull');
                DEBUG('Deleting ' + RETRY_FILE);
                fs.unlinkSync(RETRY_FILE);
                return false;
            }

            DEBUG('Writing retry file at ' + RETRY_FILE);
            fs.writeFileSync(RETRY_FILE, failedSpecs.join('\n'));

            if(argv['retry-pause'] > 0) {
                INFO('Waiting for ' + argv['retry-pause'] + ' seconds before the next run');
            }

            return Q.delay(argv['retry-pause']).then(function() { return true; });
        };
    }).then(function(retry) {
        if(retry) {
            INFO('Retrying failed specs...');
            return runTests();
        }
    }, function(err) {
        ERROR('An error was thrown');
        ERROR(err.stack);
        DEBUG('Deleting ' + RETRY_FILE);
        fs.unlinkSync(RETRY_FILE);
        process.exit(3);
    });
}

// This won't be in the same process as the above code!
function installJasmineSpecFilter() {
    var originalFilter = jasmine.getEnv().specFilter;
    var retrySpecs;
    try {
        retrySpecs = fs.readFileSync(RETRY_FILE).toString().split('\n');
    } catch(e) {
        // No such file
        if(browser.params.isRetryRun) {
            console.log('protractor-retry: No retrySpecs file found at ' + RETRY_FILE + ', allowing all specs');
        }
    }

    // This only enables specs which are listed in the RETRY_FILE file
    jasmine.getEnv().specFilter = function(spec) {
        if(!browser.params.isRetryRun || retrySpecs === undefined) {
            return originalFilter ? originalFilter(spec) : true;
        }

        for(var i = 0; i < retrySpecs.length; i++) {
            if(retrySpecs[i].toLowerCase() === spec.getFullName().toLowerCase()) {
                return originalFilter ? originalFilter(spec) : true;
            }
        }

        // console.log('protractor-retry: Removing spec ' + spec.getFullName() + ' from the list of active specs');

        return false;
    };
}

module.exports = {
    runWithRetry: runTests,
    installSpecFilter: installJasmineSpecFilter
};

<|endoftext|>


<commit_before>var arrGroup = function(arr, isNextFunction, returnSingleElements) {
  if(!returnSingleElements) returnSingleElements = true; // optional argument

  var rangeGroups  = []
    , checkBuffer  = []
    , previousItem
    , isLastItem   = false;

  arr.forEach(function(currentItem, index) {

    var flushBuffer = function() {
      rangeGroups.push(checkBuffer.map(singleRecordToElement));
      checkBuffer = [];
    }

    if(!isNextFunction(currentItem, previousItem)
       && previousItem) {
      flushBuffer();
    }

    checkBuffer.push(currentItem);

    isLastItem = (index == (arr.length-1));
    if(isLastItem) {
      flushBuffer();
    }

    previousItem = currentItem;
    return;
  });

  var rangeGroupsTrans = rangeGroups;
  if(returnSingleElements) {
    rangeGroupsTrans = rangeGroups.map(singleRecordToElement);
  }

  return rangeGroupsTrans;
};
var singleRecordToElement = function(element) {
  if(element.length == 1) {
    return element[0];
  }
  return element;
};

module.exports  = arrGroup;
<commit_msg>Fix returnSingleElements argument.<commit_after>var arrGroup = function(arr, isNextFunction, returnSingleElements) {
  if(typeof returnSingleElements == 'undefined') { // optional argument
    returnSingleElements = true;
  }

  var rangeGroups  = []
    , checkBuffer  = []
    , previousItem
    , isLastItem   = false;

  arr.forEach(function(currentItem, index) {

    var flushBuffer = function() {
      rangeGroups.push(checkBuffer.map(singleRecordToElement));
      checkBuffer = [];
    }

    if(!isNextFunction(currentItem, previousItem)
       && previousItem) {
      flushBuffer();
    }

    checkBuffer.push(currentItem);

    isLastItem = (index == (arr.length-1));
    if(isLastItem) {
      flushBuffer();
    }

    previousItem = currentItem;
    return;
  });

  var rangeGroupsTrans = rangeGroups;
  if(returnSingleElements) {
    rangeGroupsTrans = rangeGroups.map(singleRecordToElement);
  }

  return rangeGroupsTrans;
};
var singleRecordToElement = function(element) {
  if(element.length == 1) {
    return element[0];
  }
  return element;
};

module.exports  = arrGroup;
<|endoftext|>


<commit_before>var request = require('request');

function Tumblr(credentials) {
  this.credentials = credentials;
}

module.exports = Tumblr;

// TODO: Check option validity (arrays of expected option values)

// Blogs

Tumblr.prototype.blogInfo = function (blogName, callback) {
  blogRequest('/info', blogName, {}, callback, this.credentials);
};

Tumblr.prototype.avatar = function (options, callback) {
  // TODO
};

Tumblr.prototype.followers = function (blogName, options, callback) {
  blogRequest('/followers', blogName, options, callback, this.credentials);
};

Tumblr.prototype.posts = function (blogName, options, callback) {
  blogRequest('/posts', blogName, options, callback, this.credentials);
};

Tumblr.prototype.queue = function (blogName, options, callback) {
  blogRequest('/posts/queue', blogName, options, callback, this.credentials);
};

Tumblr.prototype.drafts = function (blogName, options, callback) {
  blogRequest('/posts/draft', blogName, options, callback, this.credentials);
};

Tumblr.prototype.submissions = function (blogName, options, callback) {
  blogRequest('/posts/submission', blogName, options, callback, this.credentials);
};

// Posts

Tumblr.prototype.edit = function (options) {
  // TODO
};

Tumblr.prototype.reblog = function (options) {
  // TODO
};

Tumblr.prototype.delete = function (options) {
  // TODO
};

Tumblr.prototype.photo = function (options) {
  options = options || {};
  options.type = 'photo';

  // TODO
};

Tumblr.prototype.quote = function (options) {
  options = options || {};
  options.type = 'quote';

  // TODO
};

Tumblr.prototype.text = function (options) {
  options = options || {};
  options.type = 'text';

  // TODO
};

Tumblr.prototype.link = function (options) {
  options = options || {};
  options.type = 'link';

  // TODO
};

Tumblr.prototype.chat = function (options) {
  options = options || {};
  options.type = 'chat';

  // TODO
};

Tumblr.prototype.audio = function (options) {
  options = options || {};
  options.type = 'audio';

  // TODO
};

Tumblr.prototype.video = function (options) {
  options = options || {};
  options.type = 'video';

  // TODO
};

// User

Tumblr.prototype.userInfo = function (callback) {
  get('/user/info', {}, callback, this.credentials);
};

Tumblr.prototype.dashboard = function (options, callback) {
  get('/user/dashboard', options, callback, this.credentials);
};

Tumblr.prototype.likes = function (offset, limit, callback) {
  get('/user/likes', {offset: offset || 0, limit: limit || 20}, callback, this.credentials);
};

Tumblr.prototype.following = function (offset, limit, callback) {
  get('/user/following', {offset: offset || 0, limit: limit || 20}, callback, this.credentials);
};

Tumblr.prototype.follow = function (blogName, callback) {
  post('/user/follow', {url: blogURL(blogName)}, callback, this.credentials);
};

Tumblr.prototype.unfollow = function (blogName, callback) {
  post('/user/unfollow', {url: blogURL(blogName)}, callback, this.credentials);
};

Tumblr.prototype.like = function (id, reblogKey, callback) {
  // TODO
};

Tumblr.prototype.unlike = function (id, reblogKey, callback) {
  // TODO
};

// Helpers

var blogRequest = function (path, blogName, options, callback, credentials) {
  options = options || {};
  options.api_key = credentials.consumer_key;

  get(blogURLPath(blogName, path), options, callback, credentials);
};

var blogURL = function (blogName) {
  return blogName + '.tumblr.com';
}

var blogURLPath = function (blogName, path) {
  return '/blog/' + blogURL(blogName) + path;
};

var baseURL = 'http://api.tumblr.com/v2';

var get = function (path, params, callback, oauth) {
  // TODO: Be smarter about handling which arguments are passed
  request.get({url: baseURL + path, qs: params, oauth: oauth}, requestCallback(callback));
};

var post = function (path, params, callback, oauth) {
  // TODO: Be smarter about handling which arguments are passed
  request.post({url: baseURL + path, form: params, oauth: oauth}, requestCallback(callback));
};

var requestCallback = function (callback) {
  if (!callback) {
    // If callback has not been provided, log the response (particularly useful in the REPL)
    callback = function (response) {
      console.log(JSON.stringify(response, null, 2));
    };
  }

  return function (err, response, body) {
    if (err)
      return callback('Unknown error');

    var responseBody = JSON.parse(body)
      , statusCode = responseBody.meta.status;

    if (statusCode != 200)
      return callback('API error: ' + statusCode);

    return callback(responseBody.response);
  };
};<commit_msg>Support for liking and unliking<commit_after>var request = require('request');

function Tumblr(credentials) {
  this.credentials = credentials;
}

module.exports = Tumblr;

// TODO: Check option validity (arrays of expected option values)

// Blogs

Tumblr.prototype.blogInfo = function (blogName, callback) {
  blogRequest('/info', blogName, {}, callback, this.credentials);
};

Tumblr.prototype.avatar = function (options, callback) {
  // TODO
};

Tumblr.prototype.followers = function (blogName, options, callback) {
  blogRequest('/followers', blogName, options, callback, this.credentials);
};

Tumblr.prototype.posts = function (blogName, options, callback) {
  blogRequest('/posts', blogName, options, callback, this.credentials);
};

Tumblr.prototype.queue = function (blogName, options, callback) {
  blogRequest('/posts/queue', blogName, options, callback, this.credentials);
};

Tumblr.prototype.drafts = function (blogName, options, callback) {
  blogRequest('/posts/draft', blogName, options, callback, this.credentials);
};

Tumblr.prototype.submissions = function (blogName, options, callback) {
  blogRequest('/posts/submission', blogName, options, callback, this.credentials);
};

// Posts

Tumblr.prototype.edit = function (options) {
  // TODO
};

Tumblr.prototype.reblog = function (options) {
  // TODO
};

Tumblr.prototype.delete = function (options) {
  // TODO
};

Tumblr.prototype.photo = function (options) {
  options = options || {};
  options.type = 'photo';

  // TODO
};

Tumblr.prototype.quote = function (options) {
  options = options || {};
  options.type = 'quote';

  // TODO
};

Tumblr.prototype.text = function (options) {
  options = options || {};
  options.type = 'text';

  // TODO
};

Tumblr.prototype.link = function (options) {
  options = options || {};
  options.type = 'link';

  // TODO
};

Tumblr.prototype.chat = function (options) {
  options = options || {};
  options.type = 'chat';

  // TODO
};

Tumblr.prototype.audio = function (options) {
  options = options || {};
  options.type = 'audio';

  // TODO
};

Tumblr.prototype.video = function (options) {
  options = options || {};
  options.type = 'video';

  // TODO
};

// User

Tumblr.prototype.userInfo = function (callback) {
  get('/user/info', {}, callback, this.credentials);
};

Tumblr.prototype.dashboard = function (options, callback) {
  get('/user/dashboard', options, callback, this.credentials);
};

Tumblr.prototype.likes = function (offset, limit, callback) {
  get('/user/likes', {offset: offset || 0, limit: limit || 20}, callback, this.credentials);
};

Tumblr.prototype.following = function (offset, limit, callback) {
  get('/user/following', {offset: offset || 0, limit: limit || 20}, callback, this.credentials);
};

Tumblr.prototype.follow = function (blogName, callback) {
  post('/user/follow', {url: blogURL(blogName)}, callback, this.credentials);
};

Tumblr.prototype.unfollow = function (blogName, callback) {
  post('/user/unfollow', {url: blogURL(blogName)}, callback, this.credentials);
};

Tumblr.prototype.like = function (id, reblogKey, callback) {
  post('/user/like', {id: id, reblog_key: reblogKey}, callback, this.credentials);
};

Tumblr.prototype.unlike = function (id, reblogKey, callback) {
  post('/user/unlike', {id: id, reblog_key: reblogKey}, callback, this.credentials);
};

// Helpers

var blogRequest = function (path, blogName, options, callback, credentials) {
  options = options || {};
  options.api_key = credentials.consumer_key;

  get(blogURLPath(blogName, path), options, callback, credentials);
};

var blogURL = function (blogName) {
  return blogName + '.tumblr.com';
}

var blogURLPath = function (blogName, path) {
  return '/blog/' + blogURL(blogName) + path;
};

var baseURL = 'http://api.tumblr.com/v2';

var get = function (path, params, callback, oauth) {
  // TODO: Be smarter about handling which arguments are passed
  request.get({url: baseURL + path, qs: params, oauth: oauth}, requestCallback(callback));
};

var post = function (path, params, callback, oauth) {
  // TODO: Be smarter about handling which arguments are passed
  request.post({url: baseURL + path, form: params, oauth: oauth}, requestCallback(callback));
};

var requestCallback = function (callback) {
  if (!callback) {
    // If callback has not been provided, log the response (particularly useful in the REPL)
    callback = function (response) {
      console.log(JSON.stringify(response, null, 2));
    };
  }

  return function (err, response, body) {
    if (err)
      return callback('Unknown error');

    var responseBody = JSON.parse(body)
      , statusCode = responseBody.meta.status;

    if (statusCode != 200)
      return callback('API error: ' + statusCode);

    return callback(responseBody.response);
  };
};<|endoftext|>


<commit_before>var through = require('through2'),
    gutil = require('gulp-util'),
    rasterize = require('./lib'),
    PluginError = gutil.PluginError;

const PLUGIN_NAME = 'gulp-raster';

module.exports = function (opt) {
    'use strict';

    opt = opt || {};
    opt.scale = opt.scale || 1;
    opt.format = opt.format || 'png';

    function raster(file, enc, callback) {
        var that = this;

        // Do nothing if no contents
        if (file.isNull()) { return callback(); }

        if (file.isBuffer()) {
            rasterize(file.path, opt.format, opt.scale, function (err, data) {
                if (err) { throw new PluginError(PLUGIN_NAME, 'Error occured during file convertation'); }

                file.contents = data;
                that.push(file);
                return callback();
            });
        }
    }

    return through.obj(raster);
};
<commit_msg>fix typo<commit_after>var through = require('through2'),
    gutil = require('gulp-util'),
    rasterize = require('./lib'),
    PluginError = gutil.PluginError;

const PLUGIN_NAME = 'gulp-raster';

module.exports = function (opt) {
    'use strict';

    opt = opt || {};
    opt.scale = opt.scale || 1;
    opt.format = opt.format || 'png';

    function raster(file, enc, callback) {
        var that = this;

        // Do nothing if no contents
        if (file.isNull()) { return callback(); }

        if (file.isBuffer()) {
            rasterize(file.path, opt.format, opt.scale, function (err, data) {
                if (err) { throw new PluginError(PLUGIN_NAME, 'Error occured during file conversion'); }

                file.contents = data;
                that.push(file);
                return callback();
            });
        }
    }

    return through.obj(raster);
};
<|endoftext|>


<commit_before>var stream  = require('stream');
var   util  = require('util');
var buffers = require('buffers');
var assert  = require('assert');

// creates new shaped trough stream
var ShapeStream = function (byteRate, chunkRate, lowWatermark, highWatermark) {
    this.rate = byteRate; // float number!
    if (!chunkRate)
        chunkRate = 10;
    if (!lowWatermark)
        lowWatermark = 0;
    if (!highWatermark)
        highWatermark = 0;

    this.checkInterval = 1000/chunkRate;
    this.buffer = buffers();
    this.startTime = Date.now();
    this.totalWritten = 0;
    this.writable = true;
    this.readable = true;
    this.sendTimer = null;
}
util.inherits(ShapeStream, stream.Stream);

ShapeStream.prototype.expectedWritten = function() {
    return parseInt(this.rate*(Date.now() - this.startTime)/1000);
}

ShapeStream.prototype.processBuffer = function() {
    var expected = this.expectedWritten();
    if (expected > this.totalWritten) {
      var lengthToWrite = expected - this.totalWritten;
      var chunk = this.buffer.splice(0, lengthToWrite);
      this.doWrite(chunk.toBuffer());
    }
    if (buffer.lengh <= this.lowWatermark && !this.stopping)
        this.emit('drain');

    if (this.buffer.length == 0) {
        if (this.stopping)
           this.emit('close');
    } else
        this.sendTimer = setTimeout(this.processBuffer.bind(this), this.checkInterval);
}

ShapeStream.prototype.doWrite = function(chunk)
{
    this.totalWritten += chunk.length;
    this.emit('data', chunk);
}

ShapeStream.prototype.write = function(chunk, encoding) {
    if (typeof chunk === 'string')
        chunk = Buffer(chunk, encoding);
    var expected = this.expectedWritten();
    if (this.totalWritten + chunk.length < this.expectedWritten() && !this.paused)
    {
        this.doWrite(chunk);
        return true;
    }
    this.buffer.push(chunk);
    if (!this.paused)
        this.processBuffer();
    return this.buffer.length < this.highWatermark;
};

ShapeStream.prototype.end = function(chunk, encoding) {
    this.stopping = true;
    if (chunk)
        this.write(chunk, encoding);
    else
        this.processBuffer();
};

ShapeStream.prototype.pause = function() {
    this.pauseCount++;
    this.paused = true;
};

ShapeStream.prototype.resume = function() {
    this.pauseCount--;
    if (this.pauseCount == 0) {
        this.paused = false;
        this.processBuffer();
    }
};

ShapeStream.prototype.destroy = function() {
    // do nothing
}

ShapeStream.prototype.destroySoon = function() {
    // do nothing
}

module.exports = function(byteRate, chunkRate) {
   return new ShapeStream(byteRate, chunkRate);
}
<commit_msg>missed `this.`<commit_after>var stream  = require('stream');
var   util  = require('util');
var buffers = require('buffers');
var assert  = require('assert');

// creates new shaped trough stream
var ShapeStream = function (byteRate, chunkRate, lowWatermark, highWatermark) {
    this.rate = byteRate; // float number!
    if (!chunkRate)
        chunkRate = 10;
    if (!lowWatermark)
        lowWatermark = 0;
    if (!highWatermark)
        highWatermark = 0;

    this.checkInterval = 1000/chunkRate;
    this.buffer = buffers();
    this.startTime = Date.now();
    this.totalWritten = 0;
    this.writable = true;
    this.readable = true;
    this.sendTimer = null;
}
util.inherits(ShapeStream, stream.Stream);

ShapeStream.prototype.expectedWritten = function() {
    return parseInt(this.rate*(Date.now() - this.startTime)/1000);
}

ShapeStream.prototype.processBuffer = function() {
    var expected = this.expectedWritten();
    if (expected > this.totalWritten) {
      var lengthToWrite = expected - this.totalWritten;
      var chunk = this.buffer.splice(0, lengthToWrite);
      this.doWrite(chunk.toBuffer());
    }
    if (this.buffer.lengh <= this.lowWatermark && !this.stopping)
        this.emit('drain');

    if (this.buffer.length == 0) {
        if (this.stopping)
           this.emit('close');
    } else
        this.sendTimer = setTimeout(this.processBuffer.bind(this), this.checkInterval);
}

ShapeStream.prototype.doWrite = function(chunk)
{
    this.totalWritten += chunk.length;
    this.emit('data', chunk);
}

ShapeStream.prototype.write = function(chunk, encoding) {
    if (typeof chunk === 'string')
        chunk = Buffer(chunk, encoding);
    var expected = this.expectedWritten();
    if (this.totalWritten + chunk.length < this.expectedWritten() && !this.paused)
    {
        this.doWrite(chunk);
        return true;
    }
    this.buffer.push(chunk);
    if (!this.paused)
        this.processBuffer();
    return this.buffer.length < this.highWatermark;
};

ShapeStream.prototype.end = function(chunk, encoding) {
    this.stopping = true;
    if (chunk)
        this.write(chunk, encoding);
    else
        this.processBuffer();
};

ShapeStream.prototype.pause = function() {
    this.pauseCount++;
    this.paused = true;
};

ShapeStream.prototype.resume = function() {
    this.pauseCount--;
    if (this.pauseCount == 0) {
        this.paused = false;
        this.processBuffer();
    }
};

ShapeStream.prototype.destroy = function() {
    // do nothing
}

ShapeStream.prototype.destroySoon = function() {
    // do nothing
}

module.exports = function(byteRate, chunkRate) {
   return new ShapeStream(byteRate, chunkRate);
}
<|endoftext|>


<commit_before>'use strict'

const SpawnWatch                    = require('spawn-watch');
const starter                       = require('./engine/process-utils').starter;
const Rx                            = require('rx');
const statusEvts                    = require('./engine/configs').pmpEngineStatusEvts;
const noBrowserTabArg               = require('./engine/configs').additionalArguments.noBrowserTabArg;
const defaultIoConfig               = require('./engine/configs').defaultIoConfig;
const ioEvts                        = require('./socket-serving/ioEvts');

class PmpEngine {
    constructor(options) {
        this._status                = new Rx.BehaviorSubject(statusEvts.stopped);
        this._hellSpawn             = new SpawnWatch({ ipc:true });
        this._socketServer          = null;
        this._pimpCommandsConfig    = null;
        this._options               = {
            ioEnabled:false,
            ioServerConfig:defaultIoConfig
        };

        //options settings (io socket serving)
        if(options) {
            this._options = Object.assign(this._options, options);
        }

        //handle socket server enabled PmpEngine
        if(this._options.ioEnabled) {

            //init socket server
            const SocketServer = require('./socket-serving/io').SocketServer;
            this._socketServer = new SocketServer(this, this._options.ioServerConfig);

            //react to external inputs (outputs are already piped)
            this._socketServer.inputStream.subscribe(socketInputActions.bind(this));
        }
    }

    //launch pmpEngine
    start(pmpConfig, additionalArgs) {
        if(!pmpConfig || !this.pmpEngineStatus === statusEvts.stopped) return false;
        this._pimpCommandsConfig = pmpConfig;
        return starter(this._hellSpawn, pmpConfig, this._status, additionalArgs);
    }

    //restart pmpEngine
    restart(pmpConfig) {
        let nextStartConfig = (pmpConfig) ? pmpConfig : Object.assign({}, this._pimpCommandsConfig);
        this.pmpEngineStatusStream
            .filter(engineStatus => { return (engineStatus === statusEvts.stopped) })
            .first()
            .subscribe(processStatus => {
                this.start(nextStartConfig, [noBrowserTabArg]);
            });

        return this.stop();
    }

    //stop pmpEngine
    stop() {
        return this._hellSpawn.stop();
    };

    //get pmpEngine current status
    get pmpEngineStatus() {
        return this._status.value;
    }

    get currentPimpConfig() {
        return Object.assign({}, this._pimpCommandsConfig);
    }

    /* observables */
    //status
    get pmpEngineStatusStream() {
        return this._status.asObservable().distinctUntilChanged();
    }
    //logs
    get pmpEngineLogsStream() {
        return this._hellSpawn.outEventStream;
    }
    //errors
    get pmpEngineErrorsStream() {
        return this._hellSpawn.errorStream;
    }
}

const socketInputActions = function(input) {
    switch(input.subType) {
        case 'start-command': this.start(input.payload); break;
        case 'stop-command': this.stop(); break;
        case 'restart-command': this.restart(input.payload); break;
        case 'config-command': this._socketServer.emit(ioEvts.outputs.config(this.currentPimpConfig)); break;

        default:
            console.log('pmpEngine received unknown command ' + input);
    }
}

/* ===========================================================================
  LAUNCH IN STANDALONE MODE
=========================================================================== */
process.argv.map(function(arg){
  if(arg === '--standalone'){
    console.log('PMP-ENGINE - STANDALONE MODE');

    //create pmp engine instance with socket connection
    let pmpEngine = new PmpEngine({ ioEnabled:true });
  }
});

module.exports = PmpEngine;<commit_msg>add standalone start to pmp engine<commit_after>'use strict'

const SpawnWatch                    = require('spawn-watch');
const starter                       = require('./engine/process-utils').starter;
const Rx                            = require('rx');
const statusEvts                    = require('./engine/configs').pmpEngineStatusEvts;
const noBrowserTabArg               = require('./engine/configs').additionalArguments.noBrowserTabArg;
const defaultIoConfig               = require('./engine/configs').defaultIoConfig;
const ioEvts                        = require('./socket-serving/ioEvts');

class PmpEngine {
    constructor(options) {
        this._status                = new Rx.BehaviorSubject(statusEvts.stopped);
        this._hellSpawn             = new SpawnWatch({ ipc:true });
        this._socketServer          = null;
        this._pimpCommandsConfig    = null;
        this._options               = {
            ioEnabled:false,
            ioServerConfig:defaultIoConfig
        };

        //options settings (io socket serving)
        if(options) {
            this._options = Object.assign(this._options, options);
        }

        //handle socket server enabled PmpEngine
        if(this._options.ioEnabled) {

            //init socket server
            const SocketServer = require('./socket-serving/io').SocketServer;
            this._socketServer = new SocketServer(this, this._options.ioServerConfig);

            //react to external inputs (outputs are already piped)
            this._socketServer.inputStream.subscribe(socketInputActions.bind(this));
        }
    }

    //launch pmpEngine
    start(pmpConfig, additionalArgs) {
        if(!pmpConfig || !this.pmpEngineStatus === statusEvts.stopped) return false;
        this._pimpCommandsConfig = pmpConfig;
        return starter(this._hellSpawn, pmpConfig, this._status, additionalArgs);
    }

    //restart pmpEngine
    restart(pmpConfig) {
        let nextStartConfig = (pmpConfig) ? pmpConfig : Object.assign({}, this._pimpCommandsConfig);
        this.pmpEngineStatusStream
            .filter(engineStatus => { return (engineStatus === statusEvts.stopped) })
            .first()
            .subscribe(processStatus => {
                this.start(nextStartConfig, [noBrowserTabArg]);
            });

        return this.stop();
    }

    //stop pmpEngine
    stop() {
        return this._hellSpawn.stop();
    };

    //get pmpEngine current status
    get pmpEngineStatus() {
        return this._status.value;
    }

    get currentPimpConfig() {
        return Object.assign({}, this._pimpCommandsConfig);
    }

    /* observables */
    //status
    get pmpEngineStatusStream() {
        return this._status.asObservable().distinctUntilChanged();
    }
    //logs
    get pmpEngineLogsStream() {
        return this._hellSpawn.outEventStream;
    }
    //errors
    get pmpEngineErrorsStream() {
        return this._hellSpawn.errorStream;
    }
}

const socketInputActions = function(input) {
    switch(input.subType) {
        case 'start-command': this.start(input.payload); break;
        case 'stop-command': this.stop(); break;
        case 'restart-command': this.restart(input.payload); break;
        case 'config-command': this._socketServer.emit(ioEvts.outputs.config(this.currentPimpConfig)); break;

        default:
            console.log('pmpEngine received unknown command ' + input);
    }
}

/* ===========================================================================
  LAUNCH IN STANDALONE MODE
=========================================================================== */
let isStandAlone = false;
let isDebug = false;
process.argv.map(function(arg){
  if(arg === '--standalone'){
    console.log('PMP-ENGINE - STANDALONE MODE');
    isStandAlone    = true;
    isDebug         = false;
  } else if(arg === '--standalone-debug') {
    console.log('PMP-ENGINE - STANDALONE MODE & DEBUG');
    isStandAlone = isDebug = true;
  }
});

if(isStandAlone) {
    // create pmp engine instance with socket connection
    let pmpEngine = new PmpEngine({ ioEnabled:true });

    // debug
    if(isDebug) {
        pmpEngine._hellSpawn._status.subscribe(status => {
            console.log('status changes --> ' + status);
            if(status === 'started') console.dir(pmpEngine._pimpCommandsConfig, { depth: null });
        });
    }
};

module.exports = PmpEngine;<|endoftext|>


<commit_before>require("dotenv").config();

const IncomingWebhook = require("@slack/client").IncomingWebhook;
const axios = require("axios");
const parseXML = require("xml2js").parseString;
const xmldoc = require("xmldoc");
const moment = require("moment");

const url = process.env.SLACK_WEBHOOK_URL || "";
const webhook = new IncomingWebhook(url);

const getUrl = play =>
  `https://nemestats.com/PlayedGame/Details/${play.playedGameId}`;

const createPlayers = players => {
  // Check to make sure _someone_ scored points
  const points = players.some(
    player => player.pointsScored !== null && player.pointsScored !== 0
  );
  const names = players.map(player => {
    const prefix = player.gameRank === 1 ? ":trophy: " : "";
    const name = player.playerName.split(" ")[0];
    const score = points ? ` (${player.pointsScored})` : "";
    return prefix + name + score;
  });

  return { title: "Players", value: names.join(", "), short: true };
};

const getThumbnail = bggId => {
  return axios
    .get("https://www.boardgamegeek.com/xmlapi2/thing", {
      params: { id: bggId }
    })
    .then(response => {
      const document = new xmldoc.XmlDocument(response.data);
      return document.valueWithPath("item.thumbnail");
    })
    .catch(err => console.error(err));
};

const createAttachment = (play, thumbUrl) => {
  const date = moment(play.datePlayed).format("dddd, MMMM Do YYYY");
  return [
    {
      fallback: "Required plain-text summary of the attachment.",
      color: "#354458",
      title: play.gameDefinitionName,
      title_link: getUrl(play),
      text: `Played on ${date}`,
      fields: [createPlayers(play.playerGameResults)],
      thumb_url: thumbUrl
    }
  ];
};

axios
  .get("https://nemestats.com/api/v2/PlayedGames/", {
    params: {
      gamingGroupId: 13468,
      datePlayedFrom: moment().subtract(1, 'days').format('YYYY-MM-DD'),
      datePlayedTo: moment().subtract(1, 'days').format('YYYY-MM-DD')
    }
  })
  .then(({ data: { playedGames } }) => {
    playedGames.forEach(play => {
      getThumbnail(play.boardGameGeekGameDefinitionId).then(thumbUrl => {
        webhook.send({ attachments: createAttachment(play, thumbUrl) });
      });
    });
  })
  .catch(error => {
    console.error(error);
  });
<commit_msg>Testing heroku run.<commit_after>require("dotenv").config();

const IncomingWebhook = require("@slack/client").IncomingWebhook;
const axios = require("axios");
const parseXML = require("xml2js").parseString;
const xmldoc = require("xmldoc");
const moment = require("moment");

const url = process.env.SLACK_WEBHOOK_URL || "";
const webhook = new IncomingWebhook(url);

const getUrl = play =>
  `https://nemestats.com/PlayedGame/Details/${play.playedGameId}`;

const createPlayers = players => {
  // Check to make sure _someone_ scored points
  const points = players.some(
    player => player.pointsScored !== null && player.pointsScored !== 0
  );
  const names = players.map(player => {
    const prefix = player.gameRank === 1 ? ":trophy: " : "";
    const name = player.playerName.split(" ")[0];
    const score = points ? ` (${player.pointsScored})` : "";
    return prefix + name + score;
  });

  return { title: "Players", value: names.join(", "), short: true };
};

const getThumbnail = bggId => {
  return axios
    .get("https://www.boardgamegeek.com/xmlapi2/thing", {
      params: { id: bggId }
    })
    .then(response => {
      const document = new xmldoc.XmlDocument(response.data);
      return document.valueWithPath("item.thumbnail");
    })
    .catch(err => console.error(err));
};

const createAttachment = (play, thumbUrl) => {
  const date = moment(play.datePlayed).format("dddd, MMMM Do YYYY");
  return [
    {
      fallback: "Required plain-text summary of the attachment.",
      color: "#354458",
      title: play.gameDefinitionName,
      title_link: getUrl(play),
      text: `Played on ${date}`,
      fields: [createPlayers(play.playerGameResults)],
      thumb_url: thumbUrl
    }
  ];
};

axios
  .get("https://nemestats.com/api/v2/PlayedGames/", {
    params: {
      gamingGroupId: 13468,
      datePlayedFrom: moment().subtract(14, 'days').format('YYYY-MM-DD'),
      datePlayedTo: moment().subtract(1, 'days').format('YYYY-MM-DD')
    }
  })
  .then(({ data: { playedGames } }) => {
    playedGames.forEach(play => {
      getThumbnail(play.boardGameGeekGameDefinitionId).then(thumbUrl => {
        webhook.send({ attachments: createAttachment(play, thumbUrl) });
      });
    });
  })
  .catch(error => {
    console.error(error);
  });
<|endoftext|>


<commit_before>'use strict';

const postcss = require('postcss');
const helpers = require('./lib/helpers');

module.exports = postcss.plugin('postcss-js-mixins', (options = {}) => {
	let mixins = options.mixins || {},
		mixinArguments = {};

	if (options.units) {
		helpers.setDefaultUnits(options.units);
	}

	function isNumber(value) {
		let val = parseFloat(value);

		// Check that parseFloat did not create NaN or infinity and
		// make sure no other characters exist in original value but numerical digits
		return ! isNaN(val) && isFinite(val) && ! /[^\d.]/.test(value);
	}

	/**
	 * Convert argument to proper type
	 *
	 * @param {string} arg
	 * @returns {*}
	 */
	function castArgument(arg) {
		if (isNumber(arg)) {
			return parseFloat(arg);
		} else if (typeof arg === 'string') {
			if (arg === 'false' || arg === 'true') {
				return arg === 'true';
			}

			return arg.length ? arg : undefined;
		}

		return arg;
	}

	/**
	 * Generate arguments array
	 *
	 * @param {string} name
	 * @param {function} mixin
	 * @param {object} obj - mixin arguments property
	 * @returns {Array}
	 */
	function getArguments(name, mixin, obj) {
		let named = obj.named,
			ordered = obj.ordered,
			args = [],
			parameters;

		// Cache function parameter names
		if (! mixinArguments[name]) {
			mixinArguments[name] = mixin.toString()
				.match(/(function)?.*?\(([^)]*)\)/)[2]
				.split(',')
				.map(function(arg) {
					return arg.split('=')[0].trim();
				});
		}

		parameters = mixinArguments[name];

		Object.keys(named).forEach(key => {
			parameters.forEach((param, i) => {
				if (key === param) {
					args[i] = castArgument(named[key]);
				}
			});
		});

		ordered.forEach((arg, i) => {
			args[i] = castArgument(arg);
		});

		return args;
	}

	/**
	 * Evaluate mixin
	 *
	 * @param {object} node - postcss.Node
	 * @param {object} result - postcss.Result
	 * @returns {array|object}
	 */
	function evalMixin(node, result) {
		let key = node.name,
			mixin = mixins[key];

		if (! mixin) {
			node.warn(result, 'Mixin not found.');
			return [];
		}

		return mixin.apply(mixins, getArguments(key, mixin, node.arguments));
	}

	/**
	 * Create many declarations/rules from one mixin result
	 *
	 * @param {array} data
	 * @param {object} node - postcss.Mixin
	 */
	function createNodes(data, node) {
		return data.map(obj => {
			return createNode(obj, node);
		});
	}

	function createNode(obj, node) {
		if (obj.type === 'rule') {
			return createRule(obj.selector, obj.declarations, node);
		}

		return createDeclaration(obj.prop, obj.value, node);
	}

	/**
	 * Create postcss declaration with meta info from mixin
	 *
	 * @param {string} selector
	 * @param {Array} declarations - one or many postcss.Declaration
	 * @param {object} node - postcss.Mixin
	 * @returns {Rule}
	 */
	function createRule(selector, declarations, node) {
		let rule = postcss.rule({
			selector: selector,
			parent: node.parent,
			source: node.source
		});

		declarations.forEach(decl => {
			rule.append(createDeclaration(decl.prop, decl.value, node));
		});

		return rule;
	}

	/**
	 * Create postcss declaration with meta info from mixin
	 *
	 * @param {string} prop
	 * @param {string|number} value
	 * @param {object} node - postcss.Mixin
	 * @returns {Declaration}
	 */
	function createDeclaration(prop, value, node) {
		let decl = postcss.decl({
			prop: prop,
			value: value,
			parent: node.parent,
			source: node.source
		});

		return decl;
	}

	return (root, result) => {
		root.walk(node => {
			if (node.type === 'mixin') {
				let results = evalMixin(node, result);

				// Remove mixin from CSS
				// Accounting for conditional mixin logic
				if (results === false) {
					return node.remove();
				}

				if (Array.isArray(results)) {
					if (! results.length) {
						return;
					}

					node.replaceWith(createNodes(results, node));
				}

				node.replaceWith(createNode(results, node));
			}
		});
	};
});<commit_msg>Update regex for capturing mixin parameters<commit_after>'use strict';

const postcss = require('postcss');
const helpers = require('./lib/helpers');

module.exports = postcss.plugin('postcss-js-mixins', (options = {}) => {
	let mixins = options.mixins || {},
		mixinArguments = {};

	if (options.units) {
		helpers.setDefaultUnits(options.units);
	}

	function isNumber(value) {
		let val = parseFloat(value);

		// Check that parseFloat did not create NaN or infinity and
		// make sure no other characters exist in original value but numerical digits
		return ! isNaN(val) && isFinite(val) && ! /[^\d.]/.test(value);
	}

	/**
	 * Convert argument to proper type
	 *
	 * @param {string} arg
	 * @returns {*}
	 */
	function castArgument(arg) {
		if (isNumber(arg)) {
			return parseFloat(arg);
		} else if (typeof arg === 'string') {
			if (arg === 'false' || arg === 'true') {
				return arg === 'true';
			}

			return arg.length ? arg : undefined;
		}

		return arg;
	}

	/**
	 * Generate arguments array
	 *
	 * @param {string} name
	 * @param {function} mixin
	 * @param {object} obj - mixin arguments property
	 * @returns {Array}
	 */
	function getArguments(name, mixin, obj) {
		let named = obj.named,
			ordered = obj.ordered,
			args = [],
			parameters;

		// Cache function parameter names
		if (! mixinArguments[name]) {
			mixinArguments[name] = mixin.toString()
				.match(/(?:function)?\s?.*?\(([^)]*)\)/)[1]
				.split(',')
				.map(function(arg) {
					return arg.split('=')[0].trim();
				});
		}

		parameters = mixinArguments[name];

		Object.keys(named).forEach(key => {
			parameters.forEach((param, i) => {
				if (key === param) {
					args[i] = castArgument(named[key]);
				}
			});
		});

		ordered.forEach((arg, i) => {
			args[i] = castArgument(arg);
		});

		return args;
	}

	/**
	 * Evaluate mixin
	 *
	 * @param {object} node - postcss.Node
	 * @param {object} result - postcss.Result
	 * @returns {array|object}
	 */
	function evalMixin(node, result) {
		let key = node.name,
			mixin = mixins[key];

		if (! mixin) {
			node.warn(result, 'Mixin not found.');
			return [];
		}

		return mixin.apply(mixins, getArguments(key, mixin, node.arguments));
	}

	/**
	 * Create many declarations/rules from one mixin result
	 *
	 * @param {array} data
	 * @param {object} node - postcss.Mixin
	 */
	function createNodes(data, node) {
		return data.map(obj => {
			return createNode(obj, node);
		});
	}

	function createNode(obj, node) {
		if (obj.type === 'rule') {
			return createRule(obj.selector, obj.declarations, node);
		}

		return createDeclaration(obj.prop, obj.value, node);
	}

	/**
	 * Create postcss declaration with meta info from mixin
	 *
	 * @param {string} selector
	 * @param {Array} declarations - one or many postcss.Declaration
	 * @param {object} node - postcss.Mixin
	 * @returns {Rule}
	 */
	function createRule(selector, declarations, node) {
		let rule = postcss.rule({
			selector: selector,
			parent: node.parent,
			source: node.source
		});

		declarations.forEach(decl => {
			rule.append(createDeclaration(decl.prop, decl.value, node));
		});

		return rule;
	}

	/**
	 * Create postcss declaration with meta info from mixin
	 *
	 * @param {string} prop
	 * @param {string|number} value
	 * @param {object} node - postcss.Mixin
	 * @returns {Declaration}
	 */
	function createDeclaration(prop, value, node) {
		let decl = postcss.decl({
			prop: prop,
			value: value,
			parent: node.parent,
			source: node.source
		});

		return decl;
	}

	return (root, result) => {
		root.walk(node => {
			if (node.type === 'mixin') {
				let results = evalMixin(node, result);

				// Remove mixin from CSS
				// Accounting for conditional mixin logic
				if (results === false) {
					return node.remove();
				}

				if (Array.isArray(results)) {
					if (! results.length) {
						return;
					}

					node.replaceWith(createNodes(results, node));
				}

				node.replaceWith(createNode(results, node));
			}
		});
	};
});<|endoftext|>