<commit_before>var service = null;
var privateGadgetNames = null;
var nameOfGadgetBeingEdited = '';
var entryOfGadgetBeingEdited = null;
var privateGadgetSpecFeedUrl = null;

var SERVICE_NAME = 'esp';
var APP_NAME = 'private-gadget-editor';

var SPINNER = '';
var DEFAULT_SPINNER_ID = 'spinner';

function $(id) {
  return document.getElementById(id);
};

function startSpinner(id) {
  $(id ? id : DEFAULT_SPINNER_ID).innerHTML = SPINNER;
};

function stopSpinner(id) {
  $(id ? id : DEFAULT_SPINNER_ID).innerHTML = '';
};

function initEditor() {
  if (privateGadgetSpecFeedUrl) {
    initGadgetNameList();
  }
};

function noCache(url) {
  return url + '?nocache=' + (new Date().getTime());
};

function setNameOfGadgetBeingEdited(name) {
  nameOfGadgetBeingEdited = name;
  if (name) {
    $('spec-url-wrapper').style.display = 'inline';
    var specUrl = 'http://www.google.com/a/feeds/server/g/domain/' +
        detectDomainName() + '/PrivateGadgetSpec/' + name;
    $('spec-url').href = specUrl;
  } else {
    $('spec-url-wrapper').style.display = 'none';
  }
};

function initGadgetNameList() {
  service = new google.gdata.client.FeedServerService(SERVICE_NAME, APP_NAME);
  service.setGadgetsAuthentication('SIGNED');
  service.getFeed(noCache(privateGadgetSpecFeedUrl), function(response) {
    stopSpinner();
    var entries = response && response.feed.entry ? response.feed.entry : [];
    showPrivateGadgetNames(setPrivateGadgetNames(entries));
  }, function(response) {
    stopSpinner()
    showMessage('Error: failed to load private gadget specs');
  });
  startSpinner();
};

function setPrivateGadgetNames(entries) {
  privateGadgetNames = [];
  for (var i = 0; i < entries.length; i++) {
    var name = entries[i].id.$t;
    name = name.substring(name.lastIndexOf('/') + 1);
    privateGadgetNames.push(name);
  }
  return privateGadgetNames;
};

function showPrivateGadgetNames() {
  var html = ['<select id="gadget-select" onchange="editSelectedGadget()">'];
  html.push('<option value="">Select to open</option>');
  for (var i = 0; i < privateGadgetNames.length; i++) {
    var privateGadgetName = privateGadgetNames[i];
    html.push('<option ', privateGadgetName == nameOfGadgetBeingEdited ? 'selected' : '',
        '>', privateGadgetName, '</option>');
  }
  document.getElementById('gadget-list').innerHTML = html.join('');
};

function getSelectedGadgetName() {
  return document.getElementById('gadget-select').value;
};

function editSelectedGadget() {
  var name = getSelectedGadgetName();
  if (name) {
    service.getEntry(noCache(privateGadgetSpecFeedUrl + '/' + name), function(response) {
      stopSpinner();
      setNameOfGadgetBeingEdited(name);
      entryOfGadgetBeingEdited = response.entry;
      editor.setCode(response.entry.content.entity.specContent);
      editor.editor.syntaxHighlight('init');
    }, function(response) {
      stopSpinner();
      showMessage('Error: failed to open gadget spec "' + name + '"');
    });
    startSpinner();
  } else {
    newGadget();
  }
};

function deleteSelectedGadget() {
  var name = getSelectedGadgetName();
  if (name) {
    service.deleteEntry(privateGadgetSpecFeedUrl + '/' + name, function(response) {
      stopSpinner();
      initEditor();
      newGadget();
    }, function(response) {
      stopSpinner();
      showMessage('Error: failed to delete gadget spec "' + name + '"');
    });
    startSpinner();
  } else {
    alert('Please select a gadget to delete.');
  }
};

function newGadget() {
  setNameOfGadgetBeingEdited('');
  editor.setCode(getGadgetSpecTemplate());
  editor.editor.syntaxHighlight('init');
  showPrivateGadgetNames();
  entryOfGadgetBeingEdited = {xmlns: 'http://www.w3.org/2005/Atom', content: {
      type: 'application/xml', entity: {name: '', specContent: ''}}};
};

function saveGadget(changeName) {
  entryOfGadgetBeingEdited.content.entity.specContent = editor.getCode();
  if (nameOfGadgetBeingEdited && !changeName) {
    service.updateEntry(privateGadgetSpecFeedUrl + '/' + nameOfGadgetBeingEdited,
        entryOfGadgetBeingEdited, function(response) {
      stopSpinner();
    }, function(error) {
      stopSpinner();
      showMessage('Error: failed to update gadget spec "' + nameOfGadgetBeingEdited + '"' + error);
    });
    startSpinner();
  } else {
    var name = changeName ?
      prompt('Please enter new name of gadget (e.g. hello.xml)') :
      prompt('Please enter name of new gadget (e.g. hello.xml)');
    if (!name) {
      return;
    }
    setNameOfGadgetBeingEdited(name);
    if (nameOfGadgetBeingEdited) {
      delete entryOfGadgetBeingEdited.content.entity.id;
      entryOfGadgetBeingEdited.content.entity.name = nameOfGadgetBeingEdited;
      service.insertEntry(privateGadgetSpecFeedUrl,
          entryOfGadgetBeingEdited, function(response) {
        stopSpinner();
        privateGadgetNames.push(nameOfGadgetBeingEdited);
        showPrivateGadgetNames();
      }, function(error) {
        stopSpinner();
        showMessage('Error: failed to add gadget spec "' + nameOfGadgetBeingEdited + '": ' + error);
      });
      startSpinner();
    }
  }
};

function openGadgetByUrl() {
  var gadgetSpecUrl = prompt('Please enter public URL of gadget spec');
  if (gadgetSpecUrl) {
    var params = {};
    params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
    gadgets.io.makeRequest(gadgetSpecUrl, function(response) {
      if (response.rc > 299) {
        showMessage('Error: ' + response.errors);
      } else {
        setNameOfGadgetBeingEdited(null);
        entryOfGadgetBeingEdited = {xmlns: 'http://www.w3.org/2005/Atom', content: {
          type: 'application/xml', entity: {name: '', specContent: response.text}}};
        editor.setCode(response.text);
        editor.editor.syntaxHighlight('init');
      }
    }, params);
  }
};

function getGadgetSpecTemplate() {
  var textArea = document.getElementById('gadget-spec-template');
  return textArea.value || textArea.defaultValue;
};

function showMessage(message) {
  var miniMessage = new gadgets.MiniMessage(null, document.getElementById('message-box'));
  miniMessage.createDismissibleMessage(message);
};

//// google.load('gdata', '1.x', {packages: ['core']});
// google.load('gdata', '1.x', {packages: ['core'], 'other_params': 'debug=1'});
// google.setOnLoadCallback(initEditor);

function initCodePress() {
  editor.style.height = '400px';
  CodePressWrapper.init();

  if (location.href.indexOf('codePressReloaded') < 0) {
    setTimeout(function() {
      if (!$('codepress-iframe').editor) {
        location.href += '&codePressReloaded';
        location.reload();
      }
    }, 500);
  }
};

function detectDomainName() {
  var params = location.href.split('&');
  for (var i = 0; i < params.length; i++) {
    if (params[i].indexOf('parent=') == 0) {
      var p = params[i].split('=');
      var parent = decodeURIComponent(p[1]);
      var r = /google.com\/a\/([^\/]*)\//.exec(parent);
      var domainName = r ? r[1] : null;
      return domainName;
    }
  }

  return null;
};

function initGadget() {
  var domainName = detectDomainName();
  if (domainName) {
    privateGadgetSpecFeedUrl = 'http://feedserver-enterprise.googleusercontent.com/a/' +
        domainName + '/g/PrivateGadgetSpec';
  } else {
    showMessage('Error: domain name missing');
  }

  gadgets.window.adjustHeight();
};

function init() {
  initCodePress();
  initGadget();
  initEditor();
};

gadgets.util.registerOnLoadHandler(init);
<commit_msg>added better domain detection to accommodate when there is a port number<commit_after>var service = null;
var privateGadgetNames = null;
var nameOfGadgetBeingEdited = '';
var entryOfGadgetBeingEdited = null;
var privateGadgetSpecFeedUrl = null;

var SERVICE_NAME = 'esp';
var APP_NAME = 'private-gadget-editor';

var SPINNER = '';
var DEFAULT_SPINNER_ID = 'spinner';

function $(id) {
  return document.getElementById(id);
};

function startSpinner(id) {
  $(id ? id : DEFAULT_SPINNER_ID).innerHTML = SPINNER;
};

function stopSpinner(id) {
  $(id ? id : DEFAULT_SPINNER_ID).innerHTML = '';
};

function initEditor() {
  if (privateGadgetSpecFeedUrl) {
    initGadgetNameList();
  }
};

function noCache(url) {
  return url + '?nocache=' + (new Date().getTime());
};

function setNameOfGadgetBeingEdited(name) {
  nameOfGadgetBeingEdited = name;
  if (name) {
    $('spec-url-wrapper').style.display = 'inline';
    var specUrl = 'http://www.google.com/a/feeds/server/g/domain/' +
        detectDomainName() + '/PrivateGadgetSpec/' + name;
    $('spec-url').href = specUrl;
  } else {
    $('spec-url-wrapper').style.display = 'none';
  }
};

function initGadgetNameList() {
  service = new google.gdata.client.FeedServerService(SERVICE_NAME, APP_NAME);
  service.setGadgetsAuthentication('SIGNED');
  service.getFeed(noCache(privateGadgetSpecFeedUrl), function(response) {
    stopSpinner();
    var entries = response && response.feed.entry ? response.feed.entry : [];
    showPrivateGadgetNames(setPrivateGadgetNames(entries));
  }, function(response) {
    stopSpinner()
    showMessage('Error: failed to load private gadget specs');
  });
  startSpinner();
};

