<commit_before>var fs = require('fs'),
    path = require('path'),
    mkdirp = require('mkdirp'),
    et = require('elementtree'),
    nCallbacks = require('../util/ncallbacks'),
    asyncCopy = require('../util/asyncCopy'),
    assetsDir = 'assets/www', // relative path to project's web assets
    sourceDir = 'src',
    counter = {};

exports.installPlugin = function (config, plugin, callback) {
    // look for assets in the plugin
    var assets = plugin.xmlDoc.findall('./asset'),
        platformTag = plugin.xmlDoc.find('./platform[@name="android"]'),
        sourceFiles = platformTag.findall('./source-file'),
        libFiles = platformTag.findall('./library-file'),
        pluginsChanges = platformTag.findall('./config-file[@target="res/xml/plugins.xml"]'),
        manifestChanges = platformTag.findall('./config-file[@target="AndroidManifest.xml"]'),

        callbackCount = assets.length + sourceFiles.length + pluginsChanges.length
            + libFiles.length + manifestChanges.length,
        endCallback = nCallbacks(callbackCount, callback)

    // move asset files
    assets.forEach(function (asset) {
        var srcPath = path.resolve(
                        config.pluginPath,
                        asset.attrib['src']);

        var targetPath = path.resolve(
                            config.projectPath,
                            assetsDir,
                            asset.attrib['target']);

        asyncCopy(srcPath, targetPath, endCallback);
    });

    // move source files
    sourceFiles.forEach(function (sourceFile) {
        var srcDir = path.resolve(config.projectPath,
                                sourceFile.attrib['target-dir'])

        mkdirp(srcDir, function (err) {
            var srcFile = srcPath(config.pluginPath, sourceFile.attrib['src']),
                destFile = path.resolve(srcDir,
                                path.basename(sourceFile.attrib['src']));

            asyncCopy(srcFile, destFile, endCallback);
        });
    })

    // move library files
    libFiles.forEach(function (libFile) {
        var libDir = path.resolve(config.projectPath,
                                libFile.attrib['target-dir'])

        mkdirp(libDir, function (err) {
            var src = path.resolve(config.pluginPath, 'src/android',
                                        libFile.attrib['src']),
                dest = path.resolve(libDir,
                                path.basename(libFile.attrib['src']));
            console.log(src, dest);

            asyncCopy(src, dest, endCallback);
        });
    })

    // edit plugins.xml
    pluginsChanges.forEach(function (configNode) {
        var pluginsPath = path.resolve(config.projectPath, 'res/xml/plugins.xml'),
            pluginsDoc = readAsETSync(pluginsPath),
            selector = configNode.attrib["parent"],
            child = configNode.find('*');

        if (addToDoc(pluginsDoc, child, selector)) {
            fs.writeFile(pluginsPath, pluginsDoc.write(), function (err) {
                if (err) endCallback(err);

                endCallback();
            });
        } else {
            endCallback('failed to add node to plugins.xml');
        }
    });

    // edit AndroidManifest.xml
    manifestChanges.forEach(function (configNode) {
        var manifestPath = path.resolve(config.projectPath, 'AndroidManifest.xml'),
            manifestDoc  = readAsETSync(manifestPath),
            selector = configNode.attrib["parent"],
            child = configNode.find('*');

        if (addToDoc(manifestDoc, child, selector)) {
            fs.writeFile(manifestPath, manifestDoc.write(), function (err) {
                if (err) endCallback(err);

                endCallback();
            });
        } else {
            endCallback('failed to add node to AndroidManifest.xml')
        }
    })
}

// adds node to doc at selector
function addToDoc(doc, node, selector) {
    var ROOT = /^\/([^\/]*)/,
        ABSOLUTE = /^\/([^\/]*)\/(.*)/,
        parent, tagName, subSelector;

    // handle absolute selector (which elementtree doesn't like)
    if (ROOT.test(selector)) {
        tagName = selector.match(ROOT)[1];
        if (tagName === doc._root.tag) {
            parent = doc._root;

            // could be an absolute path, but not selecting the root
            if (ABSOLUTE.test(selector)) {
                subSelector = selector.match(ABSOLUTE)[2];
                parent = parent.find(subSelector)
            }
        } else {
            return false;
        }
    } else {
        parent = doc.find(selector)
    }

    parent.append(node);
    return true;
}

function readAsETSync(filename) {
    var contents = fs.readFileSync(filename, 'utf-8');

    return new et.ElementTree(et.XML(contents));
}

function srcPath(pluginPath, filename) {
    var prefix = /^src\/android/;

    if (prefix.test(filename)) {
        return path.resolve(pluginPath, filename);
    } else {
        return path.resolve(pluginPath, 'src/android', filename);
    }
}
<commit_msg>[android] handle config.xml and plugins.xml<commit_after>var fs = require('fs'),
    path = require('path'),
    mkdirp = require('mkdirp'),
    et = require('elementtree'),
    nCallbacks = require('../util/ncallbacks'),
    asyncCopy = require('../util/asyncCopy'),
    getConfigChanges = require('../util/config-changes'),
    assetsDir = 'assets/www', // relative path to project's web assets
    sourceDir = 'src',
    counter = {};

exports.installPlugin = function (config, plugin, callback) {
    // look for assets in the plugin
    var assets = plugin.xmlDoc.findall('./asset'),
        platformTag = plugin.xmlDoc.find('./platform[@name="android"]'),
        sourceFiles = platformTag.findall('./source-file'),
        libFiles = platformTag.findall('./library-file'),

        configChanges = getConfigChanges(platformTag),

        callbackCount = assets.length + sourceFiles.length + libFiles.length,
        endCallback;

    // find which config-files we're interested in
    Object.keys(configChanges).forEach(function (configFile) {
        if (fs.existsSync(path.resolve(config.projectPath, configFile))) {
            // if so, add length to callbackCount
            callbackCount += configChanges[configFile].length
        } else {
            delete configChanges[configFile];
        }
    });

    endCallback = nCallbacks(callbackCount, callback)

    // move asset files
    assets.forEach(function (asset) {
        var srcPath = path.resolve(
                        config.pluginPath,
                        asset.attrib['src']);

        var targetPath = path.resolve(
                            config.projectPath,
                            assetsDir,
                            asset.attrib['target']);

        asyncCopy(srcPath, targetPath, endCallback);
    });

    // move source files
    sourceFiles.forEach(function (sourceFile) {
        var srcDir = path.resolve(config.projectPath,
                                sourceFile.attrib['target-dir'])

        mkdirp(srcDir, function (err) {
            var srcFile = srcPath(config.pluginPath, sourceFile.attrib['src']),
                destFile = path.resolve(srcDir,
                                path.basename(sourceFile.attrib['src']));

            asyncCopy(srcFile, destFile, endCallback);
        });
    })

    // move library files
    libFiles.forEach(function (libFile) {
        var libDir = path.resolve(config.projectPath,
                                libFile.attrib['target-dir'])

        mkdirp(libDir, function (err) {
            var src = path.resolve(config.pluginPath, 'src/android',
                                        libFile.attrib['src']),
                dest = path.resolve(libDir,
                                path.basename(libFile.attrib['src']));
            console.log(src, dest);

            asyncCopy(src, dest, endCallback);
        });
    })

    Object.keys(configChanges).forEach(function (filename) {
        configChanges[filename].forEach(function (configNode) {
            var filepath = path.resolve(config.projectPath, filename),
                xmlDoc = readAsETSync(filepath),
                selector = configNode.attrib["parent"],
                child = configNode.find('*');

            if (addToDoc(xmlDoc, child, selector)) {
                fs.writeFile(filepath, xmlDoc.write(), function (err) {
                    if (err) endCallback(err);

                    endCallback();
                });
            } else {
                endCallback('failed to add node to ' + filename);
            }
        });
    });
}

// adds node to doc at selector
function addToDoc(doc, node, selector) {
    var ROOT = /^\/([^\/]*)/,
        ABSOLUTE = /^\/([^\/]*)\/(.*)/,
        parent, tagName, subSelector;

    // handle absolute selector (which elementtree doesn't like)
    if (ROOT.test(selector)) {
        tagName = selector.match(ROOT)[1];
        if (tagName === doc._root.tag) {
            parent = doc._root;

            // could be an absolute path, but not selecting the root
            if (ABSOLUTE.test(selector)) {
                subSelector = selector.match(ABSOLUTE)[2];
                parent = parent.find(subSelector)
            }
        } else {
            return false;
        }
    } else {
        parent = doc.find(selector)
    }

    parent.append(node);
    return true;
}

function readAsETSync(filename) {
    var contents = fs.readFileSync(filename, 'utf-8');

    return new et.ElementTree(et.XML(contents));
}

function srcPath(pluginPath, filename) {
    var prefix = /^src\/android/;

    if (prefix.test(filename)) {
        return path.resolve(pluginPath, filename);
    } else {
        return path.resolve(pluginPath, 'src/android', filename);
    }
}
<|endoftext|>


<commit_before>/**
 * Agreement Form
 */
