<commit_before>var url = 'http://muestra0.es/';
//var url = 'http://localhost/muestra0/';
var paramsId = '?json=1&include=id,title';
var paramPost = '?json=get_post&post_id=';
var vectorIds = [];
var vectorTitles = [];

function mostrarFecha(datos) {
    var cadena, dia, mes, anyo, string;

    cadena = datos.slice(0,10); // Eliminamos la hora, minutos y segundos
    anyo = datos.substring(0,4);
    mes = datos.substring(5,7);
    string = datos.substring(8,10); // Extraemos el día
    dia = string.trim();
    if(dia.length < 2) { dia = "0" + dia; } // Si sólo tiene un carácter, le añadimos un cero delante
    return "Publicado el " + dia + "-" + mes + "-" + anyo; // Montamos la fecha correctamente

}

function montarImagen(datos) {
    var imagen = '';
    if(jQuery.type(datos.post.attachments[0].images) !== 'array') { // Comprobamos que es un objeto
        if(datos.post.attachments[0].images['post-thumbnail'].width == 604) {
            var url = datos.post.attachments[0].images['post-thumbnail'].url;

            imagen += "";
        }
    } else { // Si es un array cargamos una imagen por defecto
        imagen += "";
    }
    return imagen;
}

function montarComentarios(datos) {
    var cadena = '';

    if(datos == 1) {
        cadena += datos + " comentario";
    } else {
        cadena += datos + " comentarios";
    }
    return cadena;
}

function mostrarContenido(datos) {
    var contenido = '', categoria = '', autor = '', fecha = '', comentarios = '';

    if (datos.post.attachments.length > 0) {
        contenido += montarImagen(datos);
    } else {
        contenido += ""; // Si la entrada no tiene imagen carga una por defecto
    }
    contenido += "
" + datos.post.title + "
";
    contenido += "
" + datos.post.excerpt + "
";
    categoria += "Categoría: " + datos.post.categories[0].title;
    autor += "Escrito por " + datos.post.author.name;
    fecha += mostrarFecha(datos.post.date);
    comentarios += montarComentarios(datos.post.comment_count);
    $('#contenidoDinamico').html(contenido);
    $('#categoria').html(categoria);
    $('#autor').html(autor);
    $('#fecha').html(fecha);
    $('#comentarios').html(comentarios);
}

function mostrarMensajeError() {
    setTimeout(function() {
        $.mobile.loading('show',{ // Muestra el widget de carga
            text: '¡Falló la conexión! Comprueba tu conexión a internet y vuelve a intentarlo.',
            textVisible: true,
            textonly: true,
            theme: 'b'
        });
    },5);
}

function mostrarMensajeCarga() {
    setTimeout(function() {
        $.mobile.loading('show',{ // Muestra el widget de carga
            text: 'Cargando...',
            textVisible: true,
            theme: 'b'
        });
    },1);
}

function obtenerIds() {
    $.ajax({
        url: url + paramsId,
        dataType: 'jsonp',
        type: 'GET',
        contentType: 'text/javascript',
        beforeSend: mostrarMensajeCarga(),
        success: function(data) {
            for(var i=0; i < data.count; i++) {
                vectorIds.push(data.posts[i].id);
                $('#enlace' + i).text(data.posts[i].title); // Montamos el nombre del ancla del submenu
            }
        },
        complete: function() {
            setTimeout(function(){
                $.mobile.loading('hide'); // Ocultamos el widget de carga
            },1);
        }
    });
}

function obtenerPost(posicionVector) {
    $.ajax({
        url: url + paramPost + vectorIds[posicionVector],
        dataType: 'jsonp',
        timeout: 5000,
        type: 'GET',
        contentType: 'text/javascript',
        beforeSend: mostrarMensajeCarga(),
        success: function(data) {
            mostrarContenido(data);
            $('#contenidoDinamico a').attr('data-ajax','false'); // Añadimos el data-ajax para las páginas fuera de la app
        },
        complete: function() {
            setTimeout(function(){
                $.mobile.loading('hide'); //Ocultamos el widget de carga
            },1);
        },
        error: function(jqXHR, textStatus, errorThrown) {
            mostrarMensajeError();
        }
    });
}

$(document).ready(function() {

    obtenerIds();
    obtenerPost(0);
    $('nav > ul li:nth-child(2)').on('click', function() { //Muestra y oculta el submenu de navegación
        $('nav > ul ul').fadeToggle();
    });
});<commit_msg>Solucionado problema con el submenu<commit_after>var url = 'http://muestra0.es/';
//var url = 'http://localhost/muestra0/';
var paramsId = '?json=1&include=id,title';
var paramPost = '?json=get_post&post_id=';
var vectorIds = [];
var vectorTitles = [];

function mostrarFecha(datos) {
    var cadena, dia, mes, anyo, string;

    cadena = datos.slice(0,10); // Eliminamos la hora, minutos y segundos
    anyo = datos.substring(0,4);
    mes = datos.substring(5,7);
    string = datos.substring(8,10); // Extraemos el día
    dia = string.trim();
    if(dia.length < 2) { dia = "0" + dia; } // Si sólo tiene un carácter, le añadimos un cero delante
    return "Publicado el " + dia + "-" + mes + "-" + anyo; // Montamos la fecha correctamente

}

function montarImagen(datos) {
    var imagen = '';
    if(jQuery.type(datos.post.attachments[0].images) !== 'array') { // Comprobamos que es un objeto
        if(datos.post.attachments[0].images['post-thumbnail'].width == 604) {
            var url = datos.post.attachments[0].images['post-thumbnail'].url;

            imagen += "";
        }
    } else { // Si es un array cargamos una imagen por defecto
        imagen += "";
    }
    return imagen;
}

function montarComentarios(datos) {
    var cadena = '';

    if(datos == 1) {
        cadena += datos + " comentario";
    } else {
        cadena += datos + " comentarios";
    }
    return cadena;
}

function mostrarContenido(datos) {
    var contenido = '', categoria = '', autor = '', fecha = '', comentarios = '';

    if (datos.post.attachments.length > 0) {
        contenido += montarImagen(datos);
    } else {
        contenido += ""; // Si la entrada no tiene imagen carga una por defecto
    }
    contenido += "
" + datos.post.title + "
";
    contenido += "
" + datos.post.excerpt + "
";
    categoria += "Categoría: " + datos.post.categories[0].title;
    autor += "Escrito por " + datos.post.author.name;
    fecha += mostrarFecha(datos.post.date);
    comentarios += montarComentarios(datos.post.comment_count);
    $('#contenidoDinamico').html(contenido);
    $('#categoria').html(categoria);
    $('#autor').html(autor);
    $('#fecha').html(fecha);
    $('#comentarios').html(comentarios);
}

function mostrarMensajeError() {
    setTimeout(function() {
        $.mobile.loading('show',{ // Muestra el widget de carga
            text: '¡Falló la conexión! Comprueba tu conexión a internet y vuelve a intentarlo.',
            textVisible: true,
            textonly: true,
            theme: 'b'
        });
    },5);
}

function mostrarMensajeCarga() {
    setTimeout(function() {
        $.mobile.loading('show',{ // Muestra el widget de carga
            text: 'Cargando...',
            textVisible: true,
            theme: 'b'
        });
    },1);
}

function obtenerIds() {
    $.ajax({
        url: url + paramsId,
        dataType: 'jsonp',
        type: 'GET',
        contentType: 'text/javascript',
        beforeSend: mostrarMensajeCarga(),
        success: function(data) {
            for(var i=0; i < data.count; i++) {
                vectorIds.push(data.posts[i].id);
                $('#enlace' + i).text(data.posts[i].title); // Montamos el nombre del ancla del submenu
            }
        },
        complete: function() {
            setTimeout(function(){
                $.mobile.loading('hide'); // Ocultamos el widget de carga
            },1);
        }
    });
}

function obtenerPost(posicionVector) {
    $.ajax({
        url: url + paramPost + vectorIds[posicionVector],
        dataType: 'jsonp',
        timeout: 5000,
        type: 'GET',
        contentType: 'text/javascript',
        beforeSend: mostrarMensajeCarga(),
        success: function(data) {
            mostrarContenido(data);
            $('#contenidoDinamico a').attr('data-ajax','false'); // Añadimos el data-ajax para las páginas fuera de la app
        },
        complete: function() {
            setTimeout(function(){
                $.mobile.loading('hide'); //Ocultamos el widget de carga
            },1);
        },
        error: function(jqXHR, textStatus, errorThrown) {
            mostrarMensajeError();
        }
    });
}

$(document).ready(function() {

    obtenerIds();
    obtenerPost(0);
    $('nav > ul > li:nth-child(2)').on('click', function() { //Muestra y oculta el submenu de navegación
        $('nav > ul ul').fadeToggle();
    });
});<|endoftext|>


<commit_before>if (window.NodeList && !NodeList.prototype.forEach) {
  NodeList.prototype.forEach = Array.prototype.forEach;
}