function setPrivateGadgetNames(entries) {
  privateGadgetNames = [];
  for (var i = 0; i < entries.length; i++) {
    var name = entries[i].id.$t;
    name = name.substring(name.lastIndexOf('/') + 1);
    privateGadgetNames.push(name);
  }
  return privateGadgetNames;
};

function showPrivateGadgetNames() {
  var html = ['<select id="gadget-select" onchange="editSelectedGadget()">'];
  html.push('<option value="">Select to open</option>');
  for (var i = 0; i < privateGadgetNames.length; i++) {
    var privateGadgetName = privateGadgetNames[i];
    html.push('<option ', privateGadgetName == nameOfGadgetBeingEdited ? 'selected' : '',
        '>', privateGadgetName, '</option>');
  }
  document.getElementById('gadget-list').innerHTML = html.join('');
};

function getSelectedGadgetName() {
  return document.getElementById('gadget-select').value;
};

function editSelectedGadget() {
  var name = getSelectedGadgetName();
  if (name) {
    service.getEntry(noCache(privateGadgetSpecFeedUrl + '/' + name), function(response) {
      stopSpinner();
      setNameOfGadgetBeingEdited(name);
      entryOfGadgetBeingEdited = response.entry;
      editor.setCode(response.entry.content.entity.specContent);
      editor.editor.syntaxHighlight('init');
    }, function(response) {
      stopSpinner();
      showMessage('Error: failed to open gadget spec "' + name + '"');
    });
    startSpinner();
  } else {
    newGadget();
  }
};

function deleteSelectedGadget() {
  var name = getSelectedGadgetName();
  if (name) {
    service.deleteEntry(privateGadgetSpecFeedUrl + '/' + name, function(response) {
      stopSpinner();
      initEditor();
      newGadget();
    }, function(response) {
      stopSpinner();
      showMessage('Error: failed to delete gadget spec "' + name + '"');
    });
    startSpinner();
  } else {
    alert('Please select a gadget to delete.');
  }
};

function newGadget() {
  setNameOfGadgetBeingEdited('');
  editor.setCode(getGadgetSpecTemplate());
  editor.editor.syntaxHighlight('init');
  showPrivateGadgetNames();
  entryOfGadgetBeingEdited = {xmlns: 'http://www.w3.org/2005/Atom', content: {
      type: 'application/xml', entity: {name: '', specContent: ''}}};
};

function saveGadget(changeName) {
  entryOfGadgetBeingEdited.content.entity.specContent = editor.getCode();
  if (nameOfGadgetBeingEdited && !changeName) {
    service.updateEntry(privateGadgetSpecFeedUrl + '/' + nameOfGadgetBeingEdited,
        entryOfGadgetBeingEdited, function(response) {
      stopSpinner();
    }, function(error) {
      stopSpinner();
      showMessage('Error: failed to update gadget spec "' + nameOfGadgetBeingEdited + '"' + error);
    });
    startSpinner();
  } else {
    var name = changeName ?
      prompt('Please enter new name of gadget (e.g. hello.xml)') :
      prompt('Please enter name of new gadget (e.g. hello.xml)');
    if (!name) {
      return;
    }
    setNameOfGadgetBeingEdited(name);
    if (nameOfGadgetBeingEdited) {
      delete entryOfGadgetBeingEdited.content.entity.id;
      entryOfGadgetBeingEdited.content.entity.name = nameOfGadgetBeingEdited;
      service.insertEntry(privateGadgetSpecFeedUrl,
          entryOfGadgetBeingEdited, function(response) {
        stopSpinner();
        privateGadgetNames.push(nameOfGadgetBeingEdited);
        showPrivateGadgetNames();
      }, function(error) {
        stopSpinner();
        showMessage('Error: failed to add gadget spec "' + nameOfGadgetBeingEdited + '": ' + error);
      });
      startSpinner();
    }
  }
};

function openGadgetByUrl() {
  var gadgetSpecUrl = prompt('Please enter public URL of gadget spec');
  if (gadgetSpecUrl) {
    var params = {};
    params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
    gadgets.io.makeRequest(gadgetSpecUrl, function(response) {
      if (response.rc > 299) {
        showMessage('Error: ' + response.errors);
      } else {
        setNameOfGadgetBeingEdited(null);
        entryOfGadgetBeingEdited = {xmlns: 'http://www.w3.org/2005/Atom', content: {
          type: 'application/xml', entity: {name: '', specContent: response.text}}};
        editor.setCode(response.text);
        editor.editor.syntaxHighlight('init');
      }
    }, params);
  }
};

function getGadgetSpecTemplate() {
  var textArea = document.getElementById('gadget-spec-template');
  return textArea.value || textArea.defaultValue;
};

function showMessage(message) {
  var miniMessage = new gadgets.MiniMessage(null, document.getElementById('message-box'));
  miniMessage.createDismissibleMessage(message);
};

//// google.load('gdata', '1.x', {packages: ['core']});
// google.load('gdata', '1.x', {packages: ['core'], 'other_params': 'debug=1'});
// google.setOnLoadCallback(initEditor);

function initCodePress() {
  editor.style.height = '400px';
  CodePressWrapper.init();

  if (location.href.indexOf('codePressReloaded') < 0) {
    setTimeout(function() {
      if (!$('codepress-iframe').editor) {
        location.href += '&codePressReloaded';
        location.reload();
      }
    }, 500);
  }
};

function detectDomainName() {
  var params = location.href.split('&');
  for (var i = 0; i < params.length; i++) {
    if (params[i].indexOf('parent=') == 0) {
      var p = params[i].split('=');
      var parent = decodeURIComponent(p[1]);
      var r = /google.com(:[0-9]+)?\/a\/([^\/]*)\//.exec(parent);
      var domainName = r ? r[2] : null;
      return domainName;
    }
  }

  return null;
};

function initGadget() {
  var domainName = detectDomainName();
  if (domainName) {
    privateGadgetSpecFeedUrl = 'http://feedserver-enterprise.googleusercontent.com/a/' +
        domainName + '/g/PrivateGadgetSpec';
  } else {
    showMessage('Error: domain name missing');
  }

  gadgets.window.adjustHeight();
};

function init() {
  initCodePress();
  initGadget();
  initEditor();
};

gadgets.util.registerOnLoadHandler(init);
<|endoftext|>


<commit_before>(function( $ ) {

  /*
   * Configuration.
   */
  var key = ''; // TODO add your API key
  var datasource = function (products, callback) {
    // TODO add your data call
    $.getJSON(your_url, url_params, callback);
  };

  /**************************************************************************/

  /*
   * Main invocation method.
   */
  $.fn.thesocialdigits = function(api, args, template) {
    var elm = this;

    callAPI(api, args, function(data) {
      if('result' in data) {
        datasource(data.result, function(products) {
          buildHTML(elm, data.result, template, products);
        });
      }
    });
  };

  /*
   * Build HTML from template.
   */
  function buildHTML(elm, ids, template, _products) {
    // ensure the correct sorting of product data
    products = [];

    for (var i in ids) {
      var id = ids[i];

      for(var j in _products) {
        if(_products[j]['id'] == id) {
          products[i] = _products[j];
        }
      }
    }

    // generate the html from the template
    elm.append(renderTemplate($(template).html(), products));

    // add click logging
    for (var i in ids) {
      var id = ids[i];

      $('a[rel="__tsd-' + id + '"]').click({'id': id}, function(event) {
        var href = $(this).attr('href');
        setTimeout('window.location.href = "' + href + '";', 1000); // always go

        callAPI('__log_click', {'product': event.data.id}, function() {
          window.location.href = href; // go on success
        });

        return false; // don't go yet
      });
    }
  }

  /*
   * Primitive template rendering.
   */
  function renderTemplate(template, products) {
    var output = '';

    for (var i in products) {
      var product = products[i];
      var tmp = template.replace('<a', '<a rel="__tsd-' + product['id'] + '"');

      for (var key in product) {
        tmp = tmp.replace('{' + key + '}', product[key]);
      }

      output += tmp;
    }

    return output;
  }

  /*
   * Base function to call the API.
   */
  function callAPI(api, args, callback) {
    args.key = key;
    var url = 'http://api.thesocialdigits.com/v1/' + api + '?callback=?';
    var data = {
            'payload': JSON.stringify(args)
          };

    $.getJSON(url, data, callback);
  }

})( jQuery );
<commit_msg>Now all occurences of a varibale in the template gets replaced.<commit_after>(function( $ ) {

  /*
   * Configuration.
   */
  var key = ''; // TODO add your API key
  var datasource = function (products, callback) {
    // TODO add your data call
    $.getJSON(your_url, url_params, callback);
  };

  /**************************************************************************/

  /*
   * Main invocation method.
   */
  $.fn.thesocialdigits = function(api, args, template) {
    var elm = this;

    callAPI(api, args, function(data) {
      if('result' in data) {
        datasource(data.result, function(products) {
          buildHTML(elm, data.result, template, products);
        });
      }
    });
  };

  /*
   * Build HTML from template.
   */
  function buildHTML(elm, ids, template, _products) {
    // ensure the correct sorting of product data
    products = [];

    for (var i in ids) {
      var id = ids[i];

      for(var j in _products) {
        if(_products[j]['id'] == id) {
          products[i] = _products[j];
        }
      }
    }

    // generate the html from the template
    elm.append(renderTemplate($(template).html(), products));

    // add click logging
    for (var i in ids) {
      var id = ids[i];

      $('a[rel="__tsd-' + id + '"]').click({'id': id}, function(event) {
        var href = $(this).attr('href');
        setTimeout('window.location.href = "' + href + '";', 1000); // always go

        callAPI('__log_click', {'product': event.data.id}, function() {
          window.location.href = href; // go on success
        });

        return false; // don't go yet
      });
    }
  }

  /*
   * Primitive template rendering.
   */
  function renderTemplate(template, products) {
    var output = '';

    for (var i in products) {
      var product = products[i];
      var tmp = template.replace('<a', '<a rel="__tsd-' + product['id'] + '"');

      for (var key in product) {
        tmp = tmp.replace(new RegExp('{' + key + '}', 'g'), product[key]);
      }

      output += tmp;
    }

    return output;
  }

  /*
   * Base function to call the API.
   */
  function callAPI(api, args, callback) {
    args.key = key;
    var url = 'http://api.thesocialdigits.com/v1/' + api + '?callback=?';
    var data = {
            'payload': JSON.stringify(args)
          };

    $.getJSON(url, data, callback);
  }

})( jQuery );
<|endoftext|>