(function() {

    Ext.require('HatimeriaCore.direct.ResponseHandler');

    Ext.define('HatimeriaCore.form.AgreementForm', {
        extend: 'Ext.form.Panel',
        config: {
            /**
             * Direct function to content of terms
             */
            directFn: function() {},

            /**
             * Optional request parameters
             */
            params: {},

            /**
             * Label of form
             */
            label: 'Regulamin',

            /**
             * Label behind checkbox
             */
            checkboxLabel: 'Akceptuję requlamin'
        },

        /**
         * Constructor
         *
         * @param {} config
         */
        constructor: function(config)
        {
            this.initConfig(config);
            this.callParent([config]);

            return this;
        },

        /**
         * Initializes form
         */
        initComponent: function()
        {
            var config = {
                border: false,
                items: [
                    {
                        id: 'agreement-field',
                        fieldLabel: this.getLabel(),
                        labelAlign: 'top',
                        xtype: 'textarea',
                        isFormField: false,
                        readOnly: true,
                        submitValue: false,
                        width: this.initialConfig.width || 350 ,
                        height: 100
                    },
                    {
                        xtype: 'checkbox',
                        fieldLabel: false,
                        boxLabel: this.getCheckboxLabel()+'*',
                        name: 'agreement'
                    }
                ]
            };
            Ext.apply(this, Ext.apply(config, this.initComponent || {}));

            this.callParent();

            this.on('afterrender', function() {
                //this.loadTerms();
                this.getComponent('agreement-field').setValue('Miller przyznał, że były trzy powody, dla których wystartował w wyborach na szefa klubu SLD. Pierwszym było "pragnienie, aby moje koleżanki i koledzy mieli prawdziwy wybór". Zdaniem byłego premiera rywalizacja jego i Ryszarda Kalisza "dawały szansę wyboru". - Po drugie mam zamiar razem z moimi koleżankami i kolegami uczynić z parlamentarnego klubu SLD liczącą się siłę, twardą opozycję, wrażliwą na racjonalne argumenty i partnera dla innych ugrupowań politycznych - przekonywał Miller. Jak zaznaczył celem Sojuszu jest to, aby "przestać być opozycją". - Chcielibyśmy być partnerem, który o nic nie prosi i przed nikim nie klęczy - zaznaczył. Trzeci powód to, jak wyznał Miller, "chęć pomocy dla formacji politycznej, z której się wywodzimy". yły prezes rady ministrów przyznał, że nie zamierza być szefem całego Sojuszu. - Trzeba poszerzać grupę liderów - przekonywał. - Mam nadzieję, że lider zostanie wybrany w głosowaniu powszechnym w prawyborach. Przy okazji dowiemy się ilu jest członków SLD i jest dobra okazja, żeby odbyć interesującą dyskusję programową. Dyskusja na ten temat jest bardzo potrzebna - stwierdził Miller.- Z Kaliszem różnimy się w niuansach. Musimy najpierw przeprowadzić proces ozdrowieńczy, żeby móc debatować z pozycji odpowiedzialnego partnera, który pokonał własne słabości. Potem kongres lewicy i proces jednoczenia. Trzeba trwać przy sztandarze, a nie go wyprowadzać. Można przeprowadzić okrągły stół lewicy, wracając do dobrych tradycji, i zobaczyć gdzie się różnimy, a gdzie są możliwości wspólnego działania - stwierdził Miller w TOK FM.');
                console.log(this.getComponent('agreement-field'))
            });
        },

        /**
         * Load terms of Agreement
         */
        loadTerms: function()
        {
            Ext.create('HatimeriaCore.direct.ResponseHandler', {
                fn: this.getDirectFn(),
                params: this.getParams(),
                scope: this,
                success: function(result) {
                    this.updateTerms(result.record);
                }
            });
        },

        /**
         * Updates a textarea field
         *
         * @param string value
         */
        updateTerms: function(value)
        {
            this.getComponent('agreement-field').setValue(value);
        }
    });

})();<commit_msg>removed mock data<commit_after>/**
 * Agreement Form
 */
(function() {

    Ext.require('HatimeriaCore.direct.ResponseHandler');

    Ext.define('HatimeriaCore.form.AgreementForm', {
        extend: 'Ext.form.Panel',
        config: {
            /**
             * Direct function to content of terms
             */
            directFn: function() {},

            /**
             * Optional request parameters
             */
            params: {},

            /**
             * Label of form
             */
            label: 'Regulamin',

            /**
             * Label behind checkbox
             */
            checkboxLabel: 'Akceptuję requlamin'
        },

        /**
         * Constructor
         *
         * @param {} config
         */
        constructor: function(config)
        {
            this.initConfig(config);
            this.callParent([config]);

            return this;
        },

        /**
         * Initializes form
         */
        initComponent: function()
        {
            var config = {
                border: false,
                items: [
                    {
                        id: 'agreement-field',
                        fieldLabel: this.getLabel(),
                        labelAlign: 'top',
                        xtype: 'textarea',
                        isFormField: false,
                        readOnly: true,
                        submitValue: false,
                        width: this.initialConfig.width || 350 ,
                        height: 100
                    },
                    {
                        xtype: 'checkbox',
                        fieldLabel: false,
                        boxLabel: this.getCheckboxLabel()+'*',
                        name: 'agreement'
                    }
                ]
            };
            Ext.apply(this, Ext.apply(config, this.initComponent || {}));

            this.callParent();

            this.on('afterrender', function() {
                this.loadTerms();
            });
        },

        /**
         * Load terms of Agreement
         */
        loadTerms: function()
        {
            Ext.create('HatimeriaCore.direct.ResponseHandler', {
                fn: this.getDirectFn(),
                params: this.getParams(),
                scope: this,
                success: function(result) {
                    this.updateTerms(result.record);
                }
            });
        },

        /**
         * Updates a textarea field
         *
         * @param string value
         */
        updateTerms: function(value)
        {
            this.getComponent('agreement-field').setValue(value);
        }
    });

})();<|endoftext|>


<commit_before>var types = require("../lib/HAP-NodeJS/accessories/types.js");
var request = require('request');
var net = require('net')
/* TODO:
 * - send keepalive
 * - auto reconnect on disconnect
 * - wait for success after command and then send the next
 * - support more devices
 */

var DEBUG = true;

PilightDeviceTypes = {
  RAW: 0,
  SWITCH: 1,
  DIMMER: 2,
  WEATHER: 3,
  RELAY: 4,
  SCREEN: 5,
  CONTACT: 6,
  PENDINGSW: 7,
  DATETIME: 8,
  XBMC: 9,
  LIRC: 10,
  WEBCAM: 11
}

function PilightPlatform(log, config){
  this.name = config['name']
  this.host = config['host']
  this.port = config['port']
  this.protocol = config['protocol']
  this.http_port = config['http-port']

  this.log = log;
}

function PilightClient(log, platform) {
    var that = this;

    this.log = log;
    this.platform = platform;
    this.registry = [];

    this.connect();
    this.startHeartBeat();
}

PilightClient.prototype = {
  send: function(cmd) {
    var msg = JSON.stringify(cmd);
    this.log("[PilightClient.send] "+msg);
    this.socket.write(msg);
  },

  register: function(idx, accessory) {
    this.registry[idx] = accessory;
  },

  updateDevices: function(devices, values) {
    for(var device of devices) {
      var dev = this.registry[device]
      if(dev === undefined || dev.length === 0) {
        continue
      }
      this.log("[PilightClient.updateDevices] External update on device "
               + device);
      dev.updateCharacteristics(values);
    }
  },

  startHeartBeat: function() {
    var that = this;
    var cb = function() { that.socket.write("HEART\n", "utf-8"); };
    this.heartBeatInterval = setInterval(cb, 60000);
  },

  onBeat: function() {
    if(DEBUG) {
      var stamp =  new Date(Date.now());
      this.log("BEAT " + stamp);
    }

    if(this.heartBeatTimeout) {
      clearTimeout(this.heartBeatTimeout);
    }

    var that = this;
    var reconnectDelay = 150000;
    var reconnect = function() {
      that.log('[PilightClient.reconnect] Trying reconnect');
      try{
        that.socket.end();
        that.socket.destroy();
      }
      finally {
        that.connect();
      }
    }
    this.heartBeatTimeout = setTimeout(reconnect, reconnectDelay);
  },

  connect: function() {
    var that = this;
    var bootstrap = function() {
      that.log('connected to server!');
      var cmd = {
        "action": "identify",
        "options": {
          "config": 1
        }
      };
      that.send(cmd);
    }

    this.socket = net.connect({host:this.platform.host, port:this.platform.port}, bootstrap);

    this.socket.on('data', function(data) {
      data = data.toString();
      for(msg of data.split('\n')) {
        if(msg.length === 0) { continue; }

        if(msg === "BEAT") {
          that.onBeat();
          continue;
        }

        try {
          data = JSON.parse(data);
        } catch(e) {
          if (e instanceof SyntaxError) { that.log("SyntaxError in " + msg); }
          else { that.log("Error " + JSON.stringify(e)); }
          continue;
        }
        if(data['origin'] !== 'update') {
          continue;
        }
        that.updateDevices(data['devices'], data['values']);
      }
    });

    this.socket.on('end', function() {
      clearTimeout(that.heartBeatTimeout);
      clearTimeout(that.heartBeatInterval);
      that.log('disconnected from server');
    });
  }
}

PilightPlatform.prototype = {
  accessories: function(callback) {
    // XXX request config over socket crashes pilight
    var url = this.protocol + '://' + this.host + ':'
                + this.http_port +'/config';

    this.log("[PilightPlatform.accessories] Fetching pilight devices.");
    this.log("[PilightPlatform.accessories] Fetching from: "+url);

    var collected = [];
    var that = this;
    var client = new PilightClient(this.log, this);

    request.get({url: url, json:true}, function(err, response, json) {
      var accessory = null;

      if (!err && response.statusCode == 200) {

        for (var device_name in json['gui']) {

          var gui = json['gui'][device_name]
          var device = json['devices'][device_name]
          var accessory;

          for (var proto of device['protocol']) {

            that.log("[PilightPlatform.accessories] " + gui['name'] + " : " + proto);

            switch(proto) {
              case "brennenstuhl":
              case "elro_800_switch":
                accessory = PilightSwitch;
                break;
              case 'openweathermap':
                accessory = PilightThermostat;
                break;
              default: // arping, sunriseset, datetime, generic_label
                that.log("Unsupported pilight device (" + gui['name']
                         + ") with protocol " + device['protocol']);
                continue;
            }
            that.log("Adding pilight device (" + gui['name'] + ") with protocol "
                     + device['protocol']);
            collected.push(
                    new accessory(that.log, device_name, gui['name'], proto, device, client));
            break;  // we found a supported protocol, so we are done with this device
          }
        }
        callback(collected);
      } else {
        that.log("There was a problem requesting the configuration from pilight.");
      }
    });
  }
}

function PilightSwitch(log, idx, name, protocol, info, client) {
  // device info
  log("Name: "+idx);
  this.idx = idx;
  this.name = name;
  this.log = log;
  this.client = client;
  this.registry = [];
  this.info = info;
  this.protocol = protocol;
}