(function game() {
  'use strict';

  var achievements = JSON.parse(window.localStorage.getItem('achievements'));
  var pageKey = window.location.pathname.replace('/article/', '').replace('/','');
  var totalAchievements = document.querySelectorAll('[data-achievement-text]').length;
  var achievementsHeadline = document.getElementById('achievements-headline');
  var interaction = 'click';
  if (typeof PointerEvent === 'function') {
    interaction = 'pointerup';
  }

  function addAchievementToDOM(text) {
    var achievementItem = document.createElement('li');
    achievementItem.textContent = text;
    document.getElementById('achievements-list').appendChild(achievementItem);
  }

  if (achievements && achievements[pageKey] && achievements[pageKey].length > 0) {
    achievements[pageKey].forEach(addAchievementToDOM);
    achievementsHeadline.innerHTML += ' ' + achievements[pageKey].length + '/' + totalAchievements + '';
  }
  else {
    achievementsHeadline.innerHTML += ' 0/' + totalAchievements + '';
  }

  document.documentElement.addEventListener(interaction, function choiceClicks(event) {
    var achievementText = event.target.dataset.achievementText;
    var achievementType = event.target.dataset.achievementType;
    if (!event.target.closest('.game-button')) {
      return false;
    }

    // Achievement.
    if (achievementText) {
      if (!achievements || !achievements[pageKey]) {
        achievements = {};
        achievements[pageKey] = [];
      }
      if (achievements[pageKey].indexOf(achievementText) === -1) {
        document.getElementById('achievement-message').textContent = achievementText;
        document.getElementById('achievement').classList.add('achievement--active');
        document.getElementById('achievement').dataset.achievementType = achievementType;
        window.setTimeout(function hideAchievementMessage() {
          document.getElementById('achievement').classList.remove('achievement--active');
        }, 5000);
        addAchievementToDOM(achievementText);
        achievements[pageKey].push(achievementText);
        window.localStorage.setItem('achievements', JSON.stringify(achievements));
        document.getElementById('achievements-count').textContent = achievements[pageKey].length + '/' + totalAchievements;
      }
      if (achievements[pageKey].length === totalAchievements) {
        addAchievementToDOM('⭐ Beat the game to the max!');
        achievements[pageKey].push('⭐ Beat the game to the max!');
        window.localStorage.setItem('achievements', JSON.stringify(achievements));
        document.getElementById('achievements-count').textContent = ( totalAchievements + 1 ) + '/' + ( totalAchievements + 1 );
        document.getElementById('achievement-max').classList.add('achievement--active');
        document.querySelector('#achievement-max button').addEventListener('click', function click(event) {
          document.getElementById('achievement-max').classList.remove('achievement--active');
        }, false);
      }
    }

    if (event.target.dataset.targetId === 'restart') {
      // Hide all exposed paths.
      document.querySelectorAll('.game-scene')
        .forEach(function hideGamePath(element) {
          element.hidden = true;
        });
      // Re-enable all choices.
      document.querySelectorAll('.game-button')
        .forEach(function showChoices(element) {
          element.removeAttribute('disabled');
          element.classList.remove('game-button--selected');
        });
      // Show the starting segment.
      document.querySelector('.game-scene').hidden = false;
      // Scroll to top, aka the beginning of the story.
      window.scrollTo(0, 0);
      return;
    }

    // Show the path selected.
    document.getElementById(event.target.dataset.targetId).hidden = false;
    // Disable the buttons to choose the current path, and indicate your correct choice.
    event.target.parentNode.querySelectorAll('.game-button')
      .forEach(function disableButtons(button) {
        button.setAttribute('disabled', 'disabled');
        if (button.dataset.targetId === event.target.dataset.targetId) {
          button.classList.add('game-button--selected');
        }
      });
  }, false);
})();

// vim: foldmethod=marker
<commit_msg>Remove event param<commit_after>if (window.NodeList && !NodeList.prototype.forEach) {
  NodeList.prototype.forEach = Array.prototype.forEach;
}

(function game() {
  'use strict';

  var achievements = JSON.parse(window.localStorage.getItem('achievements'));
  var pageKey = window.location.pathname.replace('/article/', '').replace('/','');
  var totalAchievements = document.querySelectorAll('[data-achievement-text]').length;
  var achievementsHeadline = document.getElementById('achievements-headline');
  var interaction = 'click';
  if (typeof PointerEvent === 'function') {
    interaction = 'pointerup';
  }

  function addAchievementToDOM(text) {
    var achievementItem = document.createElement('li');
    achievementItem.textContent = text;
    document.getElementById('achievements-list').appendChild(achievementItem);
  }

  if (achievements && achievements[pageKey] && achievements[pageKey].length > 0) {
    achievements[pageKey].forEach(addAchievementToDOM);
    achievementsHeadline.innerHTML += ' ' + achievements[pageKey].length + '/' + totalAchievements + '';
  }
  else {
    achievementsHeadline.innerHTML += ' 0/' + totalAchievements + '';
  }

  document.documentElement.addEventListener(interaction, function choiceClicks(event) {
    var achievementText = event.target.dataset.achievementText;
    var achievementType = event.target.dataset.achievementType;
    if (!event.target.closest('.game-button')) {
      return false;
    }

    // Achievement.
    if (achievementText) {
      if (!achievements || !achievements[pageKey]) {
        achievements = {};
        achievements[pageKey] = [];
      }
      if (achievements[pageKey].indexOf(achievementText) === -1) {
        document.getElementById('achievement-message').textContent = achievementText;
        document.getElementById('achievement').classList.add('achievement--active');
        document.getElementById('achievement').dataset.achievementType = achievementType;
        window.setTimeout(function hideAchievementMessage() {
          document.getElementById('achievement').classList.remove('achievement--active');
        }, 5000);
        addAchievementToDOM(achievementText);
        achievements[pageKey].push(achievementText);
        window.localStorage.setItem('achievements', JSON.stringify(achievements));
        document.getElementById('achievements-count').textContent = achievements[pageKey].length + '/' + totalAchievements;
      }
      if (achievements[pageKey].length === totalAchievements) {
        addAchievementToDOM('⭐ Beat the game to the max!');
        achievements[pageKey].push('⭐ Beat the game to the max!');
        window.localStorage.setItem('achievements', JSON.stringify(achievements));
        document.getElementById('achievements-count').textContent = ( totalAchievements + 1 ) + '/' + ( totalAchievements + 1 );
        document.getElementById('achievement-max').classList.add('achievement--active');
        document.querySelector('#achievement-max button').addEventListener('click', function click() {
          document.getElementById('achievement-max').classList.remove('achievement--active');
        }, false);
      }
    }

    if (event.target.dataset.targetId === 'restart') {
      // Hide all exposed paths.
      document.querySelectorAll('.game-scene')
        .forEach(function hideGamePath(element) {
          element.hidden = true;
        });
      // Re-enable all choices.
      document.querySelectorAll('.game-button')
        .forEach(function showChoices(element) {
          element.removeAttribute('disabled');
          element.classList.remove('game-button--selected');
        });
      // Show the starting segment.
      document.querySelector('.game-scene').hidden = false;
      // Scroll to top, aka the beginning of the story.
      window.scrollTo(0, 0);
      return;
    }

    // Show the path selected.
    document.getElementById(event.target.dataset.targetId).hidden = false;
    // Disable the buttons to choose the current path, and indicate your correct choice.
    event.target.parentNode.querySelectorAll('.game-button')
      .forEach(function disableButtons(button) {
        button.setAttribute('disabled', 'disabled');
        if (button.dataset.targetId === event.target.dataset.targetId) {
          button.classList.add('game-button--selected');
        }
      });
  }, false);
})();

// vim: foldmethod=marker
<|endoftext|>