<commit_before>const gulp = require("gulp");
const del = require("del");
const browserify = require("browserify");
const babelify = require("babelify");
const src = require("vinyl-source-stream");

const source = "src";
const target = "build";

gulp.task("js", function () {
    return browserify({entries: `${source}/js/app.js`, debug: true})
        .transform(babelify)
        .bundle()
        .pipe(src("app.js"))
        .pipe(gulp.dest(`${target}/js`));
});

gulp.task("files", function () {
    return gulp.src(`${source}/files/**/*`)
        .pipe(gulp.dest(target));
});

gulp.task("clean", function (cb) {
    del(`${target}/*`, cb);
});

gulp.task("default", ["clean"], function () {
    gulp.start("js", "files");
});

gulp.task("watch", ["default"], function () {
    gulp.watch(`${source}/js/**/*.js`, ["js"]);
    gulp.watch(`${source}/files/**/*`, ["files"]);
});
<commit_msg>Make babelify generate source maps with relative paths<commit_after>const gulp = require("gulp");
const del = require("del");
const browserify = require("browserify");
const babelify = require("babelify");
const src = require("vinyl-source-stream");

const source = "src";
const target = "build";

gulp.task("js", function () {
    return browserify({entries: `${source}/js/app.js`, debug: true})
        .transform(babelify.configure({sourceMapRelative: `${__dirname}/`}))
        .bundle()
        .pipe(src("app.js"))
        .pipe(gulp.dest(`${target}/js`));
});

gulp.task("files", function () {
    return gulp.src(`${source}/files/**/*`)
        .pipe(gulp.dest(target));
});

gulp.task("clean", function (cb) {
    del(`${target}/*`, cb);
});

gulp.task("default", ["clean"], function () {
    gulp.start("js", "files");
});

gulp.task("watch", ["default"], function () {
    gulp.watch(`${source}/js/**/*.js`, ["js"]);
    gulp.watch(`${source}/files/**/*`, ["files"]);
});
<|endoftext|>


<commit_before>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple, Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/view') ;
sc_require('mixins/control') ;
sc_require('mixins/validatable') ;

/** @class

  Base view for managing a view backed by an input element.  Since the web
  browser provides native support for editing input elements, this view
  provides basic support for listening to changes on these input elements and
  responding to them.

  Generally you will not work with a FieldView directly.  Instead, you should
  use one of the subclasses implemented by your target platform such as
  SC.CheckboxView, SC.RadioView, SC.TextFieldView, and so on.

  @extends SC.View
  @extends SC.Control
  @extends SC.Validatable
  @since SproutCore 1.0
*/
SC.FieldView = SC.View.extend(SC.Control, SC.Validatable,
/** @scope SC.FieldView.prototype */ {

  /**
    The raw value of the field itself.  This is computed from the 'value'
    propery by passing it through any validator you might have set.  This is
    the value that will be set on the field itself when the view is updated.

    @property {String}
  */
  fieldValue: function() {
    var value = this.get('value');
    if (SC.typeOf(value) === SC.T_ERROR) value = value.get('value');
    return this.fieldValueForObject(value);
  }.property('value', 'validator').cacheable(),

  // ..........................................................
  // PRIMITIVES
  //

  /**
    Override to return an CoreQuery object that selects the input elements
    for the view.  If this method is defined, the field view will
    automatically edit the attrbutes of the input element to reflect the
    current isEnabled state among other things.
  */
  $input: function() {
    return this.$('input').andSelf().filter('input');
  },

  /**
    Override to set the actual value of the field.

    The default implementation will simple copy the newValue to the value
    attribute of any input tags in the receiver view.  You can override this
    method to provide specific functionality needed by your view.

    @param {Object} newValue the value to display.
    @returns {SC.FieldView} receiver
  */
  setFieldValue: function(newValue) {
    if (SC.none(newValue)) newValue = '' ;
    this.$input().val(newValue);
    return this ;
  },

  /**
    Override to retrieve the actual value of the field.

    The default implementation will simply retrieve the value attribute from
    the first input tag in the receiver view.

    @returns {String} value
  */
  getFieldValue: function() {
    return this.$input().val();
  },

  _field_fieldValueDidChange: function(evt) {
    SC.RunLoop.begin();
    this.fieldValueDidChange(NO);
    SC.RunLoop.end();
  },

  /**
    Your class should call this method anytime you think the value of the
    input element may have changed.  This will retrieve the value and update
    the value property of the view accordingly.

    If this is a partial change (i.e. the user is still editing the field and
    you expect the value to change further), then be sure to pass YES for the
    partialChange parameter.  This will change the kind of validation done on
    the value.  Otherwise, the validator may mark the field as having an error
    when the user is still in mid-edit.

    @param partialChange (optional) YES if this is a partial change.
    @returns {Boolean|SC.Error} result of validation.
  */
  fieldValueDidChange: function(partialChange) {

    // collect the field value and convert it back to a value
    var fieldValue = this.getFieldValue();
    var value = this.objectForFieldValue(fieldValue, partialChange);
    this.setIfChanged('value', value);

    // validate value if needed...

    // this.notifyPropertyChange('fieldValue');
    //
    // // get the field value and set it.
    // // if ret is an error, use that instead of the field value.
    // var ret = this.performValidate ? this.performValidate(partialChange) : YES;
    // if (ret === SC.VALIDATE_NO_CHANGE) return ret ;
    //
    // this.propertyWillChange('fieldValue');
    //
    // // if the validator says everything is OK, then in addition to posting
    // // out the value, go ahead and pass the value back through itself.
    // // This way if you have a formatter applied, it will reformat.
    // //
    // // Do this BEFORE we set the value so that the valueObserver will not
    // // overreact.
    // //
    // var ok = SC.$ok(ret);
    // var value = ok ? this._field_getFieldValue() : ret ;
    // if (!partialChange && ok) this._field_setFieldValue(value) ;
    // this.set('value',value) ;
    //
    // this.propertyDidChange('fieldValue');
    //
    // return ret ;
  },

  // ..........................................................
  // INTERNAL SUPPORT
  //

  /** @private
    invoked when the value property changes.  Sets the field value...
  */
  _field_valueDidChange: function() {
    this.setFieldValue(this.get('fieldValue'));
  }.observes('value'),

  /** @private
    after the layer is created, set the field value and observe events
  */
  didCreateLayer: function() {
    this.setFieldValue(this.get('fieldValue'));
    SC.Event.add(this.$input(), 'change', this, this._field_fieldValueDidChange) ;
  },

  willDestroyLayer: function() {
    SC.Event.remove(this.$input(), 'change', this, this._field_fieldValueDidChange);
  },

  /** @private
    when the layer is updated, go ahead and call render like normal.  this
    will allow normal CSS class + style updates.  by then also update field
    value manually.

    Most subclasses should not regenerate their contents unless necessary.
  */
  updateLayer: function() {
    sc_super();

    // only change field value if it has changed from the last time we
    // set it.  This allows the browser-native field value to change without
    // this method interfering with it.
    var fieldValue = this.get('fieldValue');
    this.setFieldValue(fieldValue);
  },

  // ACTIONS
  // You generally do not need to override these but they may be used.

  /**
    Called to perform validation on the field just before the form
    is submitted.  If you have a validator attached, this will get the
    validators.
  */
  // validateSubmit: function() {
  //   var ret = this.performValidateSubmit ? this.performValidateSubmit() : YES;
  //   // save the value if needed
  //   var value = SC.$ok(ret) ? this._field_getFieldValue() : ret ;
  //   if (value != this.get('value')) this.set('value', value) ;
  //   return ret ;
  // },

  // OVERRIDE IN YOUR SUBCLASS
  // Override these primitives in your subclass as required.

  /**
    Allow the browser to do its normal event handling for the mouse down
    event.  But first, set isActive to YES.
  */
  mouseDown: function(evt) {
    if (this.get('isEnabled')) {
      this.set('isActive', YES);
      this._field_isMouseDown = YES;
    }
    evt.allowDefault();
    return YES;
  },

  /** @private
    Remove the active class on mouseOut if mouse is down.
  */
  mouseOut: function(evt) {
    if (this._field_isMouseDown) this.set('isActive', NO);
    evt.allowDefault();
    return YES;
  },

  /** @private
    If mouse was down and we renter the button area, set the active state again.
  */
  mouseOver: function(evt) {
    this.set('isActive', this._field_isMouseDown);
    evt.allowDefault();
    return YES;
  },

  _field_isMouseDown: NO,

  /** @private
    on mouse up, remove the isActive class and then allow the browser to do
    its normal thing.
  */
  mouseUp: function(evt) {
    // track independently in case isEnabled has changed
    if (this._field_isMouseDown) this.set('isActive', NO);
    this._field_isMouseDown = false;
    evt.allowDefault();
    return YES ;
  },

  // called whenever the value is set on the object.  Will set the value
  // on the field if the value is changed.
  // valueDidChange: function() {
  //   var value = this.get('value') ;
  //   var isError = SC.typeOf(value) === SC.T_ERROR ;
  //   if (!isError && (value !== this._field_getFieldValue())) {
  //     this._field_setFieldValue(value) ;
  //   }
  // }.observes('value'),

  // these methods use the validator to convert the raw field value returned
  // by your subclass into an object and visa versa.
  _field_setFieldValue: function(newValue) {

    this.propertyWillChange('fieldValue');
    if (this.fieldValueForObject) {
      newValue = this.fieldValueForObject(newValue) ;
    }
    var ret = this.setFieldValue(newValue) ;
    this.propertyDidChange('fieldValue');
    return ret ;
  },

  _field_getFieldValue: function() {
    var ret = this.getFieldValue() ;
    if (this.objectForFieldValue) ret=this.objectForFieldValue(ret);
    return ret ;
  }

}) ;