PilightSwitch.prototype = {

  registerForUpdates: function(characteristic) {
    this.log("[PilightSwitch.registerForUpdates] Registering device "+this.idx
             + " for updates on characteristic '"+characteristic.manfDescription+"'");

    this.registry.push(characteristic);
    this.client.register(this.idx, this);
  },

  updateCharacteristics: function(values) {
    var newValue = (values['state'] === 'on')*1;
    for(var c of this.registry) {
      if(c.value === newValue) {
        this.log("[PilightSwitch.updateCharacteristics] No state change "
                 + "detected on characteristic, skiping event");
        continue;
      }
      this.log("[PilightSwitch.updateCharacteristics] Updating device "
               +this.name + " to value "+newValue+" on characteristic '"
               +JSON.stringify(c.manfDescription)+"'");
      c.updateValue(newValue);
    }
  },

  setPowerState: function(powerOn) {
    var that = this;
    this.log("[PilightSwitch.setPowerState] "+powerOn);
    this.client.send({
          "action": "control",
          "code": {
            "device": that.idx,
            "state": (powerOn)?"on":"off"
          }});
  },

  getServices: function() {
    var that = this;
    return [{
      sType: types.ACCESSORY_INFORMATION_STYPE,
      characteristics: [{
        cType: types.NAME_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: that.name,
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Name of the accessory",
        designedMaxLength: 255
      },{
        cType: types.MANUFACTURER_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "pilight",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Manufacturer",
        designedMaxLength: 255
      },{
        cType: types.MODEL_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "Rev-1",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Model",
        designedMaxLength: 255
      },{
        cType: types.SERIAL_NUMBER_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "BIVAB-PO-1",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "SN",
        designedMaxLength: 255
      },{
        cType: types.IDENTIFY_CTYPE,
        onUpdate: null,
        perms: ["pw"],
        format: "bool",
        initialValue: false,
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Identify Accessory",
        designedMaxLength: 1
      }]
    },{
      sType: types.OUTLET_STYPE,
      characteristics: [{
        cType: types.NAME_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: that.name + " outlet service",
        supportEvents: true,
        supportBonjour: false,
        manfDescription: "Name of service",
        designedMaxLength: 255
      },{
        cType: types.POWER_STATE_CTYPE,
        onUpdate: function(value) { that.setPowerState(value)},
        onRegister: function(characteristic) { that.registerForUpdates(characteristic) },
        perms: ["pw","pr","ev"],
        format: "bool",
        initialValue: (this.state==='on')?1:0,
        supportEvents: true,
        supportBonjour: false,
        manfDescription: "Change the power state of the outlet",
        designedMaxLength: 1
      },{
        cType: types.OUTLET_IN_USE_CTYPE,
        onUpdate: function(value) { that.setPowerState(value)},
        perms: ["pr","ev"],
        format: "bool",
        initialValue: true,
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Turn On the Light",
        designedMaxLength: 1
      }]
    }];
  }
};

// XXX refactory and merge with Switch
function PilightThermostat(log, idx, name, protocol, info, client) {
  // device info
  log("Name: "+idx);
  this.idx = idx;
  this.name = name;
  this.log = log;
  this.client = client;
  this.registry = [];
  this.info = info;
  this.protocol = protocol;
}

// XXX merge with Switch prototype and extract common prototype
PilightThermostat.prototype = {

  registerForUpdates: function(characteristic) {
    this.log("[PilightThermostat.registerForUpdates] Registering device "+this.idx
             + " for updates on characteristic '"+characteristic.manfDescription+"'");

    this.registry.push(characteristic);
    this.client.register(this.idx, this);
  },

  updateCharacteristics: function(values) {
    var newValue;
    for(var c of this.registry) {
      switch(c.type) {
        case types.CURRENT_TEMPERATURE_CTYPE:
          newValue = values['temperature'];
          break;
        case types.CURRENT_RELATIVE_HUMIDITY_CTYPE:
          newValue = values['humidity'];
          break;
      }
      if(newValue === undefined || c.value === newValue) {
        this.log("[PilightThermostat.updateCharacteristics] No state change "
                 + "detected on characteristic, skiping event");
        continue;
      }
      this.log("[PilightThermostat.updateCharacteristics] Updating device "
               +this.name + " to value "+newValue+" on characteristic '"
               +JSON.stringify(c.manfDescription)+"'");
    }
    c.updateValue(newValue);
  },

  getServices: function() {
    var that = this;
    return [{
      sType: types.ACCESSORY_INFORMATION_STYPE,
      characteristics: [{
        cType: types.NAME_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: that.name + "Thermostat 1",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Bla",
        designedMaxLength: 255
        },{
        cType: types.MANUFACTURER_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "Oltica",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Bla",
        designedMaxLength: 255
        },{
        cType: types.MODEL_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "Rev-1",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Bla",
        designedMaxLength: 255
        },{
        cType: types.SERIAL_NUMBER_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "A1S2NASF88EW",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Bla",
        designedMaxLength: 255
        },{
        cType: types.IDENTIFY_CTYPE,
        onUpdate: null,
        perms: ["pw"],
        format: "bool",
        initialValue: false,
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Identify Accessory",
        designedMaxLength: 1
      }]
      },{
      sType: types.THERMOSTAT_STYPE,
      characteristics: [{
          cType: types.NAME_CTYPE,
          onUpdate: null,
          perms: ["pr"],
          format: "string",
          initialValue: that.name + "Thermostat Control",
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Bla",
          designedMaxLength: 255
        },{
          cType: types.CURRENTHEATINGCOOLING_CTYPE,
          perms: ["pr","ev"],
          format: "int",
          initialValue: 0,
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Current Mode",
          designedMaxLength: 1,
          designedMinValue: 0,
          designedMaxValue: 2,
          designedMinStep: 1,
        },{
          cType: types.TARGETHEATINGCOOLING_CTYPE,
          perms: ["pr","ev"],
          format: "int",
          initialValue: 0,
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Target Mode",
          designedMinValue: 0,
          designedMaxValue: 3,
          designedMinStep: 1,
        },{
          cType: types.CURRENT_TEMPERATURE_CTYPE,
          onRegister: function(characteristic) { that.registerForUpdates(characteristic) },
          onUpdate: null,
          perms: ["pr","ev"],
          format: "int",
          initialValue: parseFloat(that.info['temperature']),
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Current Temperature",
          unit: "celsius"
        },{
          cType: types.TARGET_TEMPERATURE_CTYPE,
          onUpdate: null,
          perms: ["pr","ev"],
          format: "int",
          initialValue: parseFloat(that.info['temperature']),
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Target Temperature",
          designedMinValue: 16,
          designedMaxValue: 38,
          designedMinStep: 1,
          unit: "celsius"
        },{
          cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE,
          onRegister: function(characteristic) { that.registerForUpdates(characteristic) },
          onUpdate: null,
          perms: ["pr","ev"],
          format: "float",
          initialValue: parseFloat(that.info['humidity']),
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Current relative humidity",
          unit: "percentage",
          designedMinValue: 0,
          designedMaxValue: 100,
          designedMinStep: 1
        },{
          cType: types.TEMPERATURE_UNITS_CTYPE,
          perms: ["pr","ev"],
          format: "int",
          initialValue: 0,
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Unit",
        }]
      }];
  }
};
module.exports.accessory = [PilightSwitch, PilightThermostat];
module.exports.platform = PilightPlatform;
<commit_msg>Removed finished todo items<commit_after>var types = require("../lib/HAP-NodeJS/accessories/types.js");
var request = require('request');
var net = require('net')
/* TODO:
 * - wait for success after command and then send the next
 * - support more devices
 */

var DEBUG = true;

PilightDeviceTypes = {
  RAW: 0,
  SWITCH: 1,
  DIMMER: 2,
  WEATHER: 3,
  RELAY: 4,
  SCREEN: 5,
  CONTACT: 6,
  PENDINGSW: 7,
  DATETIME: 8,
  XBMC: 9,
  LIRC: 10,
  WEBCAM: 11
}

function PilightPlatform(log, config){
  this.name = config['name']
  this.host = config['host']
  this.port = config['port']
  this.protocol = config['protocol']
  this.http_port = config['http-port']

  this.log = log;
}

function PilightClient(log, platform) {
    var that = this;

    this.log = log;
    this.platform = platform;
    this.registry = [];

    this.connect();
    this.startHeartBeat();
}

PilightClient.prototype = {
  send: function(cmd) {
    var msg = JSON.stringify(cmd);
    this.log("[PilightClient.send] "+msg);
    this.socket.write(msg);
  },

  register: function(idx, accessory) {
    this.registry[idx] = accessory;
  },

  updateDevices: function(devices, values) {
    for(var device of devices) {
      var dev = this.registry[device]
      if(dev === undefined || dev.length === 0) {
        continue
      }
      this.log("[PilightClient.updateDevices] External update on device "
               + device);
      dev.updateCharacteristics(values);
    }
  },

  startHeartBeat: function() {
    var that = this;
    var cb = function() { that.socket.write("HEART\n", "utf-8"); };
    this.heartBeatInterval = setInterval(cb, 60000);
  },

  onBeat: function() {
    if(DEBUG) {
      var stamp =  new Date(Date.now());
      this.log("BEAT " + stamp);
    }

    if(this.heartBeatTimeout) {
      clearTimeout(this.heartBeatTimeout);
    }

    var that = this;
    var reconnectDelay = 150000;
    var reconnect = function() {
      that.log('[PilightClient.reconnect] Trying reconnect');
      try{
        that.socket.end();
        that.socket.destroy();
      }
      finally {
        that.connect();
      }
    }
    this.heartBeatTimeout = setTimeout(reconnect, reconnectDelay);
  },

  connect: function() {
    var that = this;
    var bootstrap = function() {
      that.log('connected to server!');
      var cmd = {
        "action": "identify",
        "options": {
          "config": 1
        }
      };
      that.send(cmd);
    }

    this.socket = net.connect({host:this.platform.host, port:this.platform.port}, bootstrap);

    this.socket.on('data', function(data) {
      data = data.toString();
      for(msg of data.split('\n')) {
        if(msg.length === 0) { continue; }

        if(msg === "BEAT") {
          that.onBeat();
          continue;
        }

        try {
          data = JSON.parse(data);
        } catch(e) {
          if (e instanceof SyntaxError) { that.log("SyntaxError in " + msg); }
          else { that.log("Error " + JSON.stringify(e)); }
          continue;
        }
        if(data['origin'] !== 'update') {
          continue;
        }
        that.updateDevices(data['devices'], data['values']);
      }
    });

    this.socket.on('end', function() {
      clearTimeout(that.heartBeatTimeout);
      clearTimeout(that.heartBeatInterval);
      that.log('disconnected from server');
    });
  }
}