<commit_before>var Text = BaseShape.extend({
    init: function (config) {
        this.localFonts = ['Arial', 'Georgia'];
        this.cornerOffset = true;

        // if width is no provided we calculate it based on the number of words
        if (!config.width) {
            config.width = 400;
        }

        this._super(config);
    },
    draw: function () {
        this.txt = new Kinetic.Text({
            width: this.width,
            text: this.title,
            fill: 'black',
            fontSize: 24,
            lineHeight: 1.2,
            fontFamily: 'Arial',
            lineJoin: 'round'
        });

        this.group.add(this.txt);

        this.applyProperties(function () {
            this._updateHeight();
            this.redraw();

            this.ready = true;
            this.fire('ready', this);
        });
    },
    redraw: function () {
        this.drawStage();
    },
    update: function () {
        this._super();

        this.setSize(this.scale * this.group.getWidth(), this.scale * this.group.getHeight());

        this.scale = 1;
        this.group.setScale(1, 1);

        this.txt.setWidth(this.width);
        if (this.stroke) { this.stroke.setWidth(this.width); }

        this.setHeight(this.txt.getHeight());
    },
    setText: function (txt) {
        this.title = txt;
        this.txt.setText(txt);
        if (this.stroke) { this.stroke.setText(txt); }
        this._updateHeight();
        this.redraw();
    },
    getText: function () {
        return this.txt.getText();
    },
    setFontSize: function (size) {
        this.txt.setFontSize(size);
        if (this.stroke) { this.stroke.setFontSize(size); }
        this._updateHeight();
    },
    getFontSize: function () {
        return this.txt.getFontSize();
    },
    setLineHeight: function (height) {
        this.txt.setLineHeight(height);
        if (this.stroke) { this.stroke.setLineHeight(height); }
        this._updateHeight();
    },
    getLineHeight: function () {
        return this.txt.getLineHeight();
    },
    setFontFamily: function (family, callback) {
        if (jQuery.inArray(family, this.localFonts) !== -1) {
            this._setFontFamily(family);
            callback(this);
        } else {
            this._loadFont(family, function () {
                this._setFontFamily(family);
                callback(this);
            }.bind(this));
        }
    },
    getFontFamily: function () {
        return this.txt.getFontFamily();
    },
    setTextAlign: function (align) {
        this.txt.setAlign(align);
        if (this.stroke) { this.stroke.setAlign(align); }
    },
    getTextAlign: function () {
        return this.txt.getAlign();
    },
    setTextFill: function (color) {
        this.txt.setFill(color);
        if (this.stroke) { this.stroke.setFill(color); }
    },
    getTextFill: function () {
        return this.txt.getFill();
    },
    setStrokeColor: function (color) {
        this._createStroke();
        this.stroke.setStroke(color);
    },
    getStrokeColor: function () {
        if (this.stroke)
            return this.stroke.getStroke();
    },
    setStrokeWidth: function (width) {
        if (width === 0) {
            this._removeStroke();
        } else {
            this._createStroke();
            this.stroke.setStrokeWidth(width);
        }
    },
    getStrokeWidth: function () {
        if (this.stroke)
            return this.stroke.getStrokeWidth();
    },
    setOpacity: function (opacity) {
        this.txt.setOpacity(opacity);
        if (this.stroke) { this.stroke.setOpacity(opacity); }
    },
    getOpacity: function () {
        return this.txt.getOpacity();
    },
    getProperties: function () {
        var cfg = this.getShadowConfig();
        cfg = cfg.concat(this._getPropertyConfig(['TextFill', 'FontSize', 'FontFamily', 'LineHeight', 'TextAlign']));
        if (this.stroke) {
            cfg = cfg.concat(this._getPropertyConfig(['StrokeColor', 'StrokeWidth']));
        }
        return cfg;
    },
    getConfig: function () {
        return jQuery.extend({ title: this.title }, this._super());
    },
    _updateHeight: function () {
        this.setHeight(this.txt.getHeight());
    },
    _setFontFamily: function (family) {
        this.txt.setFontFamily(family);
        if (this.stroke) { this.stroke.setFontFamily(family); }
        this._updateHeight();
    },
    _loadFont: function (family, complete) {
        this.fire('progress-start', 'Loading Font', this);
        this.ready = false;

        var scope = this;
		var loadComplete = function() {
            complete();
            scope.ready = true;
            scope.fire('progress-complete', scope);
		};

        WebFont.load({
            google: {
                families: [family]
            },
            active: function () {
				loadComplete();
            },
            inactive: function () {
                loadComplete();
				scope.fire('error', 'The font could not be loaded', this);
            }
        });
    },
    _createStroke: function () {
        if (!this.stroke) {
            this.stroke = this.txt.clone();
            this.stroke.disableFill(); // this causes the shadow to be applied to the stroke
            this.stroke.setOpacity(this.txt.getOpacity())
            this.group.add(this.stroke);
            this.stroke.moveToBottom();

            this._transferShadow(this.txt, this.stroke);
        }
    },
    _removeStroke: function () {
        if (this.stroke) {

            this._transferShadow(this.stroke, this.txt);

            this.stroke.remove();
            this.stroke = null;
        }
    },
    _getShadowedShape: function () {
        return this.stroke || this.txt;
    },
    _transferShadow: function (src, dest) {
        if (src.hasShadow()) {
            dest.setShadowColor(src.getShadowColor());
            dest.setShadowBlur(src.getShadowBlur());
            dest.setShadowOffset(src.getShadowOffset());
            dest.setShadowOpacity(src.getShadowOpacity());
            src.disableShadow();
            dest.enableShadow();
        }
    },
});<commit_msg>Update text.js<commit_after>var Text = BaseShape.extend({
    init: function (config) {
        this.localFonts = ['Arial', 'Georgia'];
        this.cornerOffset = true;

        // if width is no provided we calculate it based on the number of words
        if (!config.width) {
            config.width = 400;
        }

        this._super(config);
    },
    draw: function () {
        this.txt = new Kinetic.Text({
            width: this.width,
            text: this.title,
            fill: 'black',
            fontSize: 24,
            lineHeight: 1.2,
            fontFamily: 'Arial',
            lineJoin: 'round'
        });

        this.group.add(this.txt);

        this.applyProperties(function () {
            this._updateHeight();
            this.redraw();

            this.ready = true;
            this.fire('ready', this);
        });
    },
    redraw: function () {
        this.drawStage();
    },
    update: function () {
        this._super();

        this.setSize(this.scale * this.group.getWidth(), this.scale * this.group.getHeight());

        this.scale = 1;
        this.group.setScale(1, 1);

        this.txt.setWidth(this.width);
        if (this.stroke) { this.stroke.setWidth(this.width); }

        this.setHeight(this.txt.getHeight());
    },
    setText: function (txt) {
        if (this.selected) { this.title = txt; this.txt.setText(txt); }
        if (this.stroke) { this.stroke.setText(txt); }
        this._updateHeight();
        this.redraw();
    },
    getText: function () {
        return this.txt.getText();
    },
    setFontSize: function (size) {
        this.txt.setFontSize(size);
        if (this.stroke) { this.stroke.setFontSize(size); }
        this._updateHeight();
    },
    getFontSize: function () {
        return this.txt.getFontSize();
    },
    setLineHeight: function (height) {
        this.txt.setLineHeight(height);
        if (this.stroke) { this.stroke.setLineHeight(height); }
        this._updateHeight();
    },
    getLineHeight: function () {
        return this.txt.getLineHeight();
    },
    setFontFamily: function (family, callback) {
        if (jQuery.inArray(family, this.localFonts) !== -1) {
            this._setFontFamily(family);
            callback(this);
        } else {
            this._loadFont(family, function () {
                this._setFontFamily(family);
                callback(this);
            }.bind(this));
        }
    },
    getFontFamily: function () {
        return this.txt.getFontFamily();
    },
    setTextAlign: function (align) {
        this.txt.setAlign(align);
        if (this.stroke) { this.stroke.setAlign(align); }
    },
    getTextAlign: function () {
        return this.txt.getAlign();
    },
    setTextFill: function (color) {
        this.txt.setFill(color);
        if (this.stroke) { this.stroke.setFill(color); }
    },
    getTextFill: function () {
        return this.txt.getFill();
    },
    setStrokeColor: function (color) {
        this._createStroke();
        this.stroke.setStroke(color);
    },
    getStrokeColor: function () {
        if (this.stroke)
            return this.stroke.getStroke();
    },
    setStrokeWidth: function (width) {
        if (width === 0) {
            this._removeStroke();
        } else {
            this._createStroke();
            this.stroke.setStrokeWidth(width);
        }
    },
    getStrokeWidth: function () {
        if (this.stroke)
            return this.stroke.getStrokeWidth();
    },
    setOpacity: function (opacity) {
        this.txt.setOpacity(opacity);
        if (this.stroke) { this.stroke.setOpacity(opacity); }
    },
    getOpacity: function () {
        return this.txt.getOpacity();
    },
    getProperties: function () {
        var cfg = this.getShadowConfig();
        cfg = cfg.concat(this._getPropertyConfig(['TextFill', 'FontSize', 'FontFamily', 'LineHeight', 'TextAlign']));
        if (this.stroke) {
            cfg = cfg.concat(this._getPropertyConfig(['StrokeColor', 'StrokeWidth']));
        }
        return cfg;
    },
    getConfig: function () {
        return jQuery.extend({ title: this.title }, this._super());
    },
    _updateHeight: function () {
        this.setHeight(this.txt.getHeight());
    },
    _setFontFamily: function (family) {
        this.txt.setFontFamily(family);
        if (this.stroke) { this.stroke.setFontFamily(family); }
        this._updateHeight();
    },
    _loadFont: function (family, complete) {
        this.fire('progress-start', 'Loading Font', this);
        this.ready = false;

        var scope = this;
		var loadComplete = function() {
            complete();
            scope.ready = true;
            scope.fire('progress-complete', scope);
		};

        WebFont.load({
            google: {
                families: [family]
            },
            active: function () {
				loadComplete();
            },
            inactive: function () {
                loadComplete();
				scope.fire('error', 'The font could not be loaded', this);
            }
        });
    },
    _createStroke: function () {
        if (!this.stroke) {
            this.stroke = this.txt.clone();
            this.stroke.disableFill(); // this causes the shadow to be applied to the stroke
            this.stroke.setOpacity(this.txt.getOpacity())
            this.group.add(this.stroke);
            this.stroke.moveToBottom();

            this._transferShadow(this.txt, this.stroke);
        }
    },
    _removeStroke: function () {
        if (this.stroke) {

            this._transferShadow(this.stroke, this.txt);

            this.stroke.remove();
            this.stroke = null;
        }
    },
    _getShadowedShape: function () {
        return this.stroke || this.txt;
    },
    _transferShadow: function (src, dest) {
        if (src.hasShadow()) {
            dest.setShadowColor(src.getShadowColor());
            dest.setShadowBlur(src.getShadowBlur());
            dest.setShadowOffset(src.getShadowOffset());
            dest.setShadowOpacity(src.getShadowOpacity());
            src.disableShadow();
            dest.enableShadow();
        }
    },
});
<|endoftext|>


<commit_before>var express = require('../')
  , request = require('./support/http');

describe('app', function(){
  describe('.VERB()', function(){
    it('should only call an error handling routing callback when an error is propagated', function(done){
      var app = express();

      app.get('/', function(req, res, next){
        req.thruSecondCallback = false;
        req.thruThirdCallback = false;
        req.thruFourthCallback = false;
        next(new Error('fabricated error'));
      }, function(req, res, next) {
        req.thruSecondCallback = true;
        next();
      }, function(err, req, res, next){
        err.message.should.equal('fabricated error');
        req.thruThirdCallback = true;
        next();
      }, function(err, req, res, next){
        req.thruFourthCallback = true;
        next();
      }, function(req, res){
        req.thruSecondCallback.should.equal(false);
        req.thruThirdCallback.should.equal(true);
        req.thruFourthCallback.should.equal(false);
        res.status(204);
        res.end();
      });

      request(app)
      .get('/')
      .expect(204, done);
    })
  })
})
<commit_msg>refactor routes.error tests<commit_after>var express = require('../')
  , request = require('./support/http');

describe('app', function(){
  describe('.VERB()', function(){
    it('should only call an error handling routing callback when an error is propagated', function(done){
      var app = express();

      var a = false;
      var b = false;
      var c = false;
      var d = false;

      app.get('/', function(req, res, next){
        next(new Error('fabricated error'));
      }, function(req, res, next) {
        a = true;
        next();
      }, function(err, req, res, next){
        b = true;
        err.message.should.equal('fabricated error');
        next(err);
      }, function(err, req, res, next){
        c = true;
        err.message.should.equal('fabricated error');
        next();
      }, function(err, req, res, next){
        d = true;
        next();
      }, function(req, res){
        a.should.be.false;
        b.should.be.true;
        c.should.be.true;
        d.should.be.false;
        res.send(204);
      });

      request(app)
      .get('/')
      .expect(204, done);
    })
  })
})
<|endoftext|>