<commit_msg>whitespace, documentation typos<commit_after>// ==========================================================================
// Project:   SproutCore - JavaScript Application Framework
// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors.
//            Portions ©2008-2009 Apple, Inc. All rights reserved.
// License:   Licened under MIT license (see license.js)
// ==========================================================================

sc_require('views/view') ;
sc_require('mixins/control') ;
sc_require('mixins/validatable') ;

/** @class

  Base view for managing a view backed by an input element.  Since the web
  browser provides native support for editing input elements, this view
  provides basic support for listening to changes on these input elements and
  responding to them.

  Generally you will not work with a FieldView directly.  Instead, you should
  use one of the subclasses implemented by your target platform such as
  SC.CheckboxView, SC.RadioView, SC.TextFieldView, and so on.

  @extends SC.View
  @extends SC.Control
  @extends SC.Validatable
  @since SproutCore 1.0
*/
SC.FieldView = SC.View.extend(SC.Control, SC.Validatable,
/** @scope SC.FieldView.prototype */ {

  /**
    The raw value of the field itself.  This is computed from the 'value'
    property by passing it through any validator you might have set.  This is
    the value that will be set on the field itself when the view is updated.

    @property {String}
  */
  fieldValue: function() {
    var value = this.get('value');
    if (SC.typeOf(value) === SC.T_ERROR) value = value.get('value');
    return this.fieldValueForObject(value);
  }.property('value', 'validator').cacheable(),

  // ..........................................................
  // PRIMITIVES
  //

  /**
    Override to return an CoreQuery object that selects the input elements
    for the view.  If this method is defined, the field view will
    automatically edit the attrbutes of the input element to reflect the
    current isEnabled state among other things.
  */
  $input: function() {
    return this.$('input').andSelf().filter('input');
  },

  /**
    Override to set the actual value of the field.

    The default implementation will simple copy the newValue to the value
    attribute of any input tags in the receiver view.  You can override this
    method to provide specific functionality needed by your view.

    @param {Object} newValue the value to display.
    @returns {SC.FieldView} receiver
  */
  setFieldValue: function(newValue) {
    if (SC.none(newValue)) newValue = '' ;
    this.$input().val(newValue);
    return this ;
  },

  /**
    Override to retrieve the actual value of the field.

    The default implementation will simply retrieve the value attribute from
    the first input tag in the receiver view.

    @returns {String} value
  */
  getFieldValue: function() {
    return this.$input().val();
  },

  _field_fieldValueDidChange: function(evt) {
    SC.RunLoop.begin();
    this.fieldValueDidChange(NO);
    SC.RunLoop.end();
  },

  /**
    Your class should call this method anytime you think the value of the
    input element may have changed.  This will retrieve the value and update
    the value property of the view accordingly.

    If this is a partial change (i.e. the user is still editing the field and
    you expect the value to change further), then be sure to pass YES for the
    partialChange parameter.  This will change the kind of validation done on
    the value.  Otherwise, the validator may mark the field as having an error
    when the user is still in mid-edit.

    @param partialChange (optional) YES if this is a partial change.
    @returns {Boolean|SC.Error} result of validation.
  */
  fieldValueDidChange: function(partialChange) {
    // collect the field value and convert it back to a value
    var fieldValue = this.getFieldValue();
    var value = this.objectForFieldValue(fieldValue, partialChange);
    this.setIfChanged('value', value);

    // validate value if needed...

    // this.notifyPropertyChange('fieldValue');
    //
    // // get the field value and set it.
    // // if ret is an error, use that instead of the field value.
    // var ret = this.performValidate ? this.performValidate(partialChange) : YES;
    // if (ret === SC.VALIDATE_NO_CHANGE) return ret ;
    //
    // this.propertyWillChange('fieldValue');
    //
    // // if the validator says everything is OK, then in addition to posting
    // // out the value, go ahead and pass the value back through itself.
    // // This way if you have a formatter applied, it will reformat.
    // //
    // // Do this BEFORE we set the value so that the valueObserver will not
    // // overreact.
    // //
    // var ok = SC.$ok(ret);
    // var value = ok ? this._field_getFieldValue() : ret ;
    // if (!partialChange && ok) this._field_setFieldValue(value) ;
    // this.set('value',value) ;
    //
    // this.propertyDidChange('fieldValue');
    //
    // return ret ;
  },

  // ..........................................................
  // INTERNAL SUPPORT
  //

  /** @private
    invoked when the value property changes.  Sets the field value...
  */
  _field_valueDidChange: function() {
    this.setFieldValue(this.get('fieldValue'));
  }.observes('value'),

  /** @private
    after the layer is created, set the field value and observe events
  */
  didCreateLayer: function() {
    this.setFieldValue(this.get('fieldValue'));
    SC.Event.add(this.$input(), 'change', this, this._field_fieldValueDidChange) ;
  },

  willDestroyLayer: function() {
    SC.Event.remove(this.$input(), 'change', this, this._field_fieldValueDidChange);
  },

  /** @private
    when the layer is updated, go ahead and call render like normal.  this
    will allow normal CSS class + style updates.  by then also update field
    value manually.

    Most subclasses should not regenerate their contents unless necessary.
  */
  updateLayer: function() {
    sc_super();

    // only change field value if it has changed from the last time we
    // set it.  This allows the browser-native field value to change without
    // this method interfering with it.
    var fieldValue = this.get('fieldValue');
    this.setFieldValue(fieldValue);
  },

  // ACTIONS
  // You generally do not need to override these but they may be used.

  /**
    Called to perform validation on the field just before the form
    is submitted.  If you have a validator attached, this will get the
    validators.
  */
  // validateSubmit: function() {
  //   var ret = this.performValidateSubmit ? this.performValidateSubmit() : YES;
  //   // save the value if needed
  //   var value = SC.$ok(ret) ? this._field_getFieldValue() : ret ;
  //   if (value != this.get('value')) this.set('value', value) ;
  //   return ret ;
  // },

  // OVERRIDE IN YOUR SUBCLASS
  // Override these primitives in your subclass as required.

  /**
    Allow the browser to do its normal event handling for the mouse down
    event.  But first, set isActive to YES.
  */
  mouseDown: function(evt) {
    if (this.get('isEnabled')) {
      this.set('isActive', YES);
      this._field_isMouseDown = YES;
    }
    evt.allowDefault();
    return YES;
  },

  /** @private
    Remove the active class on mouseOut if mouse is down.
  */
  mouseOut: function(evt) {
    if (this._field_isMouseDown) this.set('isActive', NO);
    evt.allowDefault();
    return YES;
  },

  /** @private
    If mouse was down and we renter the button area, set the active state again.
  */
  mouseOver: function(evt) {
    this.set('isActive', this._field_isMouseDown);
    evt.allowDefault();
    return YES;
  },

  _field_isMouseDown: NO,

  /** @private
    on mouse up, remove the isActive class and then allow the browser to do
    its normal thing.
  */
  mouseUp: function(evt) {
    // track independently in case isEnabled has changed
    if (this._field_isMouseDown) this.set('isActive', NO);
    this._field_isMouseDown = false;
    evt.allowDefault();
    return YES ;
  },

  // called whenever the value is set on the object.  Will set the value
  // on the field if the value is changed.
  // valueDidChange: function() {
  //   var value = this.get('value') ;
  //   var isError = SC.typeOf(value) === SC.T_ERROR ;
  //   if (!isError && (value !== this._field_getFieldValue())) {
  //     this._field_setFieldValue(value) ;
  //   }
  // }.observes('value'),

  // these methods use the validator to convert the raw field value returned
  // by your subclass into an object and visa versa.
  _field_setFieldValue: function(newValue) {

    this.propertyWillChange('fieldValue');
    if (this.fieldValueForObject) {
      newValue = this.fieldValueForObject(newValue) ;
    }
    var ret = this.setFieldValue(newValue) ;
    this.propertyDidChange('fieldValue');
    return ret ;
  },

  _field_getFieldValue: function() {
    var ret = this.getFieldValue() ;
    if (this.objectForFieldValue) ret=this.objectForFieldValue(ret);
    return ret ;
  }

}) ;

<|endoftext|>