PilightPlatform.prototype = {
  accessories: function(callback) {
    // XXX request config over socket crashes pilight
    var url = this.protocol + '://' + this.host + ':'
                + this.http_port +'/config';

    this.log("[PilightPlatform.accessories] Fetching pilight devices.");
    this.log("[PilightPlatform.accessories] Fetching from: "+url);

    var collected = [];
    var that = this;
    var client = new PilightClient(this.log, this);

    request.get({url: url, json:true}, function(err, response, json) {
      var accessory = null;

      if (!err && response.statusCode == 200) {

        for (var device_name in json['gui']) {

          var gui = json['gui'][device_name]
          var device = json['devices'][device_name]
          var accessory;

          for (var proto of device['protocol']) {

            that.log("[PilightPlatform.accessories] " + gui['name'] + " : " + proto);

            switch(proto) {
              case "brennenstuhl":
              case "elro_800_switch":
                accessory = PilightSwitch;
                break;
              case 'openweathermap':
                accessory = PilightThermostat;
                break;
              default: // arping, sunriseset, datetime, generic_label
                that.log("Unsupported pilight device (" + gui['name']
                         + ") with protocol " + device['protocol']);
                continue;
            }
            that.log("Adding pilight device (" + gui['name'] + ") with protocol "
                     + device['protocol']);
            collected.push(
                    new accessory(that.log, device_name, gui['name'], proto, device, client));
            break;  // we found a supported protocol, so we are done with this device
          }
        }
        callback(collected);
      } else {
        that.log("There was a problem requesting the configuration from pilight.");
      }
    });
  }
}

function PilightSwitch(log, idx, name, protocol, info, client) {
  // device info
  log("Name: "+idx);
  this.idx = idx;
  this.name = name;
  this.log = log;
  this.client = client;
  this.registry = [];
  this.info = info;
  this.protocol = protocol;
}

PilightSwitch.prototype = {

  registerForUpdates: function(characteristic) {
    this.log("[PilightSwitch.registerForUpdates] Registering device "+this.idx
             + " for updates on characteristic '"+characteristic.manfDescription+"'");

    this.registry.push(characteristic);
    this.client.register(this.idx, this);
  },

  updateCharacteristics: function(values) {
    var newValue = (values['state'] === 'on')*1;
    for(var c of this.registry) {
      if(c.value === newValue) {
        this.log("[PilightSwitch.updateCharacteristics] No state change "
                 + "detected on characteristic, skiping event");
        continue;
      }
      this.log("[PilightSwitch.updateCharacteristics] Updating device "
               +this.name + " to value "+newValue+" on characteristic '"
               +JSON.stringify(c.manfDescription)+"'");
      c.updateValue(newValue);
    }
  },

  setPowerState: function(powerOn) {
    var that = this;
    this.log("[PilightSwitch.setPowerState] "+powerOn);
    this.client.send({
          "action": "control",
          "code": {
            "device": that.idx,
            "state": (powerOn)?"on":"off"
          }});
  },

  getServices: function() {
    var that = this;
    return [{
      sType: types.ACCESSORY_INFORMATION_STYPE,
      characteristics: [{
        cType: types.NAME_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: that.name,
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Name of the accessory",
        designedMaxLength: 255
      },{
        cType: types.MANUFACTURER_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "pilight",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Manufacturer",
        designedMaxLength: 255
      },{
        cType: types.MODEL_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "Rev-1",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Model",
        designedMaxLength: 255
      },{
        cType: types.SERIAL_NUMBER_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "BIVAB-PO-1",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "SN",
        designedMaxLength: 255
      },{
        cType: types.IDENTIFY_CTYPE,
        onUpdate: null,
        perms: ["pw"],
        format: "bool",
        initialValue: false,
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Identify Accessory",
        designedMaxLength: 1
      }]
    },{
      sType: types.OUTLET_STYPE,
      characteristics: [{
        cType: types.NAME_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: that.name + " outlet service",
        supportEvents: true,
        supportBonjour: false,
        manfDescription: "Name of service",
        designedMaxLength: 255
      },{
        cType: types.POWER_STATE_CTYPE,
        onUpdate: function(value) { that.setPowerState(value)},
        onRegister: function(characteristic) { that.registerForUpdates(characteristic) },
        perms: ["pw","pr","ev"],
        format: "bool",
        initialValue: (this.state==='on')?1:0,
        supportEvents: true,
        supportBonjour: false,
        manfDescription: "Change the power state of the outlet",
        designedMaxLength: 1
      },{
        cType: types.OUTLET_IN_USE_CTYPE,
        onUpdate: function(value) { that.setPowerState(value)},
        perms: ["pr","ev"],
        format: "bool",
        initialValue: true,
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Turn On the Light",
        designedMaxLength: 1
      }]
    }];
  }
};

// XXX refactory and merge with Switch
function PilightThermostat(log, idx, name, protocol, info, client) {
  // device info
  log("Name: "+idx);
  this.idx = idx;
  this.name = name;
  this.log = log;
  this.client = client;
  this.registry = [];
  this.info = info;
  this.protocol = protocol;
}

// XXX merge with Switch prototype and extract common prototype
PilightThermostat.prototype = {

  registerForUpdates: function(characteristic) {
    this.log("[PilightThermostat.registerForUpdates] Registering device "+this.idx
             + " for updates on characteristic '"+characteristic.manfDescription+"'");

    this.registry.push(characteristic);
    this.client.register(this.idx, this);
  },

  updateCharacteristics: function(values) {
    var newValue;
    for(var c of this.registry) {
      switch(c.type) {
        case types.CURRENT_TEMPERATURE_CTYPE:
          newValue = values['temperature'];
          break;
        case types.CURRENT_RELATIVE_HUMIDITY_CTYPE:
          newValue = values['humidity'];
          break;
      }
      if(newValue === undefined || c.value === newValue) {
        this.log("[PilightThermostat.updateCharacteristics] No state change "
                 + "detected on characteristic, skiping event");
        continue;
      }
      this.log("[PilightThermostat.updateCharacteristics] Updating device "
               +this.name + " to value "+newValue+" on characteristic '"
               +JSON.stringify(c.manfDescription)+"'");
    }
    c.updateValue(newValue);
  },

  getServices: function() {
    var that = this;
    return [{
      sType: types.ACCESSORY_INFORMATION_STYPE,
      characteristics: [{
        cType: types.NAME_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: that.name + "Thermostat 1",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Bla",
        designedMaxLength: 255
        },{
        cType: types.MANUFACTURER_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "Oltica",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Bla",
        designedMaxLength: 255
        },{
        cType: types.MODEL_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "Rev-1",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Bla",
        designedMaxLength: 255
        },{
        cType: types.SERIAL_NUMBER_CTYPE,
        onUpdate: null,
        perms: ["pr"],
        format: "string",
        initialValue: "A1S2NASF88EW",
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Bla",
        designedMaxLength: 255
        },{
        cType: types.IDENTIFY_CTYPE,
        onUpdate: null,
        perms: ["pw"],
        format: "bool",
        initialValue: false,
        supportEvents: false,
        supportBonjour: false,
        manfDescription: "Identify Accessory",
        designedMaxLength: 1
      }]
      },{
      sType: types.THERMOSTAT_STYPE,
      characteristics: [{
          cType: types.NAME_CTYPE,
          onUpdate: null,
          perms: ["pr"],
          format: "string",
          initialValue: that.name + "Thermostat Control",
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Bla",
          designedMaxLength: 255
        },{
          cType: types.CURRENTHEATINGCOOLING_CTYPE,
          perms: ["pr","ev"],
          format: "int",
          initialValue: 0,
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Current Mode",
          designedMaxLength: 1,
          designedMinValue: 0,
          designedMaxValue: 2,
          designedMinStep: 1,
        },{
          cType: types.TARGETHEATINGCOOLING_CTYPE,
          perms: ["pr","ev"],
          format: "int",
          initialValue: 0,
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Target Mode",
          designedMinValue: 0,
          designedMaxValue: 3,
          designedMinStep: 1,
        },{
          cType: types.CURRENT_TEMPERATURE_CTYPE,
          onRegister: function(characteristic) { that.registerForUpdates(characteristic) },
          onUpdate: null,
          perms: ["pr","ev"],
          format: "int",
          initialValue: parseFloat(that.info['temperature']),
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Current Temperature",
          unit: "celsius"
        },{
          cType: types.TARGET_TEMPERATURE_CTYPE,
          onUpdate: null,
          perms: ["pr","ev"],
          format: "int",
          initialValue: parseFloat(that.info['temperature']),
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Target Temperature",
          designedMinValue: 16,
          designedMaxValue: 38,
          designedMinStep: 1,
          unit: "celsius"
        },{
          cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE,
          onRegister: function(characteristic) { that.registerForUpdates(characteristic) },
          onUpdate: null,
          perms: ["pr","ev"],
          format: "float",
          initialValue: parseFloat(that.info['humidity']),
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Current relative humidity",
          unit: "percentage",
          designedMinValue: 0,
          designedMaxValue: 100,
          designedMinStep: 1
        },{
          cType: types.TEMPERATURE_UNITS_CTYPE,
          perms: ["pr","ev"],
          format: "int",
          initialValue: 0,
          supportEvents: false,
          supportBonjour: false,
          manfDescription: "Unit",
        }]
      }];
  }
};
module.exports.accessory = [PilightSwitch, PilightThermostat];
module.exports.platform = PilightPlatform;
<|endoftext|>


<commit_before>/* globals Promise, fetch */
'use strict';

/**
 * The main controlling piece of the app. It composes the other modules.
 */

var chromeUdp = require('./chrome-apis/udp');
var datastore = require('./persistence/datastore');
var extBridge = require('./extension-bridge/messaging');
var fileSystem = require('./persistence/file-system');
var settings = require('./settings');
var dnssdSem = require('./dnssd/dns-sd-semcache');
var serverApi = require('./server/server-api');
var dnsController = require('./dnssd/dns-controller');

var LISTENING_HTTP_INTERFACE = null;

var ABS_PATH_TO_BASE_DIR = null;

exports.SERVERS_STARTED = false;

/**
 * Struggling to mock this during testing with proxyquire, so use this as a
 * level of redirection.
 */
exports.getServerController = function() {
  return require('./server/server-controller');
};

/**
 * Get the interface on which the app is listening for incoming http
 * connections.
 *
 * @return {object} an object of the form:
 * {
 *   name: string,
 *   address: string,
 *   prefixLength: integer,
 * }
 */
exports.getListeningHttpInterface = function() {
  if (!LISTENING_HTTP_INTERFACE) {
    console.warn('listening http interface not set, is app started?');
  }
  return LISTENING_HTTP_INTERFACE;
};

/**
 * Set the absolute path to the base directory where SemCache is mounted on
 * the file system.
 *
 * This is necessary because, unbelievably, Chrome Apps don't provide a way to
 * access the absolute path of a file. This means that in the prototype users
 * will have to manually type the full path to the directory they choose.
 * Unfortunate.
 *
 * @param {string} absPath the absolute path to the base directory of the file
 * system where SemCache is mounted
 */