<commit_before>var Connection = require('../lib/Connection');

var fs = require('fs'),
    cpspawn = require('child_process').spawn,
    cpexec = require('child_process').exec,
    path = require('path'),
    join = path.join,
    inspect = require('util').inspect,
    assert = require('assert');

var t = -1,
    forkedTest,
    group = path.basename(__filename, '.js') + '/',
    tempdir = join(__dirname, 'temp'),
    fixturesdir = join(__dirname, 'fixtures');

var SSHD_PORT,
    HOST_FINGERPRINT = '64254520742d3d0792e918f3ce945a64',
    PRIVATE_KEY = fs.readFileSync(join(fixturesdir, 'id_rsa')),
    USER = process.env.LOGNAME || process.env.USER || process.env.USERNAME;
    DEFAULT_SSHD_OPTS = {
      'AddressFamily': 'any',
      'AllowUsers': USER,
      'AuthorizedKeysFile': join(fixturesdir, 'authorized_keys'),
      'Banner': 'none',
      'Compression': 'no',
      'HostbasedAuthentication': 'no',
      'HostKey': join(fixturesdir, 'ssh_host_rsa_key'),
      'ListenAddress': 'localhost',
      'LogLevel': 'FATAL',
      'PasswordAuthentication': 'no',
      'PermitRootLogin': 'no',
      'Protocol': '2',
      'PubkeyAuthentication': 'yes',
      'Subsystem': 'sftp internal-sftp',
      'TCPKeepAlive': 'yes',
      'UseDNS': 'no',
      //'UsePrivilegeSeparation': 'no'
    };