<commit_before>exports.start = function(server, port, host, worker_count, options) {

  function isPort (x) { return parseInt(x) >= 0; }

  var unix_socket_path = null;
  if (!isPort(port)) {
    // shift arguments
    in_options = worker_count;
    worker_count = host;
    unix_socket_path = port;
  }

  // merge options
  var default_options = {
    working_path : process.cwd(),
    tmp_path : 'tmp',
    uid : process.getuid(),
    gid : process.getgid(),
    daemonize: false,
    verbose: true
  }
  if (options) {
    (function(obj1, obj2) {
      for (attrname in obj2) { obj1[attrname] = obj2[attrname]; }
    })(default_options, options);
  }
  options = default_options;

  // setup log function
  var log = function(what) {
    if (options.verbose) console.log(what);
  }

  // Master or worker?
  var worker_id = process.env._FUGUE_WORKER;
  var is_master = !worker_id;

  // daemonize?
  if (is_master && options.daemonize) {
    log('daemonizing '+process.pid);
    daemon = require(__dirname+'/../build/default/daemon');
    daemon.setSid();
    log('daemonized');
  }

  // require needed modules
  var net   = require('net'),
      http  = require('http'),
      spawn = require('child_process').spawn,
      sys   = require('sys'),
      fs    = require('fs'),
      netBinding = process.binding('net');

  var socket_server_socket_path = options.tmp_path + '/fugue_master.sock'

  // Redirect stdout and stderr
  if (options.log_file) {
    process.stdout.fd = fs.openSync(options.log_file, 'a');
  }

  if (is_master) { // Master

    log('Starting new master...');

    if (options.master_log_file) {
      process.stdout.fd = fs.openSync(options.master_log_file, 'w');
    }

    var server_socket = null;
    if (process.env._FUGUE_PORT && process.env._FUGUE_ORIG_MASTER_PID) { // we were respawned by another master
      try {
        log('Trying to get socket back from original server...');
        // request socket to original server
        // this startup part has to work synchronously, so we dig deep into the bindings...
        var client_socket = netBinding.socket('unix');
        log('connecting to '+socket_server_socket_path);
        netBinding.connect(client_socket, socket_server_socket_path);
        var request_buffer = new Buffer('GIMME_SOCKET', 'ascii');
        netBinding.write(client_socket, request_buffer, 0, request_buffer.length);
        var responseBuffer = new Buffer(256);
        var length = null;
        do {
          length = netBinding.recvMsg(client_socket, responseBuffer, 0, responseBuffer.length)
        } while(!netBinding.recvMsg.fd);

        log('Got response from server:'+responseBuffer.toString('ascii', 0, length));
        if (netBinding.recvMsg.fd) {
          server_socket = netBinding.recvMsg.fd;
        } else {
          log('Response got no file descriptor... (bad)');
        }
      } catch(error) {
        log('Error trying to get server file descriptor: '+error.message);
      }
      if (!server_socket) {
        log('Failed to get socket from original server.');
        log('Now I\'m going to try to create one...')
      }

    }
    if (!server_socket) {
      // Now we have to create a socket

      if (unix_socket_path) {
        // UNIX socket

        // remove socket file if it exists
        try {
          fs.unlinkSync(unix_socket_path);
        } catch(err) {
          // do nothing, file does not exist
        }

        server_socket = netBinding.socket('unix');
        netBinding.bind(server_socket, unix_socket_path);
        process.env._FUGUE_SOCKET = unix_socket_path;
      } else {
        // TCP socket
        server_socket = netBinding.socket('tcp'+(netBinding.isIP(host) == 6 ? 6 : 4));
        netBinding.bind(server_socket, port, host);
        process.env._FUGUE_PORT = port;
        process.env._FUGUE_HOST = host;
      }
      netBinding.listen(server_socket, 128);
    }

    log('Have server socket: ' + server_socket);

    var fd_server = net.createServer(function(conn) {
      conn.on('data', function(data) {
        log('got data for fd_server');
        if (data.toString() == 'GIMME_SOCKET') {
          log('serving server socket file descriptor ' + server_socket);
          conn.write('HERE_YOU_GO', 'ascii', server_socket);
        }
      });
    }).listen(socket_server_socket_path);

    var workers = [];

    var spawn_worker = function(worker_idx) {
      // prepare environment for worker
      var env = {};
      for(var i in process.env){
        env[i] = process.env[i];
      }
      env._FUGUE_WORKER = "" + worker_idx;
      var args = process.argv;

      // spawn worker process
      var new_worker = workers[worker_idx] = spawn(args[0], args.slice(1), env);
      if(!options.log_file) {
        // if worker does not log into file, pipe the worker output here
        var worker_log = function(what) {
          log("WORKER "+worker_idx+ ": " + what.toString());
        }
        new_worker.stdout.on('data', worker_log);
        new_worker.stderr.on('data', worker_log);
      }

      // listen for when the worker dies and bring him back to life
      new_worker.on('exit', function() {
        log('Child '+worker_idx+' died. Respawning it.');
        spawn_worker(worker_idx);
      });

    }

    // fork workers
    log('Spawning workers...');
    for (var worker_idx = 0; worker_idx < worker_count; worker_idx ++) {
      spawn_worker(worker_idx);
    }
    log('spawned.')

    var killer = function() {
      log('exiting.');
      for(var i in workers) {
        var worker =  workers[i];
        worker.removeAllListeners(); // Prevent from master respawn
        try {
          worker.kill();
        } catch(excep) {
          // do nothing, as the error is probably that the process does no longer exist
        }
      }
      workers = [];
      netBinding.close(server_socket);
      process.removeAllListeners();
      process.nextTick(function() {
        process.exit();
      });
    }

    // Listen for process exits
    // process.on('exit', world_killer);
    process.on('SIGINT', killer);
    process.on('SIGHUP', killer);
    process.on('SIGTERM', killer);

    // Listen to SIGUSR2 for master restarts
    process.on('SIGUSR2', function() {
      log('Got SIGUSR2, respawning self');
      // respawn self
      var env = {};
      for(var i in process.env){
        env[i] = process.env[i];
      }
      env._FUGUE_ORIG_MASTER_PID = process.pid.toString();
      var args = process.argv;

      // spawn worker process
      spawned = spawn(args[0], args.slice(1), env);
      spawned.stdout.on('data', function(data) {
        log("New master goes: "+data);
      });

    });

  } else { // Worker

    log('Worker here');

    var kill_master_pid = parseInt(process.env._FUGUE_ORIG_MASTER_PID);
    if (!!kill_master_pid && workerId() == 0) { // only the first worker tries to kill original master
      process.kill(kill_master_pid);
    }

    // track connections here
    var connection_count = 0;

    var die_soon = false; // tells if worker should die after serving exisiting connections
    // Setup killer
    var worker_killer = function() {
      if(connection_count == 0) {
        process.nextTick(function() {
          process.exit();
        });
      } else {
        // Stop listening for new connections - remove watcher from the event loop
        server.watcher.stop();
        // Set process to die after serving existing connections
        die_soon = true;
      }
    }
    process.on('SIGINT', worker_killer);
    process.on('SIGHUP', worker_killer);
    process.on('SIGTERM', worker_killer);

    process.on('exit', function() {
      log("Worker "+worker_id+" exiting.");
    });

    var connection = net.createConnection(socket_server_socket_path).on('connect', function() {
      connection.write('GIMME_SOCKET');
      connection.on('fd', function(fd) {
        server.listenFD(fd);
        if (process.cwd() != options.working_path) {
          process.chdir(options.working_path);
        }
        // set gid
        if(process.getgid() != options.gid) {
          process.setgid(options.gid);
        }
        // set uid
        if(process.getuid() != options.uid) {
          process.setuid(options.uid);
        }
      });
    });

    // Track connections so we don't die in the middle of serving one
    server.on('connection', function(connection) {
      connection_count ++;
      connection.on('end', function() {
        connection_count --;
        if (die_soon && connection_count == 0) {
          process.exit();
        }
      });
    });

  }
}