exports.setAbsPathToBaseDir = function(absPath) {
  ABS_PATH_TO_BASE_DIR = absPath;
};

/**
 * @return {string} the URL for the list of pages in this device's own cache
 */
exports.getListUrlForSelf = function() {
  var iface = exports.getListeningHttpInterface();
  var host = iface.address;
  var port = iface.port;
  var result = serverApi.getListPageUrlForCache(host, port);
  return result;
};

/**
 * @return {object} the cache object that represents this machine's own cache.
 */
exports.getOwnCache = function() {
  var instanceName = settings.getInstanceName();
  var serverPort = settings.getServerPort();
  var hostName = settings.getHostName();
  var ipAddress = exports.getListeningHttpInterface().address;
  var listUrl = serverApi.getListPageUrlForCache(ipAddress, serverPort);

  var result = {
    domainName: hostName,
    instanceName: instanceName,
    port: serverPort,
    ipAddress: ipAddress,
    listUrl: listUrl
  };
  return result;
};

/**
 * @return {boolean} true if we have turned on the network
 */
exports.networkIsActive = function() {
  return exports.SERVERS_STARTED;
};

/**
 * Obtain an Array of all the caches that can be browsed on the current local
 * network.
 *
 * The current machine's cache is always returned, and is always the first
 * element in the array.
 *
 * @return {Promise} Promise that resolves with an Array of object representing
 * operational info for each cache. An example element:
 * {
 *   domainName: 'laptop.local',
 *   instanceName: 'My Cache._semcache._tcp.local',
 *   ipAddress: '1.2.3.4',
 *   port: 1111,
 *   listUrl: 'http://1.2.3.4:1111/list_pages'
 * }
 */
exports.getBrowseableCaches = function() {
  // First we'll construct our own cache info. Some of these variables may not
  // be set if we are initializing for the first time and settings haven't been
  // created.
  var thisCache = exports.getOwnCache();

  var result = [thisCache];

  if (!exports.networkIsActive()) {
    // When we shouldn't query the network.
    return Promise.resolve(result);
  }

  var ipAddress = exports.getListeningHttpInterface().address;

  return new Promise(function(resolve) {
    dnssdSem.browseForSemCacheInstances()
      .then(instances => {
        // sort by instance name.
        instances.sort(function(a, b) {
          return a.instanceName.localeCompare(b.instanceName);
        });
        instances.forEach(instance => {
          if (instance.ipAddress === ipAddress) {
            // We've found ourselves. Don't add it.
            return;
          }
          instance.listUrl = serverApi.getListPageUrlForCache(
            instance.ipAddress,
            instance.port
          );
          result.push(instance);
        });
        resolve(result);
      });
  });
};

/**
 * Start the mDNS, DNS-SD, and HTTP servers and register the local instance on
 * the network.
 *
 * @return {Promise} Promise that resolves if the service starts successfully,
 * else rejects with a message as to why.
 */
exports.startServersAndRegister = function() {
  return new Promise(function(resolve, reject) {
    var instanceName = settings.getInstanceName();
    var serverPort = settings.getServerPort();
    var baseDirId = settings.getBaseDirId();
    var hostName = settings.getHostName();
    var httpIface = '0.0.0.0';
    if (!instanceName || !serverPort || !baseDirId || !hostName) {
      reject('Complete and save settings before starting');
      return;
    }

    exports.updateCachesForSettings();
    dnsController.start()
    .then(() => {
      return dnssdSem.registerSemCache(hostName, instanceName, serverPort);
    })
    .then(registerResult => {
      console.log('REGISTERED: ', registerResult);
      exports.getServerController().start(httpIface, serverPort);
      exports.SERVERS_STARTED = true;
      resolve(registerResult);
    })
    .catch(rejected => {
      console.log('REJECTED: ', rejected);
      reject(rejected);
    });
  });
};

/**
 * The counterpart method to startServersAndRegister().
 */
exports.stopServers = function() {
  exports.getServerController().stop();
  dnsController.clearAllRecords();
  exports.SERVERS_STARTED = false;
};

/**
 * Start the app.
 *
 * @return {Promise} Promise that resolves when the app is started
 */
exports.start = function() {
  extBridge.attachListeners();

  return new Promise(function(resolve) {
    chromeUdp.getNetworkInterfaces()
      .then(interfaces => {
        var ipv4Interfaces = [];
        interfaces.forEach(nwIface => {
          if (nwIface.address.indexOf(':') === -1) {
            // ipv4
            ipv4Interfaces.push(nwIface);
          }
        });
        if (ipv4Interfaces.length === 0) {
          console.log('Could not find ipv4 interface: ', interfaces);
        } else {
          var iface = ipv4Interfaces[0];
          LISTENING_HTTP_INTERFACE = iface;
        }
      })
      .then(() => {
        return settings.init();
      })
      .then(settingsObj => {
        console.log('initialized settings: ', settingsObj);
        exports.updateCachesForSettings();
        resolve();
      });
  });
};

/**
 * Update the local state of the controller with the current settings.
 */
exports.updateCachesForSettings = function() {
  exports.setAbsPathToBaseDir(settings.getAbsPath());
  LISTENING_HTTP_INTERFACE.port = settings.getServerPort();
};

/**
 * A thin wrapper around fetch to allow mocking during tests. Expose parameters
 * are needed.
 */
exports.fetch = function(url) {
  return fetch(url);
};

/**
 * Return the absolute path to the base directory where SemCache is mounted on
 * the file system.
 *
 * This is necessary because, unbelievably, Chrome Apps don't provide a way to
 * access the absolute path of a file. This means that in the prototype users
 * will have to manually type the full path to the directory they choose.
 * Unfortunate.
 *
 * @return {string} absolute path to the base directory, without a trailing
 * slash
 */
exports.getAbsPathToBaseDir = function() {
  return ABS_PATH_TO_BASE_DIR;
};

/**
 * Save the MHTML file at mhtmlUrl into the local cache and open the URL.
 *
 * @param {captureUrl} captureUrl
 * @param {captureDate} captureDate
 * @param {string} mhtmlUrl the url of the mhtml file to save and open
 *
 * @return {Promise} a Promise that resolves after open has been called.
 */
exports.saveMhtmlAndOpen = function(captureUrl, captureDate, mhtmlUrl) {
  return new Promise(function(resolve) {
    exports.fetch(mhtmlUrl)
      .then(response => {
        return response.blob();
      })
      .then(mhtmlBlob => {
        return datastore.addPageToCache(captureUrl, captureDate, mhtmlBlob);
      })
      .then((entry) => {
        var fileUrl = fileSystem.constructFileSchemeUrl(
          exports.getAbsPathToBaseDir(),
          entry.fullPath
        );
        extBridge.sendMessageToOpenUrl(fileUrl);
        resolve();
      });
  });
};
<commit_msg>Add TODO note to grab the metadata object<commit_after>/* globals Promise, fetch */
'use strict';

/**
 * The main controlling piece of the app. It composes the other modules.
 */

var chromeUdp = require('./chrome-apis/udp');
var datastore = require('./persistence/datastore');
var extBridge = require('./extension-bridge/messaging');
var fileSystem = require('./persistence/file-system');
var settings = require('./settings');
var dnssdSem = require('./dnssd/dns-sd-semcache');
var serverApi = require('./server/server-api');
var dnsController = require('./dnssd/dns-controller');

var LISTENING_HTTP_INTERFACE = null;

var ABS_PATH_TO_BASE_DIR = null;

exports.SERVERS_STARTED = false;

/**
 * Struggling to mock this during testing with proxyquire, so use this as a
 * level of redirection.
 */
exports.getServerController = function() {
  return require('./server/server-controller');
};

/**
 * Get the interface on which the app is listening for incoming http
 * connections.
 *
 * @return {object} an object of the form:
 * {
 *   name: string,
 *   address: string,
 *   prefixLength: integer,
 * }
 */
exports.getListeningHttpInterface = function() {
  if (!LISTENING_HTTP_INTERFACE) {
    console.warn('listening http interface not set, is app started?');
  }
  return LISTENING_HTTP_INTERFACE;
};

/**
 * Set the absolute path to the base directory where SemCache is mounted on
 * the file system.
 *
 * This is necessary because, unbelievably, Chrome Apps don't provide a way to
 * access the absolute path of a file. This means that in the prototype users
 * will have to manually type the full path to the directory they choose.
 * Unfortunate.
 *
 * @param {string} absPath the absolute path to the base directory of the file
 * system where SemCache is mounted
 */
exports.setAbsPathToBaseDir = function(absPath) {
  ABS_PATH_TO_BASE_DIR = absPath;
};

/**
 * @return {string} the URL for the list of pages in this device's own cache
 */
exports.getListUrlForSelf = function() {
  var iface = exports.getListeningHttpInterface();
  var host = iface.address;
  var port = iface.port;
  var result = serverApi.getListPageUrlForCache(host, port);
  return result;
};

/**
 * @return {object} the cache object that represents this machine's own cache.
 */
exports.getOwnCache = function() {
  var instanceName = settings.getInstanceName();
  var serverPort = settings.getServerPort();
  var hostName = settings.getHostName();
  var ipAddress = exports.getListeningHttpInterface().address;
  var listUrl = serverApi.getListPageUrlForCache(ipAddress, serverPort);

  var result = {
    domainName: hostName,
    instanceName: instanceName,
    port: serverPort,
    ipAddress: ipAddress,
    listUrl: listUrl
  };
  return result;
};

/**
 * @return {boolean} true if we have turned on the network
 */
exports.networkIsActive = function() {
  return exports.SERVERS_STARTED;
};

/**
 * Obtain an Array of all the caches that can be browsed on the current local
 * network.
 *
 * The current machine's cache is always returned, and is always the first
 * element in the array.
 *
 * @return {Promise} Promise that resolves with an Array of object representing
 * operational info for each cache. An example element:
 * {
 *   domainName: 'laptop.local',
 *   instanceName: 'My Cache._semcache._tcp.local',
 *   ipAddress: '1.2.3.4',
 *   port: 1111,
 *   listUrl: 'http://1.2.3.4:1111/list_pages'
 * }
 */