var tests = [
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer(function() {
        var error,
            ready;
        conn.on('ready', function() {
          ready = true;
          this.end();
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Authenticate with a key'
  },
  { run: function() {
      // use ssh-agent with a command (this test) to make agent cleanup easier
      if (!process.env.SSH_AUTH_SOCK) {
        var proc = cpspawn('ssh-agent',
                           [process.argv[0], process.argv[1], t],
                           { stdio: 'inherit' });
        proc.on('exit', function(code, signal) {
          if (code === 0 && !signal)
            next();
        });
        return;
      }

      var self = this,
          what = this.what,
          conn = new Connection();

      // add key first
      cpexec('ssh-add ' + join(fixturesdir, 'id_rsa'), function() {
        startServer(function() {
          var error,
              ready;
          conn.on('ready', function() {
            ready = true;
            this.end();
          }).on('error', function(err) {
            error = err;
          }).on('close', function() {
            assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
            assert(ready, makeMsg(what, 'Expected ready'));
          }).connect(self.config);
        });
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      agent: process.env.SSH_AUTH_SOCK
    },
    what: 'Authenticate with an agent'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection(),
          fingerprint;
      this.config.hostVerifier = function(host) {
        fingerprint = host;
        return true; // perform actual verification at the end
      };
      startServer(function() {
        var error,
            ready;
        conn.on('ready', function() {
          ready = true;
          this.end();
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          assert(HOST_FINGERPRINT === fingerprint,
                 makeMsg(what, 'Host fingerprint mismatch.\nSaw:\n'
                               + inspect(fingerprint)
                               + '\nExpected:\n'
                               + inspect(HOST_FINGERPRINT)));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY,
      hostHash: 'md5'
    },
    what: 'Verify host fingerprint'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection(),
          bannerPath = join(fixturesdir, 'banner'),
          bannerSent = fs.readFileSync(bannerPath,
                                       { encoding: 'utf8' });
      startServer({ 'Banner': bannerPath }, function() {
        var error,
            ready,
            bannerRecvd;
        conn.on('banner', function(msg) {
          bannerRecvd = msg;
        }).on('ready', function() {
          ready = true;
          this.exec('uptime', function(err) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            conn.end();
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          assert(bannerSent === bannerRecvd,
                 makeMsg(what, 'Banner mismatch.\nSaw:\n'
                               + inspect(bannerRecvd)
                               + '\nExpected:\n'
                               + inspect(bannerSent)));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Banner message'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer(function() {
        var error,
            ready;
        conn.on('ready', function() {
          ready = true;
          this.exec('uptime', function(err) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            conn.end();
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Simple exec'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer({ 'AcceptEnv': 'SSH2NODETEST' }, function() {
        var error,
            ready,
            out;
        conn.on('ready', function() {
          ready = true;
          this.exec('echo -n $SSH2NODETEST',
                    { env: { SSH2NODETEST: self.expected } },
                    function(err, stream) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            stream.stderr.resume();
            stream.on('data', function(d) {
              if (!out)
                out = d;
              else
                out += d;
            }).on('end', function() {
              conn.end();
            }).setEncoding('ascii');
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          assert(out === self.expected,
                 makeMsg(what, 'Environment variable mismatch.\nSaw:\n'
                               + inspect(out)
                               + '\nExpected:\n'
                               + inspect(self.expected)));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    expected: 'Hello from node.js!!!',
    what: 'Exec with environment set'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer(function() {
        var error,
            ready,
            out;
        conn.on('ready', function() {
          ready = true;
          this.exec('if [ -t 1 ] ; then echo -n terminal; fi',
                    { pty: true },
                    function(err, stream) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            stream.stderr.resume();
            stream.on('data', function(d) {
              if (!out)
                out = d;
              else
                out += d;
            }).on('end', function() {
              conn.end();
            }).setEncoding('ascii');
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          if (out)
            out = out.split('\n').slice(-1)[0];
          assert(out === self.expected,
                 makeMsg(what, 'Exec output mismatch.\nSaw:\n'
                               + inspect(out)
                               + '\nExpected:\n'
                               + inspect(self.expected)));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    expected: 'terminal',
    what: 'Exec with pty set'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer(function() {
        var error,
            ready;
        conn.on('ready', function() {
          ready = true;
          this.shell(function(err) {
            assert(!err, makeMsg(what, 'Unexpected shell error: ' + err));
            conn.end();
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Simple shell'
  },
];

function startServer(opts, listencb, exitcb) {
  var sshdOpts = {},
      cmd,
      key,
      val;
  for (key in DEFAULT_SSHD_OPTS)
    sshdOpts[key] = DEFAULT_SSHD_OPTS[key];
  if (typeof opts === 'function') {
    exitcb = listencb;
    listencb = opts;
    opts = undefined;
  }
  if (opts) {
    for (key in opts)
      sshdOpts[key] = opts[key];
  }

  cmd = '`which sshd` -p '
        + SSHD_PORT
        + ' -Dde -f '
        + join(fixturesdir, 'sshd_config');

  for (key in sshdOpts) {
    val = ''+sshdOpts[key];
    if (val.indexOf(' ') > -1)
      val = '"' + val + '"';
    cmd += ' -o ' + key + '=' + val;
  }

  tests[t].config.port = SSHD_PORT;

  stopWaiting = false;
  cpexec(cmd, function(err, stdout, stderr) {
    stopWaiting = true;
    //exitcb(err, stdout, stderr);
  });
  waitForSshd(listencb);
}

function cleanupTemp() {
  // clean up any temporary files left over
  fs.readdirSync(tempdir).forEach(function(file) {
    if (file !== '.gitignore')
      fs.unlinkSync(join(tempdir, file));
  });
}

function next() {
  if (t === forkedTest || t === tests.length - 1)
    return;
  cleanupTemp();
  var v = tests[++t];
  v.run.call(v);
}

function makeMsg(what, msg) {
  return '[' + group + what + ']: ' + msg;
}

var stopWaiting = false;
function waitForSshd(cb) {
  if (stopWaiting)
    return;
  cpexec('lsof -a -u '
         + USER
         + ' -c sshd -i tcp@localhost:'
         + SSHD_PORT
         + ' &>/dev/null', function(err, stdout) {
    if (err) {
      return setTimeout(function() {
        waitForSshd(cb);
      }, 50);
    }
    cb();
  });
}

function cleanup(cb) {
  cleanupTemp();
  cpexec('lsof -Fp -a -u '
         + USER
         + ' -c sshd -i tcp@localhost:'
         + SSHD_PORT, function(err, stdout) {
    if (!err) {
      var pid = parseInt(stdout.trim().replace(/[^\d]/g, ''), 10);
      if (typeof pid === 'number' && !isNaN(pid)) {
        try {
          process.kill(pid);
        } catch (ex) {}
      }
    }
    cb();
  });
}

process.once('uncaughtException', function(err) {
  cleanup(function() {
    throw err;
  });
});
process.once('exit', function() {
  cleanup(function() {
    assert(t === tests.length - 1,
           makeMsg('_exit',
                   'Only finished ' + (t + 1) + '/' + tests.length + ' tests'));
  });
});

// find an unused port for sshd to listen on ...
cpexec('netstat -nl --inet --inet6', function(err, stdout) {
  assert(!err, 'Unable to find a free port for starting sshd');
  var portsInUse = stdout.trim()
                         .split('\n')
                         .slice(2) // skip two header lines
                         .map(function(line) {
                           var addr = line.split(/[ \t]+/g)[3];
                           return parseInt(
                            addr.substr(addr.lastIndexOf(':') + 1),
                            10
                           );
                         });
  for (var port = 1025; port < 65535; ++port) {
    if (portsInUse.indexOf(port)) {
      SSHD_PORT = port;
      // start tests
      return next();
    }
  }
  assert(false, 'Unable to find a free port for starting sshd');
});

// check for forked process
if (process.argv.length > 2) {
  forkedTest = parseInt(process.argv[2], 10);
  if (!isNaN(forkedTest))
    t = forkedTest - 1;
  else
    process.exit(100);
}
  <commit_msg>test: add more integration tests<commit_after>var Connection = require('../lib/Connection');

var fs = require('fs'),
    cpspawn = require('child_process').spawn,
    cpexec = require('child_process').exec,
    path = require('path'),
    join = path.join,
    inspect = require('util').inspect,
    assert = require('assert');

var t = -1,
    forkedTest,
    group = path.basename(__filename, '.js') + '/',
    tempdir = join(__dirname, 'temp'),
    fixturesdir = join(__dirname, 'fixtures');

var SSHD_PORT,
    HOST_FINGERPRINT = '64254520742d3d0792e918f3ce945a64',
    PRIVATE_KEY = fs.readFileSync(join(fixturesdir, 'id_rsa')),
    USER = process.env.LOGNAME || process.env.USER || process.env.USERNAME;
    DEFAULT_SSHD_OPTS = {
      'AddressFamily': 'any',
      'AllowUsers': USER,
      'AuthorizedKeysFile': join(fixturesdir, 'authorized_keys'),
      'Banner': 'none',
      'Compression': 'no',
      'HostbasedAuthentication': 'no',
      'HostKey': join(fixturesdir, 'ssh_host_rsa_key'),
      'ListenAddress': 'localhost',
      'LogLevel': 'FATAL',
      'PasswordAuthentication': 'no',
      'PermitRootLogin': 'no',
      'Protocol': '2',
      'PubkeyAuthentication': 'yes',
      'Subsystem': 'sftp internal-sftp',
      'TCPKeepAlive': 'yes',
      'UseDNS': 'no',
      //'UsePrivilegeSeparation': 'no'
    };

var tests = [
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer(function() {
        var error,
            ready;
        conn.on('ready', function() {
          ready = true;
          this.end();
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Authenticate with a key'
  },
  { run: function() {
      // use ssh-agent with a command (this test) to make agent cleanup easier
      if (!process.env.SSH_AUTH_SOCK) {
        var proc = cpspawn('ssh-agent',
                           [process.argv[0], process.argv[1], t],
                           { stdio: 'inherit' });
        proc.on('exit', function(code, signal) {
          if (code === 0 && !signal)
            next();
        });
        return;
      }

      var self = this,
          what = this.what,
          conn = new Connection();

      // add key first
      cpexec('ssh-add ' + join(fixturesdir, 'id_rsa'), function() {
        startServer(function() {
          var error,
              ready;
          conn.on('ready', function() {
            ready = true;
            this.end();
          }).on('error', function(err) {
            error = err;
          }).on('close', function() {
            assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
            assert(ready, makeMsg(what, 'Expected ready'));
          }).connect(self.config);
        });
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      agent: process.env.SSH_AUTH_SOCK
    },
    what: 'Authenticate with an agent'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection(),
          fingerprint;
      this.config.hostVerifier = function(host) {
        fingerprint = host;
        return true; // perform actual verification at the end
      };
      startServer(function() {
        var error,
            ready;
        conn.on('ready', function() {
          ready = true;
          this.end();
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          assert(HOST_FINGERPRINT === fingerprint,
                 makeMsg(what, 'Host fingerprint mismatch.\nSaw:\n'
                               + inspect(fingerprint)
                               + '\nExpected:\n'
                               + inspect(HOST_FINGERPRINT)));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY,
      hostHash: 'md5'
    },
    what: 'Verify host fingerprint'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection(),
          bannerPath = join(fixturesdir, 'banner'),
          bannerSent = fs.readFileSync(bannerPath,
                                       { encoding: 'utf8' });
      startServer({ 'Banner': bannerPath }, function() {
        var error,
            ready,
            bannerRecvd;
        conn.on('banner', function(msg) {
          bannerRecvd = msg;
        }).on('ready', function() {
          ready = true;
          this.exec('uptime', function(err) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            conn.end();
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          assert(bannerSent === bannerRecvd,
                 makeMsg(what, 'Banner mismatch.\nSaw:\n'
                               + inspect(bannerRecvd)
                               + '\nExpected:\n'
                               + inspect(bannerSent)));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Banner message'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer(function() {
        var error,
            ready;
        conn.on('ready', function() {
          ready = true;
          this.exec('uptime', function(err) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            conn.end();
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Simple exec'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer({ 'AcceptEnv': 'SSH2NODETEST' }, function() {
        var error,
            ready,
            out;
        conn.on('ready', function() {
          ready = true;
          this.exec('echo -n $SSH2NODETEST',
                    { env: { SSH2NODETEST: self.expected } },
                    function(err, stream) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            stream.stderr.resume();
            stream.on('data', function(d) {
              if (!out)
                out = d;
              else
                out += d;
            }).on('end', function() {
              conn.end();
            }).setEncoding('ascii');
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          assert(out === self.expected,
                 makeMsg(what, 'Environment variable mismatch.\nSaw:\n'
                               + inspect(out)
                               + '\nExpected:\n'
                               + inspect(self.expected)));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    expected: 'Hello from node.js!!!',
    what: 'Exec with environment set'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer(function() {
        var error,
            ready,
            out;
        conn.on('ready', function() {
          ready = true;
          this.exec('if [ -t 1 ] ; then echo -n terminal; fi',
                    { pty: true },
                    function(err, stream) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            stream.stderr.resume();
            stream.on('data', function(d) {
              if (!out)
                out = d;
              else
                out += d;
            }).on('end', function() {
              conn.end();
            }).setEncoding('ascii');
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          if (out)
            out = out.split('\n').slice(-1)[0];
          assert(out === self.expected,
                 makeMsg(what, 'Exec output mismatch.\nSaw:\n'
                               + inspect(out)
                               + '\nExpected:\n'
                               + inspect(self.expected)));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    expected: 'terminal',
    what: 'Exec with pty set'
  },
  { run: function() {
      // use ssh-agent with a command (this test) to make agent cleanup easier
      if (!process.env.SSH_AUTH_SOCK) {
        var proc = cpspawn('ssh-agent',
                           [process.argv[0], process.argv[1], t],
                           { stdio: 'inherit' });
        proc.on('exit', function(code, signal) {
          if (code === 0 && !signal)
            next();
        });
        return;
      }

      var self = this,
          what = this.what,
          conn = new Connection();

      // add key first
      cpexec('ssh-add ' + join(fixturesdir, 'id_rsa'), function() {
        startServer(function() {
          var error,
              ready,
              out;
          conn.on('ready', function() {
            ready = true;
            this.exec('echo -n $SSH_AUTH_SOCK', function(err, stream) {
              assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
              stream.stderr.resume();
              stream.on('data', function(d) {
                if (!out)
                  out = d;
                else
                  out += d;
              }).on('end', function() {
                conn.end();
              }).setEncoding('ascii');
            });
          }).on('error', function(err) {
            error = err;
          }).on('close', function() {
            assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
            assert(ready, makeMsg(what, 'Expected ready'));
            assert(out && out.length,
                   makeMsg(what, 'Expected SSH_AUTH_SOCK in exec environment'));
          }).connect(self.config);
        });
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      agent: process.env.SSH_AUTH_SOCK,
      agentForward: true
    },
    what: 'Exec with OpenSSH agent forwarding'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer({
        'X11DisplayOffset': '50',
        'X11Forwarding': 'yes',
        'X11UseLocalhost': 'yes'
      }, function() {
        var error,
            ready,
            sawX11 = false;
        conn.on('ready', function() {
          ready = true;
          this.exec('xeyes', { x11: true }, function(err, stream) {
            assert(!err, makeMsg(what, 'Unexpected exec error: ' + err));
            stream.resume();
            stream.stderr.resume();
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          assert(sawX11, makeMsg(what, 'Expected X11 request'));
          next();
        }).on('x11', function(details, accept, reject) {
          sawX11 = true;
          conn.end();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Exec with X11 forwarding'
  },
  { run: function() {
      var self = this,
          what = this.what,
          conn = new Connection();
      startServer(function() {
        var error,
            ready;
        conn.on('ready', function() {
          ready = true;
          this.shell(function(err) {
            assert(!err, makeMsg(what, 'Unexpected shell error: ' + err));
            conn.end();
          });
        }).on('error', function(err) {
          error = err;
        }).on('close', function() {
          assert(!error, makeMsg(what, 'Unexpected client error: ' + error));
          assert(ready, makeMsg(what, 'Expected ready'));
          next();
        }).connect(self.config);
      });
    },
    config: {
      host: 'localhost',
      username: USER,
      privateKey: PRIVATE_KEY
    },
    what: 'Simple shell'
  },
];

function startServer(opts, listencb, exitcb) {
  var sshdOpts = {},
      cmd,
      key,
      val;
  for (key in DEFAULT_SSHD_OPTS)
    sshdOpts[key] = DEFAULT_SSHD_OPTS[key];
  if (typeof opts === 'function') {
    exitcb = listencb;
    listencb = opts;
    opts = undefined;
  }
  if (opts) {
    for (key in opts)
      sshdOpts[key] = opts[key];
  }

  cmd = '`which sshd` -p '
        + SSHD_PORT
        + ' -Dde -f '
        + join(fixturesdir, 'sshd_config');

  for (key in sshdOpts) {
    val = ''+sshdOpts[key];
    if (val.indexOf(' ') > -1)
      val = '"' + val + '"';
    cmd += ' -o ' + key + '=' + val;
  }

  tests[t].config.port = SSHD_PORT;

  stopWaiting = false;
  cpexec(cmd, function(err, stdout, stderr) {
    stopWaiting = true;
    //exitcb(err, stdout, stderr);
  });
  waitForSshd(listencb);
}

function cleanupTemp() {
  // clean up any temporary files left over
  fs.readdirSync(tempdir).forEach(function(file) {
    if (file !== '.gitignore')
      fs.unlinkSync(join(tempdir, file));
  });
}

function next() {
  if (t === forkedTest || t === tests.length - 1)
    return;
  cleanupTemp();
  var v = tests[++t];
  v.run.call(v);
}

function makeMsg(what, msg) {
  return '[' + group + what + ']: ' + msg;
}

var stopWaiting = false;
function waitForSshd(cb) {
  if (stopWaiting)
    return;
  cpexec('lsof -a -u '
         + USER
         + ' -c sshd -i tcp@localhost:'
         + SSHD_PORT
         + ' &>/dev/null', function(err, stdout) {
    if (err) {
      return setTimeout(function() {
        waitForSshd(cb);
      }, 50);
    }
    cb();
  });
}

function cleanup(cb) {
  cleanupTemp();
  cpexec('lsof -Fp -a -u '
         + USER
         + ' -c sshd -i tcp@localhost:'
         + SSHD_PORT, function(err, stdout) {
    if (!err) {
      var pid = parseInt(stdout.trim().replace(/[^\d]/g, ''), 10);
      if (typeof pid === 'number' && !isNaN(pid)) {
        try {
          process.kill(pid);
        } catch (ex) {}
      }
    }
    cb();
  });
}

process.once('uncaughtException', function(err) {
  cleanup(function() {
    throw err;
  });
});
process.once('exit', function() {
  cleanup(function() {
    assert(t === tests.length - 1,
           makeMsg('_exit',
                   'Only finished ' + (t + 1) + '/' + tests.length + ' tests'));
  });
});

// find an unused port for sshd to listen on ...
cpexec('netstat -nl --inet --inet6', function(err, stdout) {
  assert(!err, 'Unable to find a free port for starting sshd');
  var portsInUse = stdout.trim()
                         .split('\n')
                         .slice(2) // skip two header lines
                         .map(function(line) {
                           var addr = line.split(/[ \t]+/g)[3];
                           return parseInt(
                            addr.substr(addr.lastIndexOf(':') + 1),
                            10
                           );
                         });
  for (var port = 1025; port < 65535; ++port) {
    if (portsInUse.indexOf(port)) {
      SSHD_PORT = port;
      // start tests
      return next();
    }
  }
  assert(false, 'Unable to find a free port for starting sshd');
});

// check for forked process
if (process.argv.length > 2) {
  forkedTest = parseInt(process.argv[2], 10);
  if (!isNaN(forkedTest))
    t = forkedTest - 1;
  else
    process.exit(100);
}
  <|endoftext|>


<commit_before>import {select} from "d3-selection";
import {transition} from "d3-transition";

import {default as attrize} from "./attrize";

/**
    @function elem
    @desc Manages the enter/update/exit pattern for a single DOM element.
    @param {String} selector A D3 selector, which must include the tagname and a class and/or ID.
    @param {Object} params Additional parameters.
    @param {Boolean} [params.condition = true] Whether or not the element should be rendered (or removed).
    @param {Object} [params.enter = {}] A collection of key/value pairs that map to attributes to be given on enter.
    @param {Object} [params.exit = {}] A collection of key/value pairs that map to attributes to be given on exit.
    @param {D3Selection} [params.parent = d3.select("body")] The parent element for this new element to be appended to.
    @param {Number} [params.duration = 0] The duration for the d3 transition.
    @param {Object} [params.update = {}] A collection of key/value pairs that map to attributes to be given on update.
*/
export default function(selector, p) {

  // overrides default params
  p = Object.assign({}, {
    condition: true,
    enter: {},
    exit: {},
    duration: 0,
    parent: select("body"),
    update: {}
  }, p);

  const className = (/\.([^#]+)/g).exec(selector),
        id = (/#([^\.]+)/g).exec(selector),
        tag = (/^([^.^#]+)/g).exec(selector)[1],
        t = transition().duration(p.duration);

  const elem = p.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector)
    .data(p.condition ? [null] : []);

  const enter = elem.enter().append(tag).call(attrize, p.enter);

  if (id) enter.attr("id", id[1]);
  if (className) enter.attr("class", className[1]);

  elem.exit().transition(t).call(attrize, p.exit).remove();

  const update = enter.merge(elem);
  update.transition(t).call(attrize, p.update);

  return update;

}
<commit_msg>fixes linting error<commit_after>import {select} from "d3-selection";
import {transition} from "d3-transition";

import {default as attrize} from "./attrize";

/**
    @function elem
    @desc Manages the enter/update/exit pattern for a single DOM element.
    @param {String} selector A D3 selector, which must include the tagname and a class and/or ID.
    @param {Object} params Additional parameters.
    @param {Boolean} [params.condition = true] Whether or not the element should be rendered (or removed).
    @param {Object} [params.enter = {}] A collection of key/value pairs that map to attributes to be given on enter.
    @param {Object} [params.exit = {}] A collection of key/value pairs that map to attributes to be given on exit.
    @param {D3Selection} [params.parent = d3.select("body")] The parent element for this new element to be appended to.
    @param {Number} [params.duration = 0] The duration for the d3 transition.
    @param {Object} [params.update = {}] A collection of key/value pairs that map to attributes to be given on update.
*/
export default function(selector, p) {

  // overrides default params
  p = Object.assign({}, {
    condition: true,
    enter: {},
    exit: {},
    duration: 0,
    parent: select("body"),
    update: {}
  }, p);

  const className = (/\.([^#]+)/g).exec(selector),
        id = (/#([^\.]+)/g).exec(selector),
        t = transition().duration(p.duration),
        tag = (/^([^.^#]+)/g).exec(selector)[1];

  const elem = p.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector)
    .data(p.condition ? [null] : []);

  const enter = elem.enter().append(tag).call(attrize, p.enter);

  if (id) enter.attr("id", id[1]);
  if (className) enter.attr("class", className[1]);

  elem.exit().transition(t).call(attrize, p.exit).remove();

  const update = enter.merge(elem);
  update.transition(t).call(attrize, p.update);

  return update;

}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/kulikPisecny/01.jpg'),
  require('../../images/animals/kulikPisecny/02.jpg'),
  require('../../images/animals/kulikPisecny/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/kulikPisecny/01-thumb.jpg'),
  require('../../images/animals/kulikPisecny/02-thumb.jpg'),
  require('../../images/animals/kulikPisecny/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Kulík písečný (pro latiníky a milovníky lehkých jazykolamů <Text style={styles.italic}>Charadrius hiaticula</Text>) je pták dorůstající zhruba 20 centimetrů. Rozpětím křídel svou velikost efektivně dvojnásobí, neboť se tak blíží ke 40 centimetrům. K ještě větším číslům dosahuje hmotností, jež se pohybuje v intervalu 50–70 gramů. Vzhledem připomíná kulík kuličku, je tedy postavy spíše zavalité. Na zádech a temeni jsou kulíci zbarveni hnědošedě, zatímco bříško mají bílé. Bílý je i límec, který kulíkům zdobí krk.
        </AnimalText>
        <AnimalText>
          Kulíci jsou tažní ptáci. Obvykle hnízdí na severních pobřežích Evropy a Asie, objevují se také v Grónsku a v Kanadě. Na přelomu srpna a září migrují na jih, někteří mohou doletět až na jih Afriky. Občas táhnou kulíci i přes Českou republiku, někdy tu i zahnízdí. Jejich obvyklé cesty ale vedou jinudy.
        </AnimalText>
        <InPageImage indexes={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Jídelníček průměrného kulíka tvoří hmyz, korýši a červi. Potravu loví na březích všemožných vod z písku a bahna. Podle toho se kulík <Text style={styles.italic}>písečný</Text> řadí mezi <Text style={styles.italic}>bahňáky</Text>, což je různorodá skupina ptáků, jejichž společným poznávacím znakem je vazba na vodu.
        </AnimalText>
        <InPageImage indexes={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Hnízda si kulíci staví na písečném nebo bahenním podkladě. Snáší 3–5 vajec, ze kterých se vylíhnou mláďata po přibližně pětadvaceti dnech. Pokud je hnízdo s mláďaty (v libovolné fázi vývoje) ohroženo, vyběhne jeden z rodičů co nejdál od hnízda a zatímco vyluzuje hlasité zvuky, simuluje, že nemůže létat. Když predátor nemá oči pro nic jiného než rádoby-letu-neschopného kulíka (a zároveň je dostatečně daleko od hnízda), kulík uletí. A když nebezpečí pomine, vrátí se k hnízdu, kde by se, kdyby nebyl ptákem, mohl smát pod vousy.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<commit_msg>Update kulikPisecny.adult.js<commit_after>import React, { Component } from 'react';
import { Text } from 'react-native';

import styles from '../../styles/styles';
import InPageImage from '../inPageImage';
import AnimalText from '../animalText';
import AnimalTemplate from '../animalTemplate';

const IMAGES = [
  require('../../images/animals/kulikPisecny/01.jpg'),
  require('../../images/animals/kulikPisecny/02.jpg'),
  require('../../images/animals/kulikPisecny/03.jpg'),
];

const THUMBNAILS = [
  require('../../images/animals/kulikPisecny/01-thumb.jpg'),
  require('../../images/animals/kulikPisecny/02-thumb.jpg'),
  require('../../images/animals/kulikPisecny/03-thumb.jpg'),
];

var AnimalDetail = React.createClass({
  render() {
    return (
      <AnimalTemplate firstIndex={[2]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator}>
        <AnimalText>
          Kulík písečný (pro latiníky a milovníky lehkých jazykolamů <Text style={styles.italic}>Charadrius hiaticula</Text>) je pták dorůstající zhruba 20 centimetrů. Rozpětím křídel svou velikost efektivně dvojnásobí, neboť se tak blíží ke 40 centimetrům. K ještě větším číslům dosahuje hmotností, jež se pohybuje v intervalu 50–70 gramů. Vzhledem připomíná kulík kuličku, je tedy postavy spíše zavalité. Na zádech a temeni jsou kulíci zbarveni hnědošedě, zatímco bříško mají bílé. Bílý je i límec, který kulíkům zdobí krk.
        </AnimalText>
        <AnimalText>
          Kulíci jsou tažní ptáci. Obvykle hnízdí na severních pobřežích Evropy a Asie, objevují se také v Grónsku a v Kanadě. Na přelomu srpna a září migrují na jih, někteří mohou doletět až na jih Afriky. Občas táhnou tito kulíci i přes Českou republiku, někdy tu i zahnízdí. Jejich obvyklé cesty ale vedou jinudy.
        </AnimalText>
        <InPageImage indexes={[0]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Jídelníček průměrného kulíka tvoří hmyz, korýši a červi. Potravu loví na březích všemožných vod z písku a bahna. Podle toho se kulík <Text style={styles.italic}>písečný</Text> řadí mezi <Text style={styles.italic}>bahňáky</Text>, což je různorodá skupina ptáků, jejichž společným poznávacím znakem je vazba na vodu.
        </AnimalText>
        <InPageImage indexes={[1]} thumbnails={THUMBNAILS} images={IMAGES} navigator={this.props.navigator} />
        <AnimalText>
          Hnízda si kulíci staví na písečném nebo bahenním podkladě. Snáší 3–5 vajec, ze kterých se vylíhnou mláďata po přibližně pětadvaceti dnech. Pokud je hnízdo s mláďaty (v libovolné fázi vývoje) ohroženo, vyběhne jeden z rodičů co nejdál od hnízda a zatímco vyluzuje hlasité zvuky, simuluje, že nemůže létat. Když predátor nemá oči pro nic jiného než rádoby-letu-neschopného kulíka (a zároveň je dostatečně daleko od hnízda), kulík uletí. A když nebezpečí pomine, vrátí se k hnízdu, kde by se, kdyby nebyl ptákem, mohl smát pod vousy.
        </AnimalText>
      </AnimalTemplate>
    );
  }
});

module.exports = AnimalDetail;
<|endoftext|>


<commit_before>/*
 * lib/http-cat-stream.js: concatenate objects stored on a remote HTTP server
 */

var mod_assert = require('assert');
var mod_stream = require('stream');
var mod_util = require('util');

var mod_restify = require('restify');

/* Public interface */
exports.ObjectCache = ObjectCache;
exports.HttpCatStream = HttpCatStream;

/*
 * Generic object cache: this object provides get(key), which instantiates a new
 * object using "constructor" and caches it under the specified key.  Subsequent
 * requests for the same key return the same object unless it's been removed
 * with "remove".  This is used to implement an HTTP client cache indexed by
 * host.
 */
function ObjectCache(constructor, log)
{
	this.oc_cons = constructor;
	this.oc_cache = {};
	this.os_log = log;
}

ObjectCache.prototype.get = function (key)
{
	if (!this.oc_cache.hasOwnProperty(key)) {
		this.os_log.trace('creating new object for key "%s"', key);
		this.oc_cache[key] = this.oc_cons(key);
	}

	return (this.oc_cache[key]);
};

ObjectCache.prototype.remove = function (key, obj)
{
	if (arguments.length < 2 || this.oc_cache[key] == obj) {
		this.os_log.trace('removing object for key "%s"', key);
		delete (this.oc_cache[key]);
	}
};

/*
 * Custom stream object to concatenate the contents of multiple remote HTTP
 * resources.  Callers call write(params), where "params" is an object
 * with these properties:
 *
 *    url	restify-style URL for the remote server (NOT the remote path)
 *		e.g., "http://www.joyent.com:8080"
 *
 *    uri	path on the remote server to request, using GET
 *		e.g., "/favicon.png"
 *
 * This stream fetches these resources in order and emits each resource's
 * contents.  When each one completes, the next one is fetched, without emitting
 * an "end" event until the last resource has been fetched.
 */
function HttpCatStream(options)
{
	mod_assert.ok(options.hasOwnProperty('clients'),
	    'client cache is required');
	mod_assert.ok(options.hasOwnProperty('log', 'log is required'));

	this.writable = true;
	this.readable = true;
	this.hcs_paused = false;
	this.hcs_ended = false;
	this.hcs_endemitted = false;
	this.hcs_log = options.log;
	this.hcs_cache = options.clients;  /* restify clients ObjectCache */
	this.hcs_urls = [];		   /* queue of resources to fetch */
	this.hcs_ndone = 0;		   /* number of completed resources */

	/*
	 * The following fields maintain state for whatever remote resource
	 * we're processing:
	 *
	 *    hcs_current		current resource, as passed to write()
	 *    hcs_request		ClientRequest object
	 *    hcs_response		ClientResponse object
	 *
	 * These are assigned in order as we process a resource:
	 *
	 *    STATE			INTERNAL STATE
	 *    initial state		all fields undefined
	 *    allocating socket		hcs_current assigned, others undefined
	 *    request sent		hcs_current, hcs_request assigned,
	 *				hcs_response undefined
	 *    response headers recvd	all fields assigned; data is streaming.
	 *
	 * Once the response is fully received, we go back to the initial state
	 * and process the next resource.  See kick() for details.
	 */
	this.hcs_current = undefined;
	this.hcs_request = undefined;
	this.hcs_response = undefined;
}

mod_util.inherits(HttpCatStream, mod_stream.Stream);

HttpCatStream.prototype.pause = function ()
{
	this.hcs_paused = true;

	if (this.hcs_response)
		this.hcs_response.pause();
};

HttpCatStream.prototype.resume = function ()
{
	this.hcs_paused = false;

	if (this.hcs_response) {
		this.hcs_response.resume();
	} else if (this.hcs_ended && !this.hcs_endemitted &&
	    this.hcs_urls.length === 0 && this.hcs_current === undefined) {
		this.hcs_endemitted = true;
		this.emit('end');
	}
};

HttpCatStream.prototype.write = function (rqinfo)
{
	if (!this.writable)
		throw (new Error('stream is not writable'));

	this.hcs_urls.push(rqinfo);
	this.hcs_log.trace('enqueuing resource: %j', rqinfo);
	this.kick();
};

HttpCatStream.prototype.kick = function ()
{
	if (this.hcs_current !== undefined)
		return;

	if (this.hcs_urls.length === 0) {
		this.emit('drain');

		if (this.hcs_ended && !this.hcs_endemitted) {
			this.readable = false;
			this.hcs_endemitted = true;
			this.emit('end');
		}

		return;
	}

	var stream = this;
	var next = this.hcs_urls.shift();
	var client = this.hcs_cache.get(next['url']);

	stream.hcs_current = next;
	this.hcs_log.debug('fetching %j', next);

	/*
	 * We treat any errors we see as fatal to this stream.  Connection
	 * errors are retried by the underlying client object, so we won't see
	 * them here.  Other errors occur after we've already emitted some data
	 * as part of a given request, in which case there's not much we can do
	 * short of retrying the request, hoping the data hasn't changed, and
	 * skipping all the data we already emitted.
	 *
	 * If the client itself emits an error, we remove it from the cache so
	 * someone else doesn't try to use it again.  We remove the listener
	 * that does this as soon as we get the request callback because errors
	 * intended for us after that will not be emitted on the client object
	 * (but errors from other consumers of the same client may be).
	 */
	var clientError = function (err) {
		stream.hcs_log.error(err, 'client error');
		stream.hcs_cache.remove(next['url'], client);
		stream.emit('error', err);
		stream.destroy();
	};

	client.on('error', clientError);

	client.get(next['uri'], function (err, request) {
		client.removeListener('error', clientError);

		if (err) {
			stream.hcs_log.error(err, 'client GET error');
			stream.emit('error', err);
			stream.destroy();
			return;
		}

		stream.hcs_request = request;

		request.on('error', function (err2) {
			stream.hcs_log.error(err2, 'request error');
			stream.emit('error', err2);
			stream.destroy();
			return;
		});

		request.on('result', function (err2, response) {
			if (err2) {
				stream.hcs_log.error(err2, 'request error');
				stream.emit('error', err2);
				stream.destroy();
				return;
			}

			stream.hcs_response = response;
			stream.hcs_log.debug('received response: %d',
			    response.statusCode);

			if (stream.hcs_paused)
				response.pause();

			stream.hcs_response = response;
			stream.hcs_bytes_read = 0;

			response.on('data', function (chunk) {
				stream.hcs_log.trace('received %d bytes',
				    chunk.length);
				stream.hcs_bytes_read += chunk.length;
				stream.emit('data', chunk);
			});

			response.on('end', function () {
				stream.hcs_log.debug('finished processing %j,' +
						     ' read %d bytes',
						     stream.hcs_current,
						     stream.hcs_bytes_read);
				stream.hcs_current = undefined;
				stream.hcs_request = undefined;
				stream.hcs_response = undefined;
				stream.hcs_bytes_read = 0;
				stream.hcs_ndone++;
				stream.kick();
			});
		});
	});
};

HttpCatStream.prototype.end = function (rqinfo)
{
	if (this.hcs_ended)
		throw (new Error('"end" already invoked'));

	if (arguments.length > 0)
		this.write(rqinfo);

	this.writable = false;
	this.hcs_ended = true;

	this.hcs_log.trace('end invoked');

	if (this.hcs_current === undefined && this.hcs_urls.length === 0) {
		this.readable = false;

		if (!this.hcs_paused && !this.hcs_endemitted)
			this.emit('end');
	}
};

HttpCatStream.prototype.destroy = function ()
{
	this.writable = false;

	if (this.hcs_request)
		this.hcs_request.destroy();

	if (this.hcs_response)
		this.hcs_response.destroy();
};

HttpCatStream.prototype.destroySoon = function ()
{
	this.end();
};
<commit_msg>MANTA-1006 Marlin Lackey should resume downloads when manta connection is disrupted.<commit_after>/*
 * lib/http-cat-stream.js: concatenate objects stored on a remote HTTP server
 */

var mod_assert = require('assert');
var mod_crypto = require('crypto');
var mod_stream = require('stream');
var mod_util = require('util');

var mod_restify = require('restify');

/* Public interface */
exports.ObjectCache = ObjectCache;
exports.HttpCatStream = HttpCatStream;

/*
 * Generic object cache: this object provides get(key), which instantiates a new
 * object using "constructor" and caches it under the specified key.  Subsequent
 * requests for the same key return the same object unless it's been removed
 * with "remove".  This is used to implement an HTTP client cache indexed by
 * host.
 */
function ObjectCache(constructor, log)
{
	this.oc_cons = constructor;
	this.oc_cache = {};
	this.os_log = log;
}

ObjectCache.prototype.get = function (key)
{
	if (!this.oc_cache.hasOwnProperty(key)) {
		this.os_log.trace('creating new object for key "%s"', key);
		this.oc_cache[key] = this.oc_cons(key);
	}

	return (this.oc_cache[key]);
};

ObjectCache.prototype.remove = function (key, obj)
{
	if (arguments.length < 2 || this.oc_cache[key] == obj) {
		this.os_log.trace('removing object for key "%s"', key);
		delete (this.oc_cache[key]);
	}
};

/*
 * Custom stream object to concatenate the contents of multiple remote HTTP
 * resources.  Callers call write(params), where "params" is an object
 * with these properties:
 *
 *    url	restify-style URL for the remote server (NOT the remote path)
 *		e.g., "http://www.joyent.com:8080"
 *
 *    uri	path on the remote server to request, using GET
 *		e.g., "/favicon.png"
 *
 * This stream fetches these resources in order and emits each resource's
 * contents.  When each one completes, the next one is fetched, without emitting
 * an "end" event until the last resource has been fetched.
 */
function HttpCatStream(options)
{
	mod_assert.ok(options.hasOwnProperty('clients'),
	    'client cache is required');
	mod_assert.ok(options.hasOwnProperty('log', 'log is required'));

	this.writable = true;
	this.readable = true;
	this.hcs_paused = false;
	this.hcs_ended = false;
	this.hcs_endemitted = false;
	this.hcs_log = options.log;
	this.hcs_cache = options.clients;  /* restify clients ObjectCache */
	this.hcs_urls = [];		   /* queue of resources to fetch */
	this.hcs_ndone = 0;		   /* number of completed resources */

	/*
	 * The following fields maintain state for whatever remote resource
	 * we're processing:
	 *
	 *    hcs_current		current resource, as passed to write()
	 *    hcs_request		ClientRequest object
	 *    hcs_response		ClientResponse object
	 *
	 * These are assigned in order as we process a resource:
	 *
	 *    STATE			INTERNAL STATE
	 *    initial state		all fields undefined
	 *    allocating socket		hcs_current assigned, others undefined
	 *    request sent		hcs_current, hcs_request assigned,
	 *				hcs_response undefined
	 *    response headers recvd	all fields assigned; data is streaming.
	 *
	 * Once the response is fully received, we go back to the initial state
	 * and process the next resource.  See kick() and resumeGet() for
	 * details.
	 */
	this.hcs_current = undefined;
	this.hcs_request = undefined;
	this.hcs_response = undefined;
	this.hcs_bytes_read = undefined;
	this.hcs_resume_count = undefined;
	this.hcs_exp_md5 = undefined;
	this.hcs_exp_len = undefined;
	this.hcs_md5 = undefined;
	this.hcs_etag = undefined;
}

mod_util.inherits(HttpCatStream, mod_stream.Stream);

HttpCatStream.prototype.pause = function ()
{
	this.hcs_paused = true;

	if (this.hcs_response)
		this.hcs_response.pause();
};

HttpCatStream.prototype.resume = function ()
{
	this.hcs_paused = false;

	if (this.hcs_response) {
		this.hcs_response.resume();
	} else if (this.hcs_ended && !this.hcs_endemitted &&
	    this.hcs_urls.length === 0 && this.hcs_current === undefined) {
		this.hcs_endemitted = true;
		this.emit('end');
	}
};

HttpCatStream.prototype.write = function (rqinfo)
{
	if (!this.writable)
		throw (new Error('stream is not writable'));

	this.hcs_urls.push(rqinfo);
	this.hcs_log.trace('enqueuing resource: %j', rqinfo);
	this.kick();
};

HttpCatStream.prototype.resumeGet = function (cb)
{
	if (this.hcs_current === undefined) {
		cb();
		return;
	}

	var stream = this;
	var url = stream.hcs_current['url'];
	var uri = stream.hcs_current['uri'];
	var client = stream.hcs_cache.get(url);
	var startRange = stream.hcs_bytes_read;

	/*
	 * We treat any errors we see as fatal to this stream.  Connection
	 * errors are retried by the underlying client object, so we won't see
	 * them here.  Other errors occur after we've already emitted some data
	 * as part of a given request, in which case we attempt to resume the
	 * download.
	 *
	 * If the client itself emits an error, we remove it from the cache so
	 * someone else doesn't try to use it again.  We remove the listener
	 * that does this as soon as we get the request callback because errors
	 * intended for us after that will not be emitted on the client object
	 * (but errors from other consumers of the same client may be).
	 */
	var clientError = function (err) {
		stream.hcs_log.error(err, 'client error');
		stream.hcs_cache.remove(url, client);
		cb(err);
	};

	client.on('error', clientError);

	var getOpts = {
		path: uri,
		headers: {
			'range': 'bytes=' + startRange + '-'
		}
	};

	client.get(getOpts, function (err, request) {
		client.removeListener('error', clientError);

		if (err) {
			stream.hcs_log.error(err, 'client GET error');
			cb(err);
			return;
		}

		stream.hcs_request = request;

		request.on('error', function (err2) {
			stream.hcs_log.error(err2, 'request error');
			cb(err);
			return;
		});

		request.on('result', function (err2, response) {
			if (err2) {
				stream.hcs_log.error(err2, 'request error');
				cb(err);
				return;
			}

			// The indication that this can be treated as the
			// start rather than a resume.
			if (stream.hcs_bytes_read === 0) {
				stream.hcs_response = response;
				stream.hcs_resume_count = 0;
				var headers = response.headers;
				stream.hcs_exp_md5 = headers['content-md5'];
				stream.hcs_exp_len = headers['content-length'];
				stream.hcs_md5 = mod_crypto.createHash('md5');
				stream.hcs_etag = response.headers['etag'];
			} else {
				var new_etag = response.headers['etag'];
				if (stream.hcs_etag !== new_etag) {
					var m = 'Get Resume, etag has ' +
						'changed.  Must abort request.';
					stream.hcs_log.error({
						uri: uri,
						prevEtag: stream.hcs_etag,
						newEtag: new_etag
					}, m);
					cb(new Error(m));
				}
				stream.hcs_response = response;
			}

			stream.hcs_log.debug('received response: %d',
			    response.statusCode);

			if (stream.hcs_paused)
				response.pause();

			response.on('data', function (chunk) {
				stream.hcs_log.trace('received %d bytes',
				    chunk.length);
				stream.hcs_bytes_read += chunk.length;
				stream.hcs_md5.update(chunk);
				stream.emit('data', chunk);
			});

			response.on('end', function () {
				stream.hcs_log.debug('got end event for %j,' +
						     ' read %d bytes',
						     stream.hcs_current,
						     stream.hcs_bytes_read);

				// MANTA-1006
				var exp_len = stream.hcs_exp_len;
				var cur_len = stream.hcs_bytes_read;
				var res_count = stream.hcs_resume_count;
				if (exp_len < cur_len) {
					stream.hcs_log.debug({
						uri: uri,
						resumeCount: res_count,
						bytesRead: cur_len,
						expectedBytes: exp_len
					}, 'resuming download');
					++stream.hcs_resume_count;
					stream.resumeGet(cb);
					return;
				}

				var md5 = stream.hcs_exp_md5;
				var cmd5 = stream.hcs_md5.digest('base64');
				if (md5 && md5 !== cmd5) {
					stream.hcs_log.error({
						uri: uri,
						expectedMd5: md5,
						calculatedMd5: cmd5,
						bytesRead: cur_len,
						expectedBytes: exp_len,
						etag: stream.hcs_etag
					}, 'md5 mismatch');
					cb(new Error('md5 mismatch'));
					return;
				}

				stream.hcs_request = undefined;
				stream.hcs_response = undefined;
				stream.hcs_resume_count = undefined;
				stream.hcs_exp_md5 = undefined;
				stream.hcs_exp_len = undefined;
				stream.hcs_md5 = undefined;
				stream.hcs_etag = undefined;
				cb();
			});
		});
	});
};

HttpCatStream.prototype.kick = function ()
{
	if (this.hcs_current !== undefined)
		return;

	if (this.hcs_urls.length === 0) {
		this.emit('drain');

		if (this.hcs_ended && !this.hcs_endemitted) {
			this.readable = false;
			this.hcs_endemitted = true;
			this.emit('end');
		}

		return;
	}

	var stream = this;
	var next = this.hcs_urls.shift();

	stream.hcs_current = next;
	stream.hcs_bytes_read = 0;
	this.hcs_log.debug('fetching %j', next);

	stream.resumeGet(function (err) {
		if (err) {
			stream.emit('error', err);
			stream.destroy();
			return;
		}

		stream.hcs_current = undefined;
		stream.hcs_bytes_read = undefined;
		stream.hcs_ndone++;
		stream.kick();
	});
};

HttpCatStream.prototype.end = function (rqinfo)
{
	if (this.hcs_ended)
		throw (new Error('"end" already invoked'));

	if (arguments.length > 0)
		this.write(rqinfo);

	this.writable = false;
	this.hcs_ended = true;

	this.hcs_log.trace('end invoked');

	if (this.hcs_current === undefined && this.hcs_urls.length === 0) {
		this.readable = false;

		if (!this.hcs_paused && !this.hcs_endemitted)
			this.emit('end');
	}
};

HttpCatStream.prototype.destroy = function ()
{
	this.writable = false;

	if (this.hcs_request)
		this.hcs_request.destroy();

	if (this.hcs_response)
		this.hcs_response.destroy();
};

HttpCatStream.prototype.destroySoon = function ()
{
	this.end();
};
<|endoftext|>