exports.isMaster = function() {
  return !process.env._FUGUE_WORKER;
}
var workerId = exports.workerId = function() {
  return process.env._FUGUE_WORKER !== undefined ? parseInt(process.env._FUGUE_WORKER) : null;
}<commit_msg>test if paths exist first<commit_after>exports.start = function(server, port, host, worker_count, options) {

  function isPort (x) { return parseInt(x) >= 0; }

  var unix_socket_path = null;
  if (!isPort(port)) {
    // shift arguments
    in_options = worker_count;
    worker_count = host;
    unix_socket_path = port;
  }

  // merge options
  var default_options = {
    working_path : process.cwd(),
    tmp_path : 'tmp',
    uid : process.getuid(),
    gid : process.getgid(),
    daemonize: false,
    verbose: true
  }
  if (options) {
    (function(obj1, obj2) {
      for (attrname in obj2) { obj1[attrname] = obj2[attrname]; }
    })(default_options, options);
  }
  options = default_options;

  var path = require('path');
  // check if paths exist
  if (!path.existsSync(options.tmp_path)) {
    throw "Temp path "+options.tmp_path + " does not exist. Please create it";
  }
  if (!path.existsSync(options.working_path)) {
    throw "Working path "+options.working_path + " does not exist. Please create it";
  }

  // setup log function
  var log = function(what) {
    if (options.verbose) console.log(what);
  }

  // Master or worker?
  var worker_id = process.env._FUGUE_WORKER;
  var is_master = !worker_id;

  // daemonize?
  if (is_master && options.daemonize) {
    log('daemonizing '+process.pid);
    daemon = require(__dirname+'/../build/default/daemon');
    daemon.setSid();
    log('daemonized');
  }

  // require needed modules
  var net   = require('net'),
      http  = require('http'),
      spawn = require('child_process').spawn,
      sys   = require('sys'),
      fs    = require('fs'),
      netBinding = process.binding('net');

  var socket_server_socket_path = options.tmp_path + '/fugue_master.sock'

  // Redirect stdout and stderr
  if (options.log_file) {
    process.stdout.fd = fs.openSync(options.log_file, 'a');
  }

  if (is_master) { // Master

    log('Starting new master...');

    if (options.master_log_file) {
      process.stdout.fd = fs.openSync(options.master_log_file, 'w');
    }

    var server_socket = null;
    if (process.env._FUGUE_PORT && process.env._FUGUE_ORIG_MASTER_PID) { // we were respawned by another master
      try {
        log('Trying to get socket back from original server...');
        // request socket to original server
        // this startup part has to work synchronously, so we dig deep into the bindings...
        var client_socket = netBinding.socket('unix');
        log('connecting to '+socket_server_socket_path);
        netBinding.connect(client_socket, socket_server_socket_path);
        var request_buffer = new Buffer('GIMME_SOCKET', 'ascii');
        netBinding.write(client_socket, request_buffer, 0, request_buffer.length);
        var responseBuffer = new Buffer(256);
        var length = null;
        do {
          length = netBinding.recvMsg(client_socket, responseBuffer, 0, responseBuffer.length)
        } while(!netBinding.recvMsg.fd);

        log('Got response from server:'+responseBuffer.toString('ascii', 0, length));
        if (netBinding.recvMsg.fd) {
          server_socket = netBinding.recvMsg.fd;
        } else {
          log('Response got no file descriptor... (bad)');
        }
      } catch(error) {
        log('Error trying to get server file descriptor: '+error.message);
      }
      if (!server_socket) {
        log('Failed to get socket from original server.');
        log('Now I\'m going to try to create one...')
      }

    }
    if (!server_socket) {
      // Now we have to create a socket

      if (unix_socket_path) {
        // UNIX socket

        // remove socket file if it exists
        try {
          fs.unlinkSync(unix_socket_path);
        } catch(err) {
          // do nothing, file does not exist
        }

        server_socket = netBinding.socket('unix');
        netBinding.bind(server_socket, unix_socket_path);
        process.env._FUGUE_SOCKET = unix_socket_path;
      } else {
        // TCP socket
        server_socket = netBinding.socket('tcp'+(netBinding.isIP(host) == 6 ? 6 : 4));
        netBinding.bind(server_socket, port, host);
        process.env._FUGUE_PORT = port;
        process.env._FUGUE_HOST = host;
      }
      netBinding.listen(server_socket, 128);
    }

    log('Have server socket: ' + server_socket);

    var fd_server = net.createServer(function(conn) {
      conn.on('data', function(data) {
        log('got data for fd_server');
        if (data.toString() == 'GIMME_SOCKET') {
          log('serving server socket file descriptor ' + server_socket);
          conn.write('HERE_YOU_GO', 'ascii', server_socket);
        }
      });
    }).listen(socket_server_socket_path);

    var workers = [];

    var spawn_worker = function(worker_idx) {
      // prepare environment for worker
      var env = {};
      for(var i in process.env){
        env[i] = process.env[i];
      }
      env._FUGUE_WORKER = "" + worker_idx;
      var args = process.argv;

      // spawn worker process
      var new_worker = workers[worker_idx] = spawn(args[0], args.slice(1), env);
      if(!options.log_file) {
        // if worker does not log into file, pipe the worker output here
        var worker_log = function(what) {
          log("WORKER "+worker_idx+ ": " + what.toString());
        }
        new_worker.stdout.on('data', worker_log);
        new_worker.stderr.on('data', worker_log);
      }

      // listen for when the worker dies and bring him back to life
      new_worker.on('exit', function() {
        log('Child '+worker_idx+' died. Respawning it.');
        spawn_worker(worker_idx);
      });

    }

    // fork workers
    log('Spawning workers...');
    for (var worker_idx = 0; worker_idx < worker_count; worker_idx ++) {
      spawn_worker(worker_idx);
    }
    log('spawned.')

    var killer = function() {
      log('exiting.');
      for(var i in workers) {
        var worker =  workers[i];
        worker.removeAllListeners(); // Prevent from master respawn
        try {
          worker.kill();
        } catch(excep) {
          // do nothing, as the error is probably that the process does no longer exist
        }
      }
      workers = [];
      netBinding.close(server_socket);
      process.removeAllListeners();
      process.nextTick(function() {
        process.exit();
      });
    }

    // Listen for process exits
    // process.on('exit', world_killer);
    process.on('SIGINT', killer);
    process.on('SIGHUP', killer);
    process.on('SIGTERM', killer);

    // Listen to SIGUSR2 for master restarts
    process.on('SIGUSR2', function() {
      log('Got SIGUSR2, respawning self');
      // respawn self
      var env = {};
      for(var i in process.env){
        env[i] = process.env[i];
      }
      env._FUGUE_ORIG_MASTER_PID = process.pid.toString();
      var args = process.argv;

      // spawn worker process
      spawned = spawn(args[0], args.slice(1), env);
      spawned.stdout.on('data', function(data) {
        log("New master goes: "+data);
      });

    });

  } else { // Worker

    log('Worker here');

    var kill_master_pid = parseInt(process.env._FUGUE_ORIG_MASTER_PID);
    if (!!kill_master_pid && workerId() == 0) { // only the first worker tries to kill original master
      process.kill(kill_master_pid);
    }

    // track connections here
    var connection_count = 0;

    var die_soon = false; // tells if worker should die after serving exisiting connections
    // Setup killer
    var worker_killer = function() {
      if(connection_count == 0) {
        process.nextTick(function() {
          process.exit();
        });
      } else {
        // Stop listening for new connections - remove watcher from the event loop
        server.watcher.stop();
        // Set process to die after serving existing connections
        die_soon = true;
      }
    }
    process.on('SIGINT', worker_killer);
    process.on('SIGHUP', worker_killer);
    process.on('SIGTERM', worker_killer);

    process.on('exit', function() {
      log("Worker "+worker_id+" exiting.");
    });

    var connection = net.createConnection(socket_server_socket_path).on('connect', function() {
      connection.write('GIMME_SOCKET');
      connection.on('fd', function(fd) {
        server.listenFD(fd);
        if (process.cwd() != options.working_path) {
          process.chdir(options.working_path);
        }
        // set gid
        if(process.getgid() != options.gid) {
          process.setgid(options.gid);
        }
        // set uid
        if(process.getuid() != options.uid) {
          process.setuid(options.uid);
        }
      });
    });

    // Track connections so we don't die in the middle of serving one
    server.on('connection', function(connection) {
      connection_count ++;
      connection.on('end', function() {
        connection_count --;
        if (die_soon && connection_count == 0) {
          process.exit();
        }
      });
    });

  }
}

exports.isMaster = function() {
  return !process.env._FUGUE_WORKER;
}
var workerId = exports.workerId = function() {
  return process.env._FUGUE_WORKER !== undefined ? parseInt(process.env._FUGUE_WORKER) : null;
}<|endoftext|>


<commit_before>WulinMaster.actions.fullscreen = {
  ...WulinMaster.actions.BaseAction,
  name: 'fullscreen',

  handler: function (e, args) {
    let $fullscreen = $(e.currentTarget);
    let fullscreen = $fullscreen.data('fullscreen');
    let grid = this.getGrid();
    let currentContainer = grid.container;
    Array.from(currentContainer.siblings(':not(script)')).forEach((sbl) =>
      $(sbl).toggle()
    );

    if (fullscreen) {
      let gridOriginalHeight = $fullscreen.data('grid_height');
      let gridOriginalWidth = $fullscreen.data('grid_width');
      $fullscreen.removeData('fullscreen', 'grid_height', 'grid_width');
      currentContainer.height(gridOriginalHeight).width(gridOriginalWidth);
    } else {
      $fullscreen.data('fullscreen', true);
      let gridContainerHeight = currentContainer[0].style.height;
      let gridContainerWidth = currentContainer[0].style.width;
      $fullscreen.data('grid_height', gridContainerHeight);
      $fullscreen.data('grid_width', gridContainerWidth);

      currentContainer.height('100%').width('100%');
    }
    currentContainer.resize();

    let $icon = $fullscreen.find('a.fullscreen_action, i.material-icons');
    let $iconText = $icon.text();
    $icon.text($iconText === 'fullscreen' ? 'fullscreen_exit' : 'fullscreen');
    let $iconLabel = $fullscreen.find('a.fullscreen_action, span');
    let $iconLabelText = $iconLabel.text();
    $iconLabel.text(
      $iconLabelText === 'Fullscreen' ? 'Fullscreen Exit' : 'Fullscreen'
    );
  },
};

WulinMaster.ActionManager.register(WulinMaster.actions.fullscreen);
<commit_msg>MISC #218 - Rename variable<commit_after>WulinMaster.actions.fullscreen = {
  ...WulinMaster.actions.BaseAction,
  name: 'fullscreen',

  handler: function (e, args) {
    let $fullscreenElement = $(e.currentTarget);
    let fullscreen = $fullscreenElement.data('fullscreen');
    let grid = this.getGrid();
    let currentContainer = grid.container;
    Array.from(currentContainer.siblings(':not(script)')).forEach((sbl) =>
      $(sbl).toggle()
    );

    if (fullscreen) {
      let gridOriginalHeight = $fullscreenElement.data('grid_height');
      let gridOriginalWidth = $fullscreenElement.data('grid_width');
      $fullscreenElement.removeData('fullscreen', 'grid_height', 'grid_width');
      currentContainer.height(gridOriginalHeight).width(gridOriginalWidth);
    } else {
      $fullscreenElement.data('fullscreen', true);
      let gridContainerHeight = currentContainer[0].style.height;
      let gridContainerWidth = currentContainer[0].style.width;
      $fullscreenElement.data('grid_height', gridContainerHeight);
      $fullscreenElement.data('grid_width', gridContainerWidth);

      currentContainer.height('100%').width('100%');
    }
    currentContainer.resize();

    let $icon = $fullscreenElement.find(
      'a.fullscreen_action, i.material-icons'
    );
    let $iconText = $icon.text();
    $icon.text($iconText === 'fullscreen' ? 'fullscreen_exit' : 'fullscreen');
    let $iconLabel = $fullscreenElement.find('a.fullscreen_action, span');
    let $iconLabelText = $iconLabel.text();
    $iconLabel.text(
      $iconLabelText === 'Fullscreen' ? 'Fullscreen Exit' : 'Fullscreen'
    );
  },
};

WulinMaster.ActionManager.register(WulinMaster.actions.fullscreen);
<|endoftext|>


<commit_before>import capitalize from 'capitalize'
import {compose, map} from 'ramda'
import React from 'react'
import {instrumentInstanceNames} from '../../utils/derivedData'
import {eventValuePath, rawConnect} from '../../utils/helpers'
import {
  updateActivePatternInstrument,
  updateActivePatternOctave,
  updateActivePatternVolume,
  updateActivePatternXLength
} from '../../actions'
import InstrumentSelector from '../molecules/InstrumentSelector'
import RangeSelector from '../molecules/RangeSelector'
import FullButton from '../atoms/FullButton'