exports.getBrowseableCaches = function() {
  // First we'll construct our own cache info. Some of these variables may not
  // be set if we are initializing for the first time and settings haven't been
  // created.
  var thisCache = exports.getOwnCache();

  var result = [thisCache];

  if (!exports.networkIsActive()) {
    // When we shouldn't query the network.
    return Promise.resolve(result);
  }

  var ipAddress = exports.getListeningHttpInterface().address;

  return new Promise(function(resolve) {
    dnssdSem.browseForSemCacheInstances()
      .then(instances => {
        // sort by instance name.
        instances.sort(function(a, b) {
          return a.instanceName.localeCompare(b.instanceName);
        });
        instances.forEach(instance => {
          if (instance.ipAddress === ipAddress) {
            // We've found ourselves. Don't add it.
            return;
          }
          instance.listUrl = serverApi.getListPageUrlForCache(
            instance.ipAddress,
            instance.port
          );
          result.push(instance);
        });
        resolve(result);
      });
  });
};

/**
 * Start the mDNS, DNS-SD, and HTTP servers and register the local instance on
 * the network.
 *
 * @return {Promise} Promise that resolves if the service starts successfully,
 * else rejects with a message as to why.
 */
exports.startServersAndRegister = function() {
  return new Promise(function(resolve, reject) {
    var instanceName = settings.getInstanceName();
    var serverPort = settings.getServerPort();
    var baseDirId = settings.getBaseDirId();
    var hostName = settings.getHostName();
    var httpIface = '0.0.0.0';
    if (!instanceName || !serverPort || !baseDirId || !hostName) {
      reject('Complete and save settings before starting');
      return;
    }

    exports.updateCachesForSettings();
    dnsController.start()
    .then(() => {
      return dnssdSem.registerSemCache(hostName, instanceName, serverPort);
    })
    .then(registerResult => {
      console.log('REGISTERED: ', registerResult);
      exports.getServerController().start(httpIface, serverPort);
      exports.SERVERS_STARTED = true;
      resolve(registerResult);
    })
    .catch(rejected => {
      console.log('REJECTED: ', rejected);
      reject(rejected);
    });
  });
};

/**
 * The counterpart method to startServersAndRegister().
 */
exports.stopServers = function() {
  exports.getServerController().stop();
  dnsController.clearAllRecords();
  exports.SERVERS_STARTED = false;
};

/**
 * Start the app.
 *
 * @return {Promise} Promise that resolves when the app is started
 */
exports.start = function() {
  extBridge.attachListeners();

  return new Promise(function(resolve) {
    chromeUdp.getNetworkInterfaces()
      .then(interfaces => {
        var ipv4Interfaces = [];
        interfaces.forEach(nwIface => {
          if (nwIface.address.indexOf(':') === -1) {
            // ipv4
            ipv4Interfaces.push(nwIface);
          }
        });
        if (ipv4Interfaces.length === 0) {
          console.log('Could not find ipv4 interface: ', interfaces);
        } else {
          var iface = ipv4Interfaces[0];
          LISTENING_HTTP_INTERFACE = iface;
        }
      })
      .then(() => {
        return settings.init();
      })
      .then(settingsObj => {
        console.log('initialized settings: ', settingsObj);
        exports.updateCachesForSettings();
        resolve();
      });
  });
};

/**
 * Update the local state of the controller with the current settings.
 */
exports.updateCachesForSettings = function() {
  exports.setAbsPathToBaseDir(settings.getAbsPath());
  LISTENING_HTTP_INTERFACE.port = settings.getServerPort();
};

/**
 * A thin wrapper around fetch to allow mocking during tests. Expose parameters
 * are needed.
 */
exports.fetch = function(url) {
  return fetch(url);
};

/**
 * Return the absolute path to the base directory where SemCache is mounted on
 * the file system.
 *
 * This is necessary because, unbelievably, Chrome Apps don't provide a way to
 * access the absolute path of a file. This means that in the prototype users
 * will have to manually type the full path to the directory they choose.
 * Unfortunate.
 *
 * @return {string} absolute path to the base directory, without a trailing
 * slash
 */
exports.getAbsPathToBaseDir = function() {
  return ABS_PATH_TO_BASE_DIR;
};

/**
 * Save the MHTML file at mhtmlUrl into the local cache and open the URL.
 *
 * @param {captureUrl} captureUrl
 * @param {captureDate} captureDate
 * @param {string} mhtmlUrl the url of the mhtml file to save and open
 *
 * @return {Promise} a Promise that resolves after open has been called.
 */
exports.saveMhtmlAndOpen = function(captureUrl, captureDate, mhtmlUrl) {
  // TODO: save the metadata object with the file!
  return new Promise(function(resolve) {
    exports.fetch(mhtmlUrl)
      .then(response => {
        return response.blob();
      })
      .then(mhtmlBlob => {
        return datastore.addPageToCache(captureUrl, captureDate, mhtmlBlob);
      })
      .then((entry) => {
        var fileUrl = fileSystem.constructFileSchemeUrl(
          exports.getAbsPathToBaseDir(),
          entry.fullPath
        );
        extBridge.sendMessageToOpenUrl(fileUrl);
        resolve();
      });
  });
};
<|endoftext|>


<commit_before>(function(angular) {

  var idIncrementor = 0;

  angular
    .module('mwl.confirm', ['ngSanitize', 'ui.bootstrap.position', 'offClick'])
    .controller('PopoverConfirmCtrl', function($scope, $element, $compile, $timeout, $document, $window, $position) {
      var vm = this;
      vm.placement = vm.placement || 'top';

      if (!$element.attr('id')) {
        $element.attr('id', 'popover-trigger-' + idIncrementor++);
      }
      $scope.triggerSelector = '#' + $element.attr('id');

      var template = [
        '
',
          '

',
          '

',
          '
',
            '

',
            '
',
              '
',
                '<button class="btn btn-{{ vm.confirmButtonType || \'danger\' }} btn-block" ng-click="vm.onConfirm(); vm.hidePopover()" ng-bind-html="vm.confirmText || \'Confirm\'"></button>',
              '
',
              '
',
                '<button class="btn btn-{{ vm.cancelButtonType || \'default\' }} btn-block" ng-click="vm.onCancel(); vm.hidePopover()" ng-bind-html="vm.cancelText || \'Cancel\'"></button>',
              '
',
            '
',
          '
',
        '
'
      ].join('\n');

      var popover = angular.element(template);
      $compile(popover)($scope);
      $document.find('body').append(popover);

      vm.isVisible = false;

      function positionPopover() {
        var position = $position.positionElements($element, popover, vm.placement, true);
        position.top += 'px';
        position.left += 'px';
        popover.css(position);
      }

      function showPopover() {
        if (!vm.isVisible) {
          popover.css({display: 'block'});
          positionPopover();
          vm.isVisible = true;
        }
      }

      function hidePopover() {
        if (vm.isVisible) {
          popover.css({display: 'none'});
          vm.isVisible = false;
        }
      }

      function togglePopover() {
        if (!vm.isVisible) {
          showPopover();
        } else {
          hidePopover();
        }
      }

      vm.showPopover = showPopover;
      vm.hidePopover = hidePopover;
      vm.togglePopover = togglePopover;

      $element.bind('click', togglePopover);

      $window.addEventListener('resize', positionPopover);

      var unbindOnDestroy = $scope.$on('$destroy', function() {
        unbindOnDestroy();
        popover.remove();
        $element.unbind('click', togglePopover);
        $window.removeEventListener('resize', positionPopover);
      });

    })
    .directive('mwlConfirm', function() {

      return {
        restrict: 'EA',
        controller: 'PopoverConfirmCtrl as vm',
        bindToController: true,
        scope: {
          confirmText: '@',
          cancelText: '@',
          message: '@',
          title: '@',
          placement: '@',
          onConfirm: '&',
          onCancel: '&',
          confirmButtonType: '@',
          cancelButtonType: '@'
        }
      };
    });

})(angular);
<commit_msg>Remove forced width<commit_after>(function(angular) {

  var idIncrementor = 0;

  angular
    .module('mwl.confirm', ['ngSanitize', 'ui.bootstrap.position', 'offClick'])
    .controller('PopoverConfirmCtrl', function($scope, $element, $compile, $timeout, $document, $window, $position) {
      var vm = this;
      vm.placement = vm.placement || 'top';

      if (!$element.attr('id')) {
        $element.attr('id', 'popover-trigger-' + idIncrementor++);
      }
      $scope.triggerSelector = '#' + $element.attr('id');

      var template = [
        '
',
          '

',
          '

',
          '
',
            '

',
            '
',
              '
',
                '<button class="btn btn-{{ vm.confirmButtonType || \'danger\' }} btn-block" ng-click="vm.onConfirm(); vm.hidePopover()" ng-bind-html="vm.confirmText || \'Confirm\'"></button>',
              '
',
              '
',
                '<button class="btn btn-{{ vm.cancelButtonType || \'default\' }} btn-block" ng-click="vm.onCancel(); vm.hidePopover()" ng-bind-html="vm.cancelText || \'Cancel\'"></button>',
              '
',
            '
',
          '
',
        '
'
      ].join('\n');

      var popover = angular.element(template);
      $compile(popover)($scope);
      $document.find('body').append(popover);

      vm.isVisible = false;

      function positionPopover() {
        var position = $position.positionElements($element, popover, vm.placement, true);
        position.top += 'px';
        position.left += 'px';
        popover.css(position);
      }

      function showPopover() {
        if (!vm.isVisible) {
          popover.css({display: 'block'});
          positionPopover();
          vm.isVisible = true;
        }
      }

      function hidePopover() {
        if (vm.isVisible) {
          popover.css({display: 'none'});
          vm.isVisible = false;
        }
      }

      function togglePopover() {
        if (!vm.isVisible) {
          showPopover();
        } else {
          hidePopover();
        }
      }

      vm.showPopover = showPopover;
      vm.hidePopover = hidePopover;
      vm.togglePopover = togglePopover;

      $element.bind('click', togglePopover);

      $window.addEventListener('resize', positionPopover);

      var unbindOnDestroy = $scope.$on('$destroy', function() {
        unbindOnDestroy();
        popover.remove();
        $element.unbind('click', togglePopover);
        $window.removeEventListener('resize', positionPopover);
      });

    })
    .directive('mwlConfirm', function() {

      return {
        restrict: 'EA',
        controller: 'PopoverConfirmCtrl as vm',
        bindToController: true,
        scope: {
          confirmText: '@',
          cancelText: '@',
          message: '@',
          title: '@',
          placement: '@',
          onConfirm: '&',
          onCancel: '&',
          confirmButtonType: '@',
          cancelButtonType: '@'
        }
      };
    });

})(angular);
<|endoftext|>