export default rawConnect(({
  activePatternIndex,
  dispatch,
  patterns,
  plugins
}) => {
  const activePattern = patterns[activePatternIndex]
  const {instrument, octave, xLength, volume} = activePattern
  return

Pattern Editor Settings

    <InstrumentSelector defaultValue={instrument}
      handleChange={(compose(
        dispatch,
        updateActivePatternInstrument,
        eventValuePath
      ))}
      label='Instrument'
      options={map(
        instr => ({text: capitalize.words(instr), value: instr}),
        instrumentInstanceNames(plugins)
      )}
    />
    <RangeSelector
      key='2'
      max='1'
      min='0'
      onChange={compose(
        dispatch,
        updateActivePatternVolume,
        Number,
        eventValuePath
      )}
      output={Math.round(volume * 100)}
      step='0.01'
      text='Volume'
      value={volume}
    />
    <RangeSelector
      key='3'
      max='16'
      min='1'
      onChange={compose(
        dispatch,
        updateActivePatternXLength,
        Number,
        eventValuePath
      )}
      output={String(xLength)}
      text='Length'
      value={xLength}
    />
    <RangeSelector
      key='4'
      max='2'
      min='-3'
      onChange={compose(
        dispatch,
        updateActivePatternOctave,
        Number,
        eventValuePath
      )}
      output={octave}
      text='Octave'
      value={octave}
    />

      <FullButton to='/pattern-editor'>OK</FullButton>

})
<commit_msg>bug(pattern) pattern-editor settings OK button should linkn to pattern-editor<commit_after>import capitalize from 'capitalize'
import {compose, map} from 'ramda'
import React from 'react'
import {instrumentInstanceNames} from '../../utils/derivedData'
import {eventValuePath, rawConnect} from '../../utils/helpers'
import {
  updateActivePatternInstrument,
  updateActivePatternOctave,
  updateActivePatternVolume,
  updateActivePatternXLength
} from '../../actions'
import InstrumentSelector from '../molecules/InstrumentSelector'
import RangeSelector from '../molecules/RangeSelector'
import FullButton from '../atoms/FullButton'

export default rawConnect(({
  activePatternIndex,
  dispatch,
  patterns,
  plugins
}) => {
  const activePattern = patterns[activePatternIndex]
  const {instrument, octave, xLength, volume} = activePattern
  return

Pattern Editor Settings

    <InstrumentSelector defaultValue={instrument}
      handleChange={(compose(
        dispatch,
        updateActivePatternInstrument,
        eventValuePath
      ))}
      label='Instrument'
      options={map(
        instr => ({text: capitalize.words(instr), value: instr}),
        instrumentInstanceNames(plugins)
      )}
    />
    <RangeSelector
      key='2'
      max='1'
      min='0'
      onChange={compose(
        dispatch,
        updateActivePatternVolume,
        Number,
        eventValuePath
      )}
      output={Math.round(volume * 100)}
      step='0.01'
      text='Volume'
      value={volume}
    />
    <RangeSelector
      key='3'
      max='16'
      min='1'
      onChange={compose(
        dispatch,
        updateActivePatternXLength,
        Number,
        eventValuePath
      )}
      output={String(xLength)}
      text='Length'
      value={xLength}
    />
    <RangeSelector
      key='4'
      max='2'
      min='-3'
      onChange={compose(
        dispatch,
        updateActivePatternOctave,
        Number,
        eventValuePath
      )}
      output={octave}
      text='Octave'
      value={octave}
    />

      <FullButton to='/controllers/pattern-editor'>OK</FullButton>

})
<|endoftext|>


<commit_before>process.env.NODE_ENV = 'test';
process.env.PORT = 8080;

var expect    = require('chai').expect;
var request = require('request');
var app = require("../../app");

describe('Weather App', function() {
  before(function () {
    app.listen(process.env.PORT);
  });

  after(function () {
    process.exit();
  });

  var url = 'http://localhost:8080/';

  describe('visit home page', function() {
    it('should should have the contect of home page', function(done) {
      request.get(url, function(error, response, body) {
        expect(response.statusCode).to.equal(200);
        expect(body).to.include('Welcome to Weather App, Search for a location to get the weather information');
        done();
      });
    });
  });

  describe('visit weather page', function() {

    // supported url format for weather page
    // http://localhost:3000/weather/:location
    // http://localhost:3000/weather/:location/today
    // http://localhost:3000/weather/:location/:weekday

    it('should return 404 status if location is empty in url', function(done) {
      var weatherURL =  url + "weather";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(404);
        done();
      });
    });

    // http://localhost:3000/weather/:location
    it('should return 422 page if location is non existing', function(done) {
      var weatherURL =  url + "weather/nkownLocationOnEarthWhichGoogleCanntFind";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(422);
        done();
      });
    });

    // http://localhost:3000/weather/:location
    it('should return 200 page and weather information if location is existing', function(done) {
      var weatherURL =  url + "weather/goa";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(200);
        expect(body).to.include('Current Temprature');
        done();
      });
    });

    // http://localhost:3000/weather/:location/today
    it('should return current day weather', function(done) {
      var weatherURL =  url + "weather/goa/today";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(200);
        expect(body).to.include('Max Temprature');
        expect(body).to.include('Min Temprature');
        done();
      });
    });

    // http://localhost:3000/weather/:location/weekday
    // Allowed weedays - weekdays = [ "monday", "tuesday", "wednesday",
    // "thursday", "friday", "saturday", "sunday"]
    it('should return the weekday day weather', function(done) {
      var weatherURL =  url + "weather/goa/saturday";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(200);
        expect(body).to.include('Max Temprature');
        expect(body).to.include('Min Temprature');
        done();
      });
    });

    // http://localhost:3000/weather/:location/weekday
    it('should return 422 for invalid weekday day weather', function(done) {
      var weatherURL =  url + "weather/goa/somedayInAWeek";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(422);
        expect(body).to.include('Invalid request');
        done();
      });
    });
  });

  describe('visit weather page requesting json format', function() {

    // supported url format for weather page
    // http://localhost:3000/weather/:location?format=json
    // http://localhost:3000/weather/:location/todayformat=json
    // http://localhost:3000/weather/:location/:weekdayformat=json

    // http://localhost:3000/weather/:location
    it('should return 422 page if location is non existing in json format', function(done) {
      var weatherURL =  url + "weather/nkownLocationOnEarthWhichGoogleCanntFind?format=json";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(422);
        done();
      });
    });

    // http://localhost:3000/weather/:location
    it('should return 200 page and weather information if location is existing in json format', function(done) {
      var weatherURL =  url + "weather/goa?format=json";
      request.get(weatherURL, function(error, response, body) {
        body = JSON.parse(body);
        expect(response.statusCode).to.equal(200);
        expect(body.currently).to.not.equal(undefined)
        done();
      });
    });

    // http://localhost:3000/weather/:location/today
    it('should return current day weather in json format', function(done) {
      var weatherURL =  url + "weather/goa/today?format=json";
      request.get(weatherURL, function(error, response, body) {
        body = JSON.parse(body);
        expect(body.daily.data).to.have.length(1);
        done();
      });
    });

    // http://localhost:3000/weather/:location/weekday
    // Allowed weedays - weekdays = [ "monday", "tuesday", "wednesday",
    // "thursday", "friday", "saturday", "sunday"]
    it('should return the weekday day weather in json format', function(done) {
      var weatherURL =  url + "weather/goa/saturday?format=json";
      request.get(weatherURL, function(error, response, body) {
        body = JSON.parse(body);
        expect(body.daily.data.length).to.be.greaterThan(1);
        done();
      });
    });

    // http://localhost:3000/weather/:location/weekday
    it('should return 422 for invalid weekday day weather in json format', function(done) {
      var weatherURL =  url + "weather/goa/somedayInAWeek?format=json";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(422);
        done();
      });
    });
  });

  describe('visit weather page with unsupported format', function() {
      // http://localhost:3000/weather/:location
      it('should return 200 page and weather information if location is existing in unsupported format', function(done) {
        var weatherURL =  url + "weather/goa?format=unsupported";
        request.get(weatherURL, function(error, response, body) {
          expect(response.statusCode).to.equal(422);
          expect(body).to.equal("Invalid request, unsupported format")
          done();
        });
      });
  });

});<commit_msg>test refactored<commit_after>process.env.NODE_ENV = 'test';
process.env.PORT = 8080;

var expect    = require('chai').expect;
var request = require('request');
var app = require("../../app");

// this is booted here cause then unit test wont run as process exits and app.close()
// is nom ore available
app.listen(process.env.PORT);