<commit_before>var KallyRazor = require('../lib/main');
var Parser = require('../lib/parser');

var razor = KallyRazor();
var parser = Parser();
var start;
var contents;
var i;

start = Date.now();
contents = "This is a very small test @model.name";
for (i = 0; i < 1000000; i++) {
    parser.parseRazorContents(contents);
}
console.log('Parsing razor token ' + i + ' times in ' + (Date.now() - start) + ' ms');<commit_msg>Checking for ASCII identifiers first trimms off a few hundred miliseconds.<commit_after>var KallyRazor = require('../lib/main');
var Parser = require('../lib/parser');

var razor = KallyRazor();
var parser = Parser();
var start;
var contents;
var i;

start = Date.now();
contents = "This is a very small test @model.name";
for (i = 0; i < 1000000; i++) {
    parser.parseRazorContents(contents);
}
console.log('Parsing razor contents ' + i + ' times in ' + (Date.now() - start) + ' ms');<|endoftext|>


<commit_before>//loader.includeJS("https://w.soundcloud.com/player/api.js");

window.$ = window.$ || function(){return window.$};
$.getScript = $.getScript || function(js,cb){loader.includeJS(js,cb);};

function SoundCloudPlayer(){
	return SoundCloudPlayer.super_.apply(this, arguments);
};

(function() {
	var EVENT_MAP = {
			"onplay": "onPlaying",
			"onresume": "onPlaying",
			"onpause": "onPaused",
			"onstop": "onPaused",
			"onfinish": "onEnded"
		},
		ERROR_EVENTS = [
			"onerror",
			"ontimeout",
			"onfailure",
			"ondataerror"
		];

	function Player(eventHandlers, embedVars) {
		this.label = 'SoundCloud';
		this.eventHandlers = eventHandlers || {};
		this.embedVars = embedVars || {};
		this.element = null;
		this.widget = null;
		this.isReady = false;
		this.trackInfo = {};
		this.soundOptions = {autoPlay:true};

		var that = this;
		$.getScript("https://connect.soundcloud.com/sdk.js", function() {
			SC.initialize({client_id: SOUNDCLOUD_CLIENT_ID});
			for (var i in EVENT_MAP)
				(function(i) {
					that.soundOptions[i] = function() {
						//console.log("SC event:", i /*, this*/);
						var handler = eventHandlers[EVENT_MAP[i]];
						handler && handler(that);
					}
				})(i);
			ERROR_EVENTS.map(function(evt){
				that.soundOptions[evt] = function(e) {
					console.error("SC error:", evt, e, e.stack);
					that.eventHandlers.onError && that.eventHandlers.onError(that, {code:evt.substr(2), source:"SoundCloudPlayer"});
				};
			});
			that.isReady = true;
			soundManager.onready(function() {
				that.callHandler("onApiReady", that); // eventHandlers.onApiReady && eventHandlers.onApiReady(that);
			});
		});

		this.callHandler = function(name, params) {
			try {
				eventHandlers[name] && eventHandlers[name](params);//.apply(null, params);
			}
			catch (e) {
				console.error("SC error:", e, e.stack);
			}
		}
	}

	Player.prototype.safeCall = function(fctName, param) {
		try {
			//console.log("SC safecall", fctName);
			if (this.widget && this.widget[fctName])
				this.widget[fctName](param);
		}
		catch(e) {
			console.error("SC safecall error", e.stack);
		}
	}

	Player.prototype.getEid = function(url) {
		var matches = /(?:https?:)?\/\/(?:www\.)?soundcloud\.com\/([\w-_\/]+)/.exec(url);
		return matches ? url.substr(url.lastIndexOf("/")+1) : null;
	}

	Player.prototype.getTrackPosition = function(callback) {
		callback(this.trackInfo.position = this.widget.position / 1000);
		if (this.widget.durationEstimate)
			this.eventHandlers.onTrackInfo && this.eventHandlers.onTrackInfo({
				duration: this.widget.duration / 1000
			});
	};

	Player.prototype.setTrackPosition = function(pos) {
		this.safeCall("setPosition", pos * 1000);
	};

	Player.prototype.play = function(id) {
		this.trackInfo = {};
		this.embedVars.trackId = id;
		//console.log("soundcloud play", this.embedVars);
		var that = this;

		SC.stream("/tracks/"+id, this.soundOptions, function(sound){
			that.widget = sound;
			that.callHandler("onEmbedReady", that);
			//that.safeCall("play");
		});
	}

	Player.prototype.resume = function() {
		this.safeCall("play");
	}

	Player.prototype.pause = function() {
		this.safeCall("pause");
	}

	Player.prototype.stop = function() {
		this.safeCall("stop");
	}

	Player.prototype.setVolume = function(vol) {
		this.safeCall("setVolume", 100 * vol);
	}

	//inherits(SoundCloudPlayer, Player);
	SoundCloudPlayer.prototype = Player.prototype;
	SoundCloudPlayer.super_ = Player;
	// this method exports Player under the name "SoundCloudPlayer", even after minification
	// so that SoundCloudPlayer.name == "SoundCloudPlayer" instead of SoundCloudPlayer.name == "Player"
})();
<commit_msg>support http and https, and whyd/eId urls. resolve short URLs.<commit_after>//loader.includeJS("https://w.soundcloud.com/player/api.js");

window.$ = window.$ || function(){return window.$};
$.getScript = $.getScript || function(js,cb){loader.includeJS(js,cb);};
$.getJSON = $.getJSON || function(url, cb){
  var cbName = "_cb_" + Date.now();
  url = url.replace("callback=?", "callback=" + cbName);
  window[cbName] = function(){
    cb.apply(window, arguments);
    delete window[cbName];
  };
  loader.includeJS(url);
};

function SoundCloudPlayer(){
	return SoundCloudPlayer.super_.apply(this, arguments);
};

(function() {
	var EVENT_MAP = {
			"onplay": "onPlaying",
			"onresume": "onPlaying",
			"onpause": "onPaused",
			"onstop": "onPaused",
			"onfinish": "onEnded"
		},
		ERROR_EVENTS = [
			"onerror",
			"ontimeout",
			"onfailure",
			"ondataerror"
		];

	function Player(eventHandlers, embedVars) {
		this.label = 'SoundCloud';
		this.eventHandlers = eventHandlers || {};
		this.embedVars = embedVars || {};
		this.element = null;
		this.widget = null;
		this.isReady = false;
		this.trackInfo = {};
		this.soundOptions = {autoPlay:true};

		var that = this;
		$.getScript("https://connect.soundcloud.com/sdk.js", function() {
			SC.initialize({client_id: SOUNDCLOUD_CLIENT_ID});
			for (var i in EVENT_MAP)
				(function(i) {
					that.soundOptions[i] = function() {
						//console.log("SC event:", i /*, this*/);
						var handler = eventHandlers[EVENT_MAP[i]];
						handler && handler(that);
					}
				})(i);
			ERROR_EVENTS.map(function(evt){
				that.soundOptions[evt] = function(e) {
					console.error("SC error:", evt, e, e.stack);
					that.eventHandlers.onError && that.eventHandlers.onError(that, {code:evt.substr(2), source:"SoundCloudPlayer"});
				};
			});
			that.isReady = true;
			soundManager.onready(function() {
				that.callHandler("onApiReady", that); // eventHandlers.onApiReady && eventHandlers.onApiReady(that);
			});
		});

		this.callHandler = function(name, params) {
			try {
				eventHandlers[name] && eventHandlers[name](params);//.apply(null, params);
			}
			catch (e) {
				console.error("SC error:", e, e.stack);
			}
		}
	}

	Player.prototype.safeCall = function(fctName, param) {
		try {
			//console.log("SC safecall", fctName);
			if (this.widget && this.widget[fctName])
				this.widget[fctName](param);
		}
		catch(e) {
			console.error("SC safecall error", e.stack);
		}
	}

	Player.prototype.getEid = function(url) {
		if (/(soundcloud\.com)\/player\/\?.*url\=(.+)/.test(url))
			url = decodeURIComponent(RegExp.lastParen);
		if (/(soundcloud\.com)(\/[\w-_\/]+)/.test(url))
			return RegExp.lastParen; //url.substr(url.lastIndexOf("/")+1);
		else if (/snd\.sc\/([\w-_]+)/.test(url))
			return RegExp.lastMatch;
		// => returns:
		// - /tracks/<number> (ready to stream)
		// - or /<artistname>/<tracktitle>
		// - or snd.sc/<hash>
	}

	Player.prototype.getTrackPosition = function(callback) {
		callback(this.trackInfo.position = this.widget.position / 1000);
		if (this.widget.durationEstimate)
			this.eventHandlers.onTrackInfo && this.eventHandlers.onTrackInfo({
				duration: this.widget.duration / 1000
			});
	};

	Player.prototype.setTrackPosition = function(pos) {
		this.safeCall("setPosition", pos * 1000);
	};

	Player.prototype.play = function(id) {
		console.log("sc PLAY id:", id)
		this.trackInfo = {};
		var that = this;
		function playId(id){
			console.log("=> sc PLAY id:", id)
			that.embedVars.trackId = id;
			//console.log("soundcloud play", this.embedVars);
			SC.stream(id, that.soundOptions, function(sound){
				that.widget = sound;
				that.callHandler("onEmbedReady", that);
				//that.safeCall("play");
			});
		}
		if (id.indexOf("/tracks/") == 0)
			return playId(id);
		id = "http://" + (!id.indexOf("/") ? "soundcloud.com" : "") + id;
		console.log("sc resolve url:", id);
		$.getJSON("https://api.soundcloud.com/resolve.json?client_id=" + SOUNDCLOUD_CLIENT_ID + "&url=" + encodeURIComponent(id) + "&callback=?", function(data){
			playId(data.id);
		});
	}

	Player.prototype.resume = function() {
		this.safeCall("play");
	}

	Player.prototype.pause = function() {
		this.safeCall("pause");
	}

	Player.prototype.stop = function() {
		this.safeCall("stop");
	}

	Player.prototype.setVolume = function(vol) {
		this.safeCall("setVolume", 100 * vol);
	}

	//inherits(SoundCloudPlayer, Player);
	SoundCloudPlayer.prototype = Player.prototype;
	SoundCloudPlayer.super_ = Player;
	// this method exports Player under the name "SoundCloudPlayer", even after minification
	// so that SoundCloudPlayer.name == "SoundCloudPlayer" instead of SoundCloudPlayer.name == "Player"
})();
<|endoftext|>


<commit_before>/**
 * COMMON WEBPACK CONFIGURATION
 */

const path = require('path');
const webpack = require('webpack');

module.exports = (options) => ({
  entry: options.entry,
  output: Object.assign({ // Compile into js/build.js
    path: path.resolve(process.cwd(), 'build'),
    publicPath: '/',
  }, options.output), // Merge with env dependent settings
  module: {
    loaders: [{
      test: /\.js$/, // Transform all .js files required somewhere with Babel
      loader: 'babel-loader',
      exclude: /node_modules/,
      query: options.babelQuery,
    }, {
      // Do not transform vendor's CSS with CSS-modules
      // The point is that they remain in global scope.
      // Since we require these CSS files in our JS or CSS files,
      // they will be a part of our compilation either way.
      // So, no need for ExtractTextPlugin here.
      test: /\.css$/,
      include: /node_modules/,
      loaders: ['style-loader', 'css-loader'],
    }, {
  // Transform our own .css files with PostCSS and CSS-modules
      test: /\.css$/,
      exclude: /node_modules/,
      use: ['style-loader', 'css-loader'],
    }, {
      // Do not transform vendor's CSS with CSS-modules
      // The point is that they remain in global scope.
      // Since we require these CSS files in our JS or CSS files,
      // they will be a part of our compilation either way.
      // So, no need for ExtractTextPlugin here.
      test: /\.css$/,
      include: /node_modules/,
      use: ['style-loader', 'css-loader'],
    },
    {
      test: /\.(eot|svg|ttf|woff|woff2)$/,
      loader: 'file-loader',
    }, {
      test: /\.(jpg|png|gif)$/,
      loaders: [
        'file-loader',
        {
          loader: 'image-webpack-loader',
          query: {
            progressive: true,
            optimizationLevel: 7,
            interlaced: false,
            pngquant: {
              quality: '65-90',
              speed: 4,
            },
          },
        },
      ],
    }, {
      test: /\.html$/,
      loader: 'html-loader',
    }, {
      test: /\.json$/,
      loader: 'json-loader',
    }, {
      test: /\.(mp4|webm)$/,
      loader: 'url-loader',
      query: {
        limit: 10000,
      },
    }],
  },
  plugins: options.plugins.concat([
    new webpack.ProvidePlugin({
      // make fetch available
      fetch: 'exports-loader?self.fetch!whatwg-fetch',
    }),

    // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
    // inside your code for any environment checks; UglifyJS will automatically
    // drop any unreachable code.
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV),
      },
    }),
    new webpack.NamedModulesPlugin(),
  ]),
  resolve: {
    modules: ['app', 'node_modules'],
    extensions: [
      '.js',
      '.jsx',
      '.react.js',
    ],
    mainFields: [
      'browser',
      'jsnext:main',
      'main',
    ],
  },
  devtool: options.devtool,
  target: 'web', // Make web variables accessible to webpack, e.g. window
  performance: options.performance || {},
});
<commit_msg>fix webpack.base.babel.js<commit_after>/**
 * COMMON WEBPACK CONFIGURATION
 */

const path = require('path');
const webpack = require('webpack');

module.exports = (options) => ({
  entry: options.entry,
  output: Object.assign({ // Compile into js/build.js
    path: path.resolve(process.cwd(), 'build'),
    publicPath: '/',
  }, options.output), // Merge with env dependent settings
  module: {
    loaders: [{
      test: /\.js$/, // Transform all .js files required somewhere with Babel
      loader: 'babel-loader',
      exclude: /node_modules/,
      query: options.babelQuery,
    }, {
      // Do not transform vendor's CSS with CSS-modules
      // The point is that they remain in global scope.
      // Since we require these CSS files in our JS or CSS files,
      // they will be a part of our compilation either way.
      // So, no need for ExtractTextPlugin here.
      test: /\.css$/,
      include: [
        /node_modules/,
        path.resolve('app/app.css'),
      ],
      loaders: ['style-loader', 'css-loader'],
    }, {
      test: /\.(eot|svg|ttf|woff|woff2)$/,
      loader: 'file-loader',
    }, {
      test: /\.(jpg|png|gif)$/,
      loaders: [
        'file-loader',
        {
          loader: 'image-webpack-loader',
          query: {
            progressive: true,
            optimizationLevel: 7,
            interlaced: false,
            pngquant: {
              quality: '40-90',
              speed: 4,
            },
          },
        },
      ],
    }, {
      test: /\.html$/,
      loader: 'html-loader',
    }, {
      test: /\.json$/,
      loader: 'json-loader',
    }, {
      test: /\.(mp4|webm)$/,
      loader: 'url-loader',
      query: {
        limit: 10000,
      },
    }],
  },
  plugins: options.plugins.concat([
    new webpack.ProvidePlugin({
      // make fetch available
      fetch: 'exports-loader?self.fetch!whatwg-fetch',
    }),

    // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
    // inside your code for any environment checks; UglifyJS will automatically
    // drop any unreachable code.
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV),
      },
    }),
    new webpack.NamedModulesPlugin(),
  ]),
  resolve: {
    modules: ['app', 'node_modules'],
    extensions: [
      '.js',
      '.jsx',
      '.react.js',
    ],
    mainFields: [
      'browser',
      'jsnext:main',
      'main',
    ],
  },
  devtool: options.devtool,
  target: 'web', // Make web variables accessible to webpack, e.g. window
  performance: options.performance || {},
});
<|endoftext|>


<commit_before>/**
 * COMMON WEBPACK CONFIGURATION
 */

const path = require('path');
const webpack = require('webpack');

module.exports = (options) => ({
  entry: options.entry,
  output: Object.assign({ // Compile into js/build.js
    path: path.resolve(process.cwd(), 'build'),
    publicPath: '/',
  }, options.output), // Merge with env dependent settings
  module: {
    loaders: [{
      test: /\.js$/, // Transform all .js files required somewhere with Babel
      loader: 'babel-loader',
      exclude: /node_modules/,
      query: options.babelQuery,
    }, {
      // Do not transform vendor's CSS with CSS-modules
      // The point is that they remain in global scope.
      // Since we require these CSS files in our JS or CSS files,
      // they will be a part of our compilation either way.
      // So, no need for ExtractTextPlugin here.
      test: /\.css$/,
      include: /node_modules/,
      loaders: ['style-loader', 'css-loader'],
    }, {
      test: /\.(eot|svg|ttf|woff|woff2)$/,
      loader: 'file-loader',
    }, {
      test: /\.(jpg|png|gif)$/,
      loaders: [
        'file-loader',
        {
          loader: 'image-webpack-loader',
          query: {
            mozjpeg: {
              progressive: true,
            },
            gifsicle: {
              interlaced: false,
            },
            optipng: {
              optimizationLevel: 7,
            },
            pngquant: {
              quality: '65-90',
              speed: 4,
            },
          },
        },
      ],
    }, {
      test: /\.html$/,
      loader: 'html-loader',
    }, {
      test: /\.json$/,
      loader: 'json-loader',
    }, {
      test: /\.(mp4|webm)$/,
      loader: 'url-loader',
      query: {
        limit: 10000,
      },
    }],
  },
  plugins: options.plugins.concat([
    new webpack.ProvidePlugin({
      // make fetch available
      fetch: 'exports-loader?self.fetch!whatwg-fetch',
    }),

    // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
    // inside your code for any environment checks; UglifyJS will automatically
    // drop any unreachable code.
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV),
      },
    }),
    new webpack.NamedModulesPlugin(),
  ]),
  resolve: {
    modules: ['app', 'node_modules'],
    extensions: [
      '.js',
      '.jsx',
      '.react.js',
    ],
    mainFields: [
      'browser',
      'jsnext:main',
      'main',
    ],
  },
  devtool: options.devtool,
  target: 'web', // Make web variables accessible to webpack, e.g. window
  performance: options.performance || {},
});
<commit_msg>push test<commit_after>/**
 * COMMON WEBPACK CONFIGURATION
 */

const path = require('path');
const webpack = require('webpack');

module.exports = (options) => ({
  entry: options.entry,
  output: Object.assign({ // Compile into js/build.js
    path: path.resolve(process.cwd(), 'build'),
    publicPath: '/',
  }, options.output), // Merge with env dependent settings
  module: {
    loaders: [{
      test: /\.js$/, // Transform all .js files required somewhere with Babel
      loader: 'babel-loader',
      exclude: /node_modules/,
      query: options.babelQuery,
    }, {
      // Do not transform vendor's CSS with CSS-modules
      // The point is that they remain in global scope.
      // Since we require these CSS files in our JS or CSS files,
      // they will be a part of our compilation either way.
      // So, no need for ExtractTextPlugin here.
      test: /\.css$/,
      include: /node_modules/,
      loaders: ['style-loader', 'css-loader'],
    }, {
      test: /\.(eot|svg|ttf|woff|woff2)$/,
      loader: 'file-loader',
    }, {
      test: /\.(jpg|png|gif)$/,
      loaders: [
        'file-loader',
        {
          loader: 'image-webpack-loader',
          query: {
            mozjpeg: {
              progressive: false,
            },
            gifsicle: {
              interlaced: false,
            },
            optipng: {
              optimizationLevel: 7,
            },
            pngquant: {
              quality: '65-90',
              speed: 4,
            },
          },
        },
      ],
    }, {
      test: /\.html$/,
      loader: 'html-loader',
    }, {
      test: /\.json$/,
      loader: 'json-loader',
    }, {
      test: /\.(mp4|webm)$/,
      loader: 'url-loader',
      query: {
        limit: 10000,
      },
    }],
  },
  plugins: options.plugins.concat([
    new webpack.ProvidePlugin({
      // make fetch available
      fetch: 'exports-loader?self.fetch!whatwg-fetch',
    }),

    // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
    // inside your code for any environment checks; UglifyJS will automatically
    // drop any unreachable code.
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV),
      },
    }),
    new webpack.NamedModulesPlugin(),
  ]),
  resolve: {
    modules: ['app', 'node_modules'],
    extensions: [
      '.js',
      '.jsx',
      '.react.js',
    ],
    mainFields: [
      'browser',
      'jsnext:main',
      'main',
    ],
  },
  devtool: options.devtool,
  target: 'web', // Make web variables accessible to webpack, e.g. window
  performance: options.performance || {},
});
<|endoftext|>