describe('Weather App', function() {
  var url = 'http://localhost:8080/';

  describe('visit home page', function() {
    it('should should have the contect of home page', function(done) {
      request.get(url, function(error, response, body) {
        expect(response.statusCode).to.equal(200);
        expect(body).to.include('Welcome to Weather App, Search for a location to get the weather information');
        done();
      });
    });
  });

  describe('visit weather page', function() {

    // supported url format for weather page
    // http://localhost:3000/weather/:location
    // http://localhost:3000/weather/:location/today
    // http://localhost:3000/weather/:location/:weekday

    it('should return 404 status if location is empty in url', function(done) {
      var weatherURL =  url + "weather";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(404);
        done();
      });
    });

    // http://localhost:3000/weather/:location
    it('should return 422 page if location is non existing', function(done) {
      var weatherURL =  url + "weather/nkownLocationOnEarthWhichGoogleCanntFind";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(422);
        done();
      });
    });

    // http://localhost:3000/weather/:location
    it('should return 200 page and weather information if location is existing', function(done) {
      var weatherURL =  url + "weather/goa";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(200);
        expect(body).to.include('Current Temprature');
        done();
      });
    });

    // http://localhost:3000/weather/:location/today
    it('should return current day weather', function(done) {
      var weatherURL =  url + "weather/goa/today";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(200);
        expect(body).to.include('Max Temprature');
        expect(body).to.include('Min Temprature');
        done();
      });
    });

    // http://localhost:3000/weather/:location/weekday
    // Allowed weedays - weekdays = [ "monday", "tuesday", "wednesday",
    // "thursday", "friday", "saturday", "sunday"]
    it('should return the weekday day weather', function(done) {
      var weatherURL =  url + "weather/goa/saturday";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(200);
        expect(body).to.include('Max Temprature');
        expect(body).to.include('Min Temprature');
        done();
      });
    });

    // http://localhost:3000/weather/:location/weekday
    it('should return 422 for invalid weekday day weather', function(done) {
      var weatherURL =  url + "weather/goa/somedayInAWeek";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(422);
        expect(body).to.include('Invalid request');
        done();
      });
    });
  });

  describe('visit weather page requesting json format', function() {

    // supported url format for weather page
    // http://localhost:3000/weather/:location?format=json
    // http://localhost:3000/weather/:location/todayformat=json
    // http://localhost:3000/weather/:location/:weekdayformat=json

    // http://localhost:3000/weather/:location
    it('should return 422 page if location is non existing in json format', function(done) {
      var weatherURL =  url + "weather/nkownLocationOnEarthWhichGoogleCanntFind?format=json";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(422);
        done();
      });
    });

    // http://localhost:3000/weather/:location
    it('should return 200 page and weather information if location is existing in json format', function(done) {
      var weatherURL =  url + "weather/goa?format=json";
      request.get(weatherURL, function(error, response, body) {
        body = JSON.parse(body);
        expect(response.statusCode).to.equal(200);
        expect(body.currently).to.not.equal(undefined)
        done();
      });
    });

    // http://localhost:3000/weather/:location/today
    it('should return current day weather in json format', function(done) {
      var weatherURL =  url + "weather/goa/today?format=json";
      request.get(weatherURL, function(error, response, body) {
        body = JSON.parse(body);
        expect(body.daily.data).to.have.length(1);
        done();
      });
    });

    // http://localhost:3000/weather/:location/weekday
    // Allowed weedays - weekdays = [ "monday", "tuesday", "wednesday",
    // "thursday", "friday", "saturday", "sunday"]
    it('should return the weekday day weather in json format', function(done) {
      var weatherURL =  url + "weather/goa/saturday?format=json";
      request.get(weatherURL, function(error, response, body) {
        body = JSON.parse(body);
        expect(body.daily.data.length).to.be.greaterThan(1);
        done();
      });
    });

    // http://localhost:3000/weather/:location/weekday
    it('should return 422 for invalid weekday day weather in json format', function(done) {
      var weatherURL =  url + "weather/goa/somedayInAWeek?format=json";
      request.get(weatherURL, function(error, response, body) {
        expect(response.statusCode).to.equal(422);
        done();
      });
    });
  });

  describe('visit weather page with unsupported format', function() {
      // http://localhost:3000/weather/:location
      it('should return 200 page and weather information if location is existing in unsupported format', function(done) {
        var weatherURL =  url + "weather/goa?format=unsupported";
        request.get(weatherURL, function(error, response, body) {
          expect(response.statusCode).to.equal(422);
          expect(body).to.equal("Invalid request, unsupported format")
          done();
        });
      });
  });

});<|endoftext|>


<commit_before>import { loadScript } from 'utils/deferred_load.js'
import ChromecastMessage, { chromecastCustomMessageBus } from './messages.js'
import StreamerAPI from 'api/streamer.js'

const chromecastSdkSenderJsUrl = '//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'

export const SenderEvent = {
  Initialized: 'sender-initialized',
  CastStateChanged: 'sender-cast-state-changed',
  SessionStateChanged: 'sender-session-state-changed',
  ChannelSent: 'sender-channel-sent',
  ChannelQueued: 'sender-channel-queued',
  CastError: 'sender-cast-error'
}

export default class ChromecastSender {

  constructor() {
    this.castContext = null
    this.queue = null

    riot.observable(this)

    this.on(SenderEvent.CastStateChanged, state => {
      if (this.queue && state == cast.framework.CastState.CONNECTED) {
        this._playImpl(...this.queue)
        this.queue = null
      }
    })

    window.__onGCastApiAvailable = isAvailable => {
      if (isAvailable)
        this.initialize()

      this.trigger(SenderEvent.Initialized)
    }

    loadScript(chromecastSdkSenderJsUrl)
  }

  initialize() {
    const castContextOptions = {
      receiverApplicationId: CHROMECAST_APP_ID,
      autoJoinPolicy:        chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
      resumeSavedSession:    true
    }

    this.on(SenderEvent.SessionStateChanged, state => this.onSessionStateChanged(state))

    let castContext = cast.framework.CastContext.getInstance()
    castContext.setOptions(castContextOptions)

    castContext.addEventListener(
      cast.framework.CastContextEventType.CAST_STATE_CHANGED,
      event => this.trigger(SenderEvent.CastStateChanged, event.castState)
    )
    castContext.addEventListener(
      cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
      event => this.trigger(SenderEvent.SessionStateChanged, event)
    )

    this.castContext = castContext
  }

  connect() {
    this.castContext.requestSession()
      .then(
        mbError => undefined,
        error   => undefined
      )
  }

  onSessionStateChanged(event) {
    if (event.session) {
      event.session.addMessageListener(chromecastCustomMessageBus, (_, data) => {
        let message = JSON.parse(data)

        this.trigger(message.type, message.data)
      })
    }
  }

  connected() {
    return this.castContext.getCastState() == cast.framework.CastState.CONNECTED
  }

  disconnect() {
    this.castContext.endCurrentSession(true)
  }

  sendCustomMessage(message) {
    let session = this.castContext.getCurrentSession()

    if (!session)
      return new Promise((_, onError) => { onError() })

    return session.sendMessage(
      chromecastCustomMessageBus,
      message
    )
  }

  deviceName() {
    let session = this.castContext.getCurrentSession()

    if (session)
      return session.getSessionObj().receiver.friendlyName

    return 'Unknown device'
  }

  play(channel, quality = null) {
    this.queue = null

    let castState = this.castContext.getCastState()

    if (castState == cast.framework.CastState.CONNECTED)
      this._playImpl(channel, quality)
    else if (castState == cast.framework.CastState.CONNECTING) {
      this.queue = [channel, quality]
      this.trigger(SenderEvent.ChannelQueued, channel)
    }
    else
      this.playLocally(channel, quality)
  }

  _playImpl(channel, quality) {
    let mediaInfo = new chrome.cast.media.MediaInfo(),
        request = new chrome.cast.media.LoadRequest(mediaInfo)

    request.customData = {
      channel: channel,
      quality: quality
    }

    this.castContext.getCurrentSession().loadMedia(request)
    this.trigger(SenderEvent.ChannelSent, channel)
  }

  playLocally(channel, quality = null) {
    let url = StreamerAPI.receiverUrl(channel)
    window.location = url
  }

  onCastError(error) {
    this.trigger(SenderEvent.CastError, error)
  }

}
<commit_msg>Falling back to local playback when casting is not available<commit_after>import { loadScript } from 'utils/deferred_load.js'
import ChromecastMessage, { chromecastCustomMessageBus } from './messages.js'
import StreamerAPI from 'api/streamer.js'

const chromecastSdkSenderJsUrl = '//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'

export const SenderEvent = {
  Initialized: 'sender-initialized',
  CastStateChanged: 'sender-cast-state-changed',
  SessionStateChanged: 'sender-session-state-changed',
  ChannelSent: 'sender-channel-sent',
  ChannelQueued: 'sender-channel-queued',
  CastError: 'sender-cast-error'
}

export default class ChromecastSender {

  constructor() {
    this.castContext = null
    this.queue = null

    riot.observable(this)

    this.on(SenderEvent.CastStateChanged, state => {
      if (this.queue && state == cast.framework.CastState.CONNECTED) {
        this._playImpl(...this.queue)
        this.queue = null
      }
    })

    window.__onGCastApiAvailable = isAvailable => {
      if (isAvailable)
        this.initialize()

      this.trigger(SenderEvent.Initialized)
    }

    loadScript(chromecastSdkSenderJsUrl)
  }

  initialize() {
    const castContextOptions = {
      receiverApplicationId: CHROMECAST_APP_ID,
      autoJoinPolicy:        chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
      resumeSavedSession:    true
    }

    this.on(SenderEvent.SessionStateChanged, state => this.onSessionStateChanged(state))

    let castContext = cast.framework.CastContext.getInstance()
    castContext.setOptions(castContextOptions)

    castContext.addEventListener(
      cast.framework.CastContextEventType.CAST_STATE_CHANGED,
      event => this.trigger(SenderEvent.CastStateChanged, event.castState)
    )
    castContext.addEventListener(
      cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
      event => this.trigger(SenderEvent.SessionStateChanged, event)
    )

    this.castContext = castContext
  }

  connect() {
    this.castContext.requestSession()
      .then(
        mbError => undefined,
        error   => undefined
      )
  }

  onSessionStateChanged(event) {
    if (event.session) {
      event.session.addMessageListener(chromecastCustomMessageBus, (_, data) => {
        let message = JSON.parse(data)

        this.trigger(message.type, message.data)
      })
    }
  }

  connected() {
    return this.castContext.getCastState() == cast.framework.CastState.CONNECTED
  }

  disconnect() {
    this.castContext.endCurrentSession(true)
  }

  sendCustomMessage(message) {
    let session = this.castContext.getCurrentSession()

    if (!session)
      return new Promise((_, onError) => { onError() })

    return session.sendMessage(
      chromecastCustomMessageBus,
      message
    )
  }

  deviceName() {
    let session = this.castContext.getCurrentSession()

    if (session)
      return session.getSessionObj().receiver.friendlyName

    return 'Unknown device'
  }

  play(channel, quality = null) {
    if (!this.castContext)
      this.playLocally(channel, quality)

    this.queue = null

    let castState = this.castContext.getCastState()

    if (castState == cast.framework.CastState.CONNECTED)
      this._playImpl(channel, quality)
    else if (castState == cast.framework.CastState.CONNECTING) {
      this.queue = [channel, quality]
      this.trigger(SenderEvent.ChannelQueued, channel)
    }
    else
      this.playLocally(channel, quality)
  }

  _playImpl(channel, quality) {
    let mediaInfo = new chrome.cast.media.MediaInfo(),
        request = new chrome.cast.media.LoadRequest(mediaInfo)

    request.customData = {
      channel: channel,
      quality: quality
    }

    this.castContext.getCurrentSession().loadMedia(request)
    this.trigger(SenderEvent.ChannelSent, channel)
  }

  playLocally(channel, quality = null) {
    let url = StreamerAPI.receiverUrl(channel)
    window.location = url
  }

  onCastError(error) {
    this.trigger(SenderEvent.CastError, error)
  }

}
<|endoftext|>