<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview
 * Class that wraps low-level details of interacting with the client plugin.
 *
 * This abstracts a <embed> element and controls the plugin which does
 * the actual remoting work. It also handles differences between
 * client plugins versions when it is necessary.
 */

'use strict';

/** @suppress {duplicate} */
var remoting = remoting || {};

/**
 * @param {remoting.ViewerPlugin} plugin The plugin embed element.
 * @constructor
 * @implements {remoting.ClientPlugin}
 */
remoting.ClientPluginAsync = function(plugin) {
  this.plugin = plugin;

  this.desktopWidth = 0;
  this.desktopHeight = 0;

  /** @param {string} iq The Iq stanza received from the host. */
  this.onOutgoingIqHandler = function (iq) {};
  /** @param {string} message Log message. */
  this.onDebugMessageHandler = function (message) {};
  /**
   * @param {number} state The connection state.
   * @param {number} error The error code, if any.
   */
  this.onConnectionStatusUpdateHandler = function(state, error) {};
  this.onDesktopSizeUpdateHandler = function () {};

  /** @type {number} */
  this.pluginApiVersion_ = -1;
  /** @type {Array.<string>} */
  this.pluginApiFeatures_ = [];
  /** @type {number} */
  this.pluginApiMinVersion_ = -1;
  /** @type {boolean} */
  this.helloReceived_ = false;
  /** @type {function(boolean)|null} */
  this.onInitializedCallback_ = null;

  /** @type {remoting.ClientSession.PerfStats} */
  this.perfStats_ = new remoting.ClientSession.PerfStats();

  /** @type {remoting.ClientPluginAsync} */
  var that = this;
  /** @param {Event} event Message event from the plugin. */
  this.plugin.addEventListener('message', function(event) {
      that.handleMessage_(event.data);
    }, false);
};

/**
 * Chromoting session API version (for this javascript).
 * This is compared with the plugin API version to verify that they are
 * compatible.
 *
 * @const
 * @private
 */
remoting.ClientPluginAsync.prototype.API_VERSION_ = 6;

/**
 * The oldest API version that we support.
 * This will differ from the |API_VERSION_| if we maintain backward
 * compatibility with older API versions.
 *
 * @const
 * @private
 */
remoting.ClientPluginAsync.prototype.API_MIN_VERSION_ = 5;

/**
 * @param {string} message_str Message from the plugin.
 */
remoting.ClientPluginAsync.prototype.handleMessage_ = function(message_str) {
  var message = /** @type {{method:string, data:Object.<string,string>}} */
      JSON.parse(message_str);

  if (!('method' in message) || !('data' in message)) {
    console.error('Received invalid message from the plugin: ' + message_str);
    return;
  }

  if (message.method == 'hello') {
    if (typeof message.data['apiVersion'] != 'number' ||
        typeof message.data['apiMinVersion'] != 'number') {
      console.error('Received invalid hello message: ' + message_str);
      return;
    }
    this.pluginApiVersion_ = /** @type {number} */ message.data['apiVersion'];
    if (this.pluginApiVersion_ >= 7) {
      if (typeof message.data['apiFeatures'] != 'string') {
        console.error('Received invalid hello message: ' + message_str);
        return;
      }
      this.pluginApiFeatures_ =
          /** @type {Array.<string>} */ message.data['apiFeatures'].split(' ');
    } else if (this.pluginApiVersion_ >= 6) {
      this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
    } else {
      this.pluginApiFeatures_ = ['highQualityScaling'];
    }
    this.pluginApiMinVersion_ =
        /** @type {number} */ message.data['apiMinVersion'];
    this.helloReceived_ = true;
    if (this.onInitializedCallback_ != null) {
      this.onInitializedCallback_(true);
      this.onInitializedCallback_ = null;
    }
  } else if (message.method == 'sendOutgoingIq') {
    if (typeof message.data['iq'] != 'string') {
      console.error('Received invalid sendOutgoingIq message: ' + message_str);
      return;
    }
    this.onOutgoingIqHandler(message.data['iq']);
  } else if (message.method == 'logDebugMessage') {
    if (typeof message.data['message'] != 'string') {
      console.error('Received invalid logDebugMessage message: ' + message_str);
      return;
    }
    this.onDebugMessageHandler(message.data['message']);
  } else if (message.method == 'onConnectionStatus') {
    if (typeof message.data['state'] != 'string' ||
        !(message.data['state'] in remoting.ClientSession.State) ||
        typeof message.data['error'] != 'string') {
      console.error('Received invalid onConnectionState message: ' +
                    message_str);
      return;
    }

    /** @type {remoting.ClientSession.State} */
    var state = remoting.ClientSession.State[message.data['state']];
    var error;
    if (message.data['error'] in remoting.ClientSession.ConnectionError) {
      error = /** @type {remoting.ClientSession.ConnectionError} */
          remoting.ClientSession.ConnectionError[message.data['error']];
    } else {
      error = remoting.ClientSession.ConnectionError.UNKNOWN;
    }

    this.onConnectionStatusUpdateHandler(state, error);
  } else if (message.method == 'onDesktopSize') {
    if (typeof message.data['width'] != 'number' ||
        typeof message.data['height'] != 'number') {
      console.error('Received invalid onDesktopSize message: ' + message_str);
      return;
    }
    this.desktopWidth = /** @type {number} */ message.data['width'];
    this.desktopHeight = /** @type {number} */ message.data['height'];
    this.onDesktopSizeUpdateHandler();
  } else if (message.method == 'onPerfStats') {
    if (typeof message.data['videoBandwidth'] != 'number' ||
        typeof message.data['videoFrameRate'] != 'number' ||
        typeof message.data['captureLatency'] != 'number' ||
        typeof message.data['encodeLatency'] != 'number' ||
        typeof message.data['decodeLatency'] != 'number' ||
        typeof message.data['renderLatency'] != 'number' ||
        typeof message.data['roundtripLatency'] != 'number') {
      console.error('Received incorrect onPerfStats message: ' + message_str);
      return;
    }
    this.perfStats_ =
        /** @type {remoting.ClientSession.PerfStats} */ message.data;
  } else if (message.method = 'injectClipboardItem') {
    if (typeof message.data['mimeType'] != 'string' ||
        typeof message.data['item'] != 'string') {
      console.error('Received incorrect injectClipboardItem message.');
      return;
    }
    if (remoting.clipboard) {
      remoting.clipboard.fromHost(message.data['mimeType'],
                                  message.data['item']);
    }
  }
}

/**
 * Deletes the plugin.
 */
remoting.ClientPluginAsync.prototype.cleanup = function() {
  this.plugin.parentNode.removeChild(this.plugin);
};

/**
 * @return {Element} HTML element that correspods to the plugin.
 */
remoting.ClientPluginAsync.prototype.element = function() {
  return this.plugin;
};

/**
 * @param {function(boolean): void} onDone
 */
remoting.ClientPluginAsync.prototype.initialize = function(onDone) {
  if (this.helloReceived_) {
    onDone(true);
  } else {
    this.onInitializedCallback_ = onDone;
  }
};

/**
 * @return {boolean} True if the plugin and web-app versions are compatible.
 */
remoting.ClientPluginAsync.prototype.isSupportedVersion = function() {
  if (!this.helloReceived_) {
    console.error(
        "isSupportedVersion() is called before the plugin is initialized.");
    return false;
  }
  return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
      this.pluginApiVersion_ >= this.API_MIN_VERSION_;
};

/**
 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
 * @return {boolean} True if the plugin supports the named feature.
 */
remoting.ClientPluginAsync.prototype.hasFeature = function(feature) {
  if (!this.helloReceived_) {
    console.error(
        "hasFeature() is called before the plugin is initialized.");
    return false;
  }
  return this.pluginApiFeatures_.indexOf(feature) > -1;
};

/**
 * @return {boolean} True if the plugin supports the injectKeyEvent API.
 */
remoting.ClientPluginAsync.prototype.isInjectKeyEventSupported = function() {
  return this.pluginApiVersion_ >= 6;
};

/**
 * @param {string} iq Incoming IQ stanza.
 */
remoting.ClientPluginAsync.prototype.onIncomingIq = function(iq) {
  if (this.plugin && this.plugin.postMessage) {
    this.plugin.postMessage(JSON.stringify(
        { method: 'incomingIq', data: { iq: iq } }));
  } else {
    // plugin.onIq may not be set after the plugin has been shut
    // down. Particularly this happens when we receive response to
    // session-terminate stanza.
    console.warn('plugin.onIq is not set so dropping incoming message.');
  }
};

/**
 * @param {string} hostJid The jid of the host to connect to.
 * @param {string} hostPublicKey The base64 encoded version of the host's
 *     public key.
 * @param {string} localJid Local jid.
 * @param {string} sharedSecret The access code for IT2Me or the PIN
 *     for Me2Me.
 * @param {string} authenticationMethods Comma-separated list of
 *     authentication methods the client should attempt to use.
 * @param {string} authenticationTag A host-specific tag to mix into
 *     authentication hashes.
 */
remoting.ClientPluginAsync.prototype.connect = function(
    hostJid, hostPublicKey, localJid, sharedSecret,
    authenticationMethods, authenticationTag) {
  this.plugin.postMessage(JSON.stringify(
    { method: 'connect', data: {
        hostJid: hostJid,
        hostPublicKey: hostPublicKey,
        localJid: localJid,
        sharedSecret: sharedSecret,
        authenticationMethods: authenticationMethods,
        authenticationTag: authenticationTag
      }
    }));
};

/**
 * @param {boolean} scaleToFit True if scale-to-fit should be enabled.
 */
remoting.ClientPluginAsync.prototype.setScaleToFit = function(scaleToFit) {
  // scaleToFit() will be removed in future versions of the plugin.
  if (this.plugin && typeof this.plugin.setScaleToFit === 'function')
    this.plugin.setScaleToFit(scaleToFit);
};

/**
 * Release all currently pressed keys.
 */
remoting.ClientPluginAsync.prototype.releaseAllKeys = function() {
  this.plugin.postMessage(JSON.stringify(
      { method: 'releaseAllKeys', data: {} }));
};

/**
 * Send a key event to the host.
 *
 * @param {number} usbKeycode The USB-style code of the key to inject.
 * @param {boolean} pressed True to inject a key press, False for a release.
 */
remoting.ClientPluginAsync.prototype.injectKeyEvent =
    function(usbKeycode, pressed) {
  this.plugin.postMessage(JSON.stringify(
      { method: 'injectKeyEvent', data: {
          'usbKeycode': usbKeycode,
          'pressed': pressed}
      }));
};

/**
 * Remap one USB keycode to another in all subsequent key events.
 *
 * @param {number} fromKeycode The USB-style code of the key to remap.
 * @param {number} toKeycode The USB-style code to remap the key to.
 */
remoting.ClientPluginAsync.prototype.remapKey =
    function(fromKeycode, toKeycode) {
  this.plugin.postMessage(JSON.stringify(
      { method: 'remapKey', data: {
          'fromKeycode': fromKeycode,
          'toKeycode': toKeycode}
      }));
};

/**
 * Returns an associative array with a set of stats for this connecton.
 *
 * @return {remoting.ClientSession.PerfStats} The connection statistics.
 */
remoting.ClientPluginAsync.prototype.getPerfStats = function() {
  return this.perfStats_;
};

/**
 * Sends a clipboard item to the host.
 *
 * @param {string} mimeType The MIME type of the clipboard item.
 * @param {string} item The clipboard item.
 */
remoting.ClientPluginAsync.prototype.sendClipboardItem =
    function(mimeType, item) {
  if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
    return;
  this.plugin.postMessage(JSON.stringify(
      { method: 'sendClipboardItem',
        data: { mimeType: mimeType, item: item }}));
};
<commit_msg>Show client plugin if no hello is received in 0.5s.<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview
 * Class that wraps low-level details of interacting with the client plugin.
 *
 * This abstracts a <embed> element and controls the plugin which does
 * the actual remoting work. It also handles differences between
 * client plugins versions when it is necessary.
 */

'use strict';

/** @suppress {duplicate} */
var remoting = remoting || {};

/**
 * @param {remoting.ViewerPlugin} plugin The plugin embed element.
 * @constructor
 * @implements {remoting.ClientPlugin}
 */
remoting.ClientPluginAsync = function(plugin) {
  this.plugin = plugin;

  this.desktopWidth = 0;
  this.desktopHeight = 0;

  /** @param {string} iq The Iq stanza received from the host. */
  this.onOutgoingIqHandler = function (iq) {};
  /** @param {string} message Log message. */
  this.onDebugMessageHandler = function (message) {};
  /**
   * @param {number} state The connection state.
   * @param {number} error The error code, if any.
   */
  this.onConnectionStatusUpdateHandler = function(state, error) {};
  this.onDesktopSizeUpdateHandler = function () {};

  /** @type {number} */
  this.pluginApiVersion_ = -1;
  /** @type {Array.<string>} */
  this.pluginApiFeatures_ = [];
  /** @type {number} */
  this.pluginApiMinVersion_ = -1;
  /** @type {boolean} */
  this.helloReceived_ = false;
  /** @type {function(boolean)|null} */
  this.onInitializedCallback_ = null;

  /** @type {remoting.ClientSession.PerfStats} */
  this.perfStats_ = new remoting.ClientSession.PerfStats();

  /** @type {remoting.ClientPluginAsync} */
  var that = this;
  /** @param {Event} event Message event from the plugin. */
  this.plugin.addEventListener('message', function(event) {
      that.handleMessage_(event.data);
    }, false);
  var showPluginForClickToPlay = function() {
    that.showPluginForClickToPlay_();
  };
  window.setTimeout(showPluginForClickToPlay, 500);
};

/**
 * Chromoting session API version (for this javascript).
 * This is compared with the plugin API version to verify that they are
 * compatible.
 *
 * @const
 * @private
 */
remoting.ClientPluginAsync.prototype.API_VERSION_ = 6;

/**
 * The oldest API version that we support.
 * This will differ from the |API_VERSION_| if we maintain backward
 * compatibility with older API versions.
 *
 * @const
 * @private
 */
remoting.ClientPluginAsync.prototype.API_MIN_VERSION_ = 5;

/**
 * @param {string} message_str Message from the plugin.
 */
remoting.ClientPluginAsync.prototype.handleMessage_ = function(message_str) {
  var message = /** @type {{method:string, data:Object.<string,string>}} */
      JSON.parse(message_str);

  if (!('method' in message) || !('data' in message)) {
    console.error('Received invalid message from the plugin: ' + message_str);
    return;
  }

  if (message.method == 'hello') {
    // Reset the size in case we had to enlarge it to support click-to-play.
    this.plugin.width = 0;
    this.plugin.height = 0;
    if (typeof message.data['apiVersion'] != 'number' ||
        typeof message.data['apiMinVersion'] != 'number') {
      console.error('Received invalid hello message: ' + message_str);
      return;
    }
    this.pluginApiVersion_ = /** @type {number} */ message.data['apiVersion'];
    if (this.pluginApiVersion_ >= 7) {
      if (typeof message.data['apiFeatures'] != 'string') {
        console.error('Received invalid hello message: ' + message_str);
        return;
      }
      this.pluginApiFeatures_ =
          /** @type {Array.<string>} */ message.data['apiFeatures'].split(' ');
    } else if (this.pluginApiVersion_ >= 6) {
      this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
    } else {
      this.pluginApiFeatures_ = ['highQualityScaling'];
    }
    this.pluginApiMinVersion_ =
        /** @type {number} */ message.data['apiMinVersion'];
    this.helloReceived_ = true;
    if (this.onInitializedCallback_ != null) {
      this.onInitializedCallback_(true);
      this.onInitializedCallback_ = null;
    }
  } else if (message.method == 'sendOutgoingIq') {
    if (typeof message.data['iq'] != 'string') {
      console.error('Received invalid sendOutgoingIq message: ' + message_str);
      return;
    }
    this.onOutgoingIqHandler(message.data['iq']);
  } else if (message.method == 'logDebugMessage') {
    if (typeof message.data['message'] != 'string') {
      console.error('Received invalid logDebugMessage message: ' + message_str);
      return;
    }
    this.onDebugMessageHandler(message.data['message']);
  } else if (message.method == 'onConnectionStatus') {
    if (typeof message.data['state'] != 'string' ||
        !(message.data['state'] in remoting.ClientSession.State) ||
        typeof message.data['error'] != 'string') {
      console.error('Received invalid onConnectionState message: ' +
                    message_str);
      return;
    }

    /** @type {remoting.ClientSession.State} */
    var state = remoting.ClientSession.State[message.data['state']];
    var error;
    if (message.data['error'] in remoting.ClientSession.ConnectionError) {
      error = /** @type {remoting.ClientSession.ConnectionError} */
          remoting.ClientSession.ConnectionError[message.data['error']];
    } else {
      error = remoting.ClientSession.ConnectionError.UNKNOWN;
    }

    this.onConnectionStatusUpdateHandler(state, error);
  } else if (message.method == 'onDesktopSize') {
    if (typeof message.data['width'] != 'number' ||
        typeof message.data['height'] != 'number') {
      console.error('Received invalid onDesktopSize message: ' + message_str);
      return;
    }
    this.desktopWidth = /** @type {number} */ message.data['width'];
    this.desktopHeight = /** @type {number} */ message.data['height'];
    this.onDesktopSizeUpdateHandler();
  } else if (message.method == 'onPerfStats') {
    if (typeof message.data['videoBandwidth'] != 'number' ||
        typeof message.data['videoFrameRate'] != 'number' ||
        typeof message.data['captureLatency'] != 'number' ||
        typeof message.data['encodeLatency'] != 'number' ||
        typeof message.data['decodeLatency'] != 'number' ||
        typeof message.data['renderLatency'] != 'number' ||
        typeof message.data['roundtripLatency'] != 'number') {
      console.error('Received incorrect onPerfStats message: ' + message_str);
      return;
    }
    this.perfStats_ =
        /** @type {remoting.ClientSession.PerfStats} */ message.data;
  } else if (message.method = 'injectClipboardItem') {
    if (typeof message.data['mimeType'] != 'string' ||
        typeof message.data['item'] != 'string') {
      console.error('Received incorrect injectClipboardItem message.');
      return;
    }
    if (remoting.clipboard) {
      remoting.clipboard.fromHost(message.data['mimeType'],
                                  message.data['item']);
    }
  }
}

/**
 * Deletes the plugin.
 */
remoting.ClientPluginAsync.prototype.cleanup = function() {
  this.plugin.parentNode.removeChild(this.plugin);
};

/**
 * @return {Element} HTML element that correspods to the plugin.
 */
remoting.ClientPluginAsync.prototype.element = function() {
  return this.plugin;
};

/**
 * @param {function(boolean): void} onDone
 */
remoting.ClientPluginAsync.prototype.initialize = function(onDone) {
  if (this.helloReceived_) {
    onDone(true);
  } else {
    this.onInitializedCallback_ = onDone;
  }
};

/**
 * @return {boolean} True if the plugin and web-app versions are compatible.
 */
remoting.ClientPluginAsync.prototype.isSupportedVersion = function() {
  if (!this.helloReceived_) {
    console.error(
        "isSupportedVersion() is called before the plugin is initialized.");
    return false;
  }
  return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
      this.pluginApiVersion_ >= this.API_MIN_VERSION_;
};

/**
 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
 * @return {boolean} True if the plugin supports the named feature.
 */
remoting.ClientPluginAsync.prototype.hasFeature = function(feature) {
  if (!this.helloReceived_) {
    console.error(
        "hasFeature() is called before the plugin is initialized.");
    return false;
  }
  return this.pluginApiFeatures_.indexOf(feature) > -1;
};

/**
 * @return {boolean} True if the plugin supports the injectKeyEvent API.
 */
remoting.ClientPluginAsync.prototype.isInjectKeyEventSupported = function() {
  return this.pluginApiVersion_ >= 6;
};

/**
 * @param {string} iq Incoming IQ stanza.
 */
remoting.ClientPluginAsync.prototype.onIncomingIq = function(iq) {
  if (this.plugin && this.plugin.postMessage) {
    this.plugin.postMessage(JSON.stringify(
        { method: 'incomingIq', data: { iq: iq } }));
  } else {
    // plugin.onIq may not be set after the plugin has been shut
    // down. Particularly this happens when we receive response to
    // session-terminate stanza.
    console.warn('plugin.onIq is not set so dropping incoming message.');
  }
};

/**
 * @param {string} hostJid The jid of the host to connect to.
 * @param {string} hostPublicKey The base64 encoded version of the host's
 *     public key.
 * @param {string} localJid Local jid.
 * @param {string} sharedSecret The access code for IT2Me or the PIN
 *     for Me2Me.
 * @param {string} authenticationMethods Comma-separated list of
 *     authentication methods the client should attempt to use.
 * @param {string} authenticationTag A host-specific tag to mix into
 *     authentication hashes.
 */
remoting.ClientPluginAsync.prototype.connect = function(
    hostJid, hostPublicKey, localJid, sharedSecret,
    authenticationMethods, authenticationTag) {
  this.plugin.postMessage(JSON.stringify(
    { method: 'connect', data: {
        hostJid: hostJid,
        hostPublicKey: hostPublicKey,
        localJid: localJid,
        sharedSecret: sharedSecret,
        authenticationMethods: authenticationMethods,
        authenticationTag: authenticationTag
      }
    }));
};

/**
 * @param {boolean} scaleToFit True if scale-to-fit should be enabled.
 */
remoting.ClientPluginAsync.prototype.setScaleToFit = function(scaleToFit) {
  // scaleToFit() will be removed in future versions of the plugin.
  if (this.plugin && typeof this.plugin.setScaleToFit === 'function')
    this.plugin.setScaleToFit(scaleToFit);
};

/**
 * Release all currently pressed keys.
 */
remoting.ClientPluginAsync.prototype.releaseAllKeys = function() {
  this.plugin.postMessage(JSON.stringify(
      { method: 'releaseAllKeys', data: {} }));
};

/**
 * Send a key event to the host.
 *
 * @param {number} usbKeycode The USB-style code of the key to inject.
 * @param {boolean} pressed True to inject a key press, False for a release.
 */
remoting.ClientPluginAsync.prototype.injectKeyEvent =
    function(usbKeycode, pressed) {
  this.plugin.postMessage(JSON.stringify(
      { method: 'injectKeyEvent', data: {
          'usbKeycode': usbKeycode,
          'pressed': pressed}
      }));
};

/**
 * Remap one USB keycode to another in all subsequent key events.
 *
 * @param {number} fromKeycode The USB-style code of the key to remap.
 * @param {number} toKeycode The USB-style code to remap the key to.
 */
remoting.ClientPluginAsync.prototype.remapKey =
    function(fromKeycode, toKeycode) {
  this.plugin.postMessage(JSON.stringify(
      { method: 'remapKey', data: {
          'fromKeycode': fromKeycode,
          'toKeycode': toKeycode}
      }));
};

/**
 * Returns an associative array with a set of stats for this connecton.
 *
 * @return {remoting.ClientSession.PerfStats} The connection statistics.
 */
remoting.ClientPluginAsync.prototype.getPerfStats = function() {
  return this.perfStats_;
};

/**
 * Sends a clipboard item to the host.
 *
 * @param {string} mimeType The MIME type of the clipboard item.
 * @param {string} item The clipboard item.
 */
remoting.ClientPluginAsync.prototype.sendClipboardItem =
    function(mimeType, item) {
  if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
    return;
  this.plugin.postMessage(JSON.stringify(
      { method: 'sendClipboardItem',
        data: { mimeType: mimeType, item: item }}));
};

/**
 * If we haven't yet received a "hello" message from the plugin, change its
 * size so that the user can confirm it if click-to-play is enabled, or can
 * see the "this plugin is disabled" message if it is actually disabled.
 * @private
 */
remoting.ClientPluginAsync.prototype.showPluginForClickToPlay_ = function() {
  if (!this.helloReceived_) {
    var width = 200;
    var height = 200;
    this.plugin.width = width;
    this.plugin.height = height;
    // Center the plugin just underneath the "Connnecting..." dialog.
    var parentNode = this.plugin.parentNode;
    var dialog = document.getElementById('client-dialog');
    var dialogRect = dialog.getBoundingClientRect();
    parentNode.style.top = (dialogRect.bottom + 16) + 'px';
    parentNode.style.left = (window.innerWidth - width) / 2 + 'px';
  }
};
<|endoftext|>


<commit_before>/*
 * @package jsDAV
 * @subpackage DAV
 * @copyright Copyright (C) 2010 Mike de Boer. All rights reserved.
 * @author Mike de Boer <mike AT ajax DOT org>
 * @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License
 */
var jsDAV                   = require("./../../jsdav"),
    jsDAV_ServerPlugin      = require("./../plugin").jsDAV_ServerPlugin,
    jsDAV_Codesearch_Plugin = require("./codesearch"),

    Spawn = require("child_process").spawn,
    Exc   = require("./../exceptions"),
    Util  = require("./../util");

function jsDAV_Filelist_Plugin(handler) {
    this.handler = handler;
    this.initialize();
}

jsDAV_Filelist_Plugin.FIND_CMD = "find";

(function() {
    this.initialize = function() {
        this.handler.addEventListener("report", this.httpReportHandler.bind(this));
    };

    this.httpReportHandler = function(e, reportName, dom) {
        if (reportName != "{DAV:}filelist")
            return e.next();
        //e.stop();

        var uri     = this.handler.getRequestUri(),
            options = this.parseOptions(dom),
            _self   = this;
        options.uri = uri;
        this.handler.server.tree.getNodeForPath(uri, function(err, node) {
            if (err)
                return e.next(err);

            if (jsDAV.debugMode)
                console.log("report" + reportName + ", " + node.path + ", " + require("sys").inspect(options));

            _self.doFilelist(node, options, function(err, sResults) {
                if (!Util.empty(err))
                    return e.stop(err);
                _self.handler.httpResponse.writeHead(207, {"Content-Type":"text/xml; charset=utf-8"});
                _self.handler.httpResponse.end(sResults);
                e.stop();
            });
        });
    };

    this.parseOptions = function(dom) {
        var options = {};
        for (var child, i = 0, l = dom.childNodes.length; i < l; ++i) {
            child = dom.childNodes[i];
            if (!child || child.nodeType != 1)
                continue;
            options[child.tagName] = child.nodeValue;
        }
        return options;
    };

    this.doFilelist = function(node, options, cbsearch) {
        var args,
            _self = this;
        if (process.platform == "darwin") {
            // BSD find
	        args = ["-L", "-E", node.path, "-type", "f", "(", "-a", "!", "-regex",
                     ".*(" + jsDAV_Codesearch_Plugin.PATTERN_DIR + ").*", ")",
                     "-print"];
        }
        else {
        	// GNU find
            args = ["-L", node.path, "-type", "f",
                    "-a", "!", "-regex", ".*(" + jsDAV_Codesearch_Plugin.PATTERN_DIR + ").*",
                    "-regextype", "posix-extended", "-print"];
        }
        if (jsDAV.debugMode)
            console.log("search command: find " + args.join(" "));
        var err  = "",
            out  = "",
            find = Spawn(jsDAV_Filelist_Plugin.FIND_CMD, args);
        find.stdout.on("data", function(data) {
            if (!Util.empty(data))
                out += data;
        });
        find.stderr.on("data", function(data) {
            if (!Util.empty(data))
                err += data;
        });
        find.on("exit", function(code) {
            cbsearch(err, _self.parseSearchResult(out || "", node.path, options));
        });
    };

    this.parseSearchResult = function(res, basePath, options) {
        var namespace, prefix, lastFile, line,
            aXml   = ['<?xml version="1.0" encoding="utf-8"?><d:multistatus'],
            aLines = res.split("\n"),
            i      = 0,
            l      = aLines.length;
        // Adding in default namespaces
        for (namespace in this.handler.xmlNamespaces) {
            prefix = this.handler.xmlNamespaces[namespace];
            aXml.push(' xmlns:' + prefix + '="' + namespace + '"');
        }
        aXml.push('><d:response query="', Util.escapeXml(options.query, '"'), '">');
        for (; i < l; ++i) {
            line = Util.trim(aLines[i]);
            if (!line) continue;
            aXml.push('<d:href>' + encodeURI(options.uri
                + Util.rtrim(line.replace(basePath, "")), "/")
                + '</d:href>');
        }
        return aXml.join("") + '</d:response></d:multistatus>';
    };
}).call(jsDAV_Filelist_Plugin.prototype = new jsDAV_ServerPlugin());

module.exports = jsDAV_Filelist_Plugin;
<commit_msg>The find command now gets empty folders as well<commit_after>/*
 * @package jsDAV
 * @subpackage DAV
 * @copyright Copyright (C) 2010 Mike de Boer. All rights reserved.
 * @author Mike de Boer <mike AT ajax DOT org>
 * @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License
 */
var jsDAV                   = require("./../../jsdav"),
    jsDAV_ServerPlugin      = require("./../plugin").jsDAV_ServerPlugin,
    jsDAV_Codesearch_Plugin = require("./codesearch"),

    Spawn = require("child_process").spawn,
    Exc   = require("./../exceptions"),
    Util  = require("./../util");

function jsDAV_Filelist_Plugin(handler) {
    this.handler = handler;
    this.initialize();
}

jsDAV_Filelist_Plugin.FIND_CMD = "find";

(function() {
    this.initialize = function() {
        this.handler.addEventListener("report", this.httpReportHandler.bind(this));
    };

    this.httpReportHandler = function(e, reportName, dom) {
        if (reportName != "{DAV:}filelist")
            return e.next();
        //e.stop();

        var uri     = this.handler.getRequestUri(),
            options = this.parseOptions(dom),
            _self   = this;
        options.uri = uri;
        this.handler.server.tree.getNodeForPath(uri, function(err, node) {
            if (err)
                return e.next(err);

            if (jsDAV.debugMode)
                console.log("report" + reportName + ", " + node.path + ", " + require("sys").inspect(options));

            _self.doFilelist(node, options, function(err, sResults) {
                if (!Util.empty(err))
                    return e.stop(err);
                _self.handler.httpResponse.writeHead(207, {"Content-Type":"text/xml; charset=utf-8"});
                _self.handler.httpResponse.end(sResults);
                e.stop();
            });
        });
    };

    this.parseOptions = function(dom) {
        var options = {};
        for (var child, i = 0, l = dom.childNodes.length; i < l; ++i) {
            child = dom.childNodes[i];
            if (!child || child.nodeType != 1)
                continue;
            options[child.tagName] = child.nodeValue;
        }
        return options;
    };

    this.doFilelist = function(node, options, cbsearch) {
        var args,
            _self = this;
        if (process.platform == "darwin") {
            // BSD find
	        args = ["-L", "-E", node.path, "(", "-a", "!", "-regex",
                     ".*(" + jsDAV_Codesearch_Plugin.PATTERN_DIR + ").*", ")",
                     "-print"];
        }
        else {
        	// GNU find
            args = ["-L", node.path,
                    "-a", "!", "-regex", ".*(" + jsDAV_Codesearch_Plugin.PATTERN_DIR + ").*",
                    "-regextype", "posix-extended", "-print"];
        }
        if (jsDAV.debugMode)
            console.log("search command: find " + args.join(" "));
        var err  = "",
            out  = "",
            find = Spawn(jsDAV_Filelist_Plugin.FIND_CMD, args);
        find.stdout.on("data", function(data) {
            if (!Util.empty(data))
                out += data;
        });
        find.stderr.on("data", function(data) {
            if (!Util.empty(data))
                err += data;
        });
        find.on("exit", function(code) {
            cbsearch(err, _self.parseSearchResult(out || "", node.path, options));
        });
    };

    this.parseSearchResult = function(res, basePath, options) {
        var namespace, prefix, lastFile, line,
            aXml   = ['<?xml version="1.0" encoding="utf-8"?><d:multistatus'],
            aLines = res.split("\n"),
            i      = 0,
            l      = aLines.length;
        // Adding in default namespaces
        for (namespace in this.handler.xmlNamespaces) {
            prefix = this.handler.xmlNamespaces[namespace];
            aXml.push(' xmlns:' + prefix + '="' + namespace + '"');
        }
        aXml.push('><d:response query="', Util.escapeXml(options.query, '"'), '">');
        for (; i < l; ++i) {
            line = Util.trim(aLines[i]);
            if (!line) continue;
            aXml.push('<d:href>' + encodeURI(options.uri
                + Util.rtrim(line.replace(basePath, "")), "/")
                + '</d:href>');
        }
        return aXml.join("") + '</d:response></d:multistatus>';
    };
}).call(jsDAV_Filelist_Plugin.prototype = new jsDAV_ServerPlugin());

module.exports = jsDAV_Filelist_Plugin;
<|endoftext|>


<commit_before>"use strict"

module.exports = new function init() {
	var spec = {}, subjects = [], results = [], only = null, ctx = spec, start, stack = 0, hasProcess = typeof process === "object"

	function o(subject, predicate) {
		subject = unique(subject, predicate)
		ctx[subject] = predicate
		if (predicate === undefined) return new Assert(subject)
	}
	o.before = hook("__before")
	o.after = hook("__after")
	o.beforeEach = hook("__beforeEach")
	o.afterEach = hook("__afterEach")
	o.new = init
	o.spec = function(subject, predicate) {
		var parent = ctx
		subject = unique(subject, predicate)
		ctx = ctx[subject] = {}
		predicate()
		ctx = parent
	}
	o.only = function(subject, predicate) {o(subject, only = predicate)}
	o.spy = function(fn) {
		var spy = function() {
			spy.this = this
			spy.args = [].slice.call(arguments)
			spy.callCount++

			if (fn) return fn.apply(this, arguments)
		}
		if (fn)
			Object.defineProperties(spy, {
				length: {value: fn.length},
				name: {value: fn.name}
			})
		spy.args = []
		spy.callCount = 0
		return spy
	}
	o.run = function() {
		start = new Date
		test(spec, [], [], report)

		function test(spec, pre, post, finalize) {
			pre = [].concat(pre, spec["__beforeEach"] || [])
			post = [].concat(spec["__afterEach"] || [], post)
			series([].concat(spec["__before"] || [], Object.keys(spec).map(function(key) {
				return function(done, timeout) {
					timeout(Infinity)

					if (key.slice(0, 2) === "__") return done()
					if (only !== null && spec[key] !== only && typeof only === typeof spec[key]) return done()
					subjects.push(key)
					var type = typeof spec[key]
					if (type === "object") test(spec[key], pre, post, pop)
					if (type === "function") series([].concat(pre, spec[key], post, pop))

					function pop() {
						subjects.pop()
						done()
					}
				}
			}), spec["__after"] || [], finalize))
		}

		function series(fns) {
			var cursor = 0
			next()

			function next() {
				stack++
				if (cursor === fns.length) return

				var fn = fns[cursor++]
				if (fn.length > 0) {
					var timeout = 0, delay = 200, s = new Date
					var isDone = false
					var body = fn.toString()
					var arg = (body.match(/\(([\w_$]+)/) || body.match(/([\w_$]+)\s*=>/) || []).pop()
					if (body.indexOf(arg) === body.lastIndexOf(arg)) throw new Error("`" + arg + "()` should be called at least once")
					try {
						fn(function done() {
							if (timeout !== undefined) {
								timeout = clearTimeout(timeout)
								if (delay !== Infinity) record(null)
								if (!isDone) next()
								else throw new Error("`" + arg + "()` should only be called once")
								isDone = true
							}
							else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms")
						}, function(t) {delay = t})
					}
					catch (e) {
						record(e.message, e)
						subjects.pop()
						next()
					}
					if (timeout === 0) {
						timeout = setTimeout(function() {
							timeout = undefined
							record("async test timed out")
							next()
						}, Math.min(delay, 2147483647))
					}
				}
				else {
					fn()
					if (stack < 5000) next()
					else (hasProcess ? process.nextTick : setTimeout)(next, stack = 0)
				}
			}
		}
	}
	function unique(subject, predicate) {
		if (ctx.hasOwnProperty(subject) && predicate) {
			console.warn("A test named `" + subject + "` was already defined")
			subject = subject + "*"
		}
		return subject
	}
	function hook(name) {
		return function(predicate) {
			if (ctx[name]) throw new Error("This hook should be defined outside of a loop or inside a nested test group:\n" + predicate)
			ctx[name] = predicate
		}
	}

	define("equals", "should equal", function(a, b) {return a === b})
	define("notEquals", "should not equal", function(a, b) {return a !== b})
	define("deepEquals", "should deep equal", deepEqual)
	define("notDeepEquals", "should not deep equal", function(a, b) {return !deepEqual(a, b)})

	function isArguments(a) {
		if ("callee" in a) {
			for (var i in a) if (i === "callee") return false
			return true
		}
	}
	function deepEqual(a, b) {
		if (a === b) return true
		if (a === null ^ b === null || a === undefined ^ b === undefined) return false
		if (typeof a === "object" && typeof b === "object") {
			var aIsArgs = isArguments(a), bIsArgs = isArguments(b)
			if (a.constructor === Object && b.constructor === Object && !aIsArgs && !bIsArgs) {
				for (var i in a) {
					if ((!(i in b)) || !deepEqual(a[i], b[i])) return false
				}
				for (var i in b) {
					if (!(i in a)) return false
				}
				return true
			}
			if (a.length === b.length && (a instanceof Array && b instanceof Array || aIsArgs && bIsArgs)) {
				var aKeys = Object.getOwnPropertyNames(a), bKeys = Object.getOwnPropertyNames(b)
				if (aKeys.length !== bKeys.length) return false
				for (var i = 0; i < aKeys.length; i++) {
					if (!b.hasOwnProperty(aKeys[i]) || !deepEqual(a[aKeys[i]], b[aKeys[i]])) return false
				}
				return true
			}
			if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
			if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) {
				for (var i = 0; i < a.length; i++) {
					if (a[i] !== b[i]) return false
				}
				return true
			}
			if (a.valueOf() === b.valueOf()) return true
		}
		return false
	}

	function Assert(value) {this.value = value}
	function define(name, verb, compare) {
		Assert.prototype[name] = function assert(value) {
			if (compare(this.value, value)) record(null)
			else record(serialize(this.value) + "\n" + verb + "\n" + serialize(value))
			return function(message) {
				var result = results[results.length - 1]
				result.message = message + "\n\n" + result.message
			}
		}
	}
	function record(message, error) {
		var result = {pass: message === null}
		if (result.pass === false) {
			if (error == null) {
				error = new Error
				if (error.stack === undefined) new function() {try {throw error} catch (e) {error = e}}
			}
			result.context = subjects.join(" > ")
			result.message = message
			result.error = error.stack
		}
		results.push(result)
	}
	function serialize(value) {
		if (value === null || (typeof value === "object" && !(value instanceof Array)) || typeof value === "number") return String(value)
		else if (typeof value === "function") return value.name || "<anonymous function>"
		try {return JSON.stringify(value)} catch (e) {return String(value)}
	}
	function highlight(message) {
		return hasProcess ? "\x1b[31m" + message + "\x1b[0m" : "%c" + message + "%c "
	}

	function report() {
		var status = 0
		for (var i = 0, r; r = results[i]; i++) {
			if (!r.pass) {
				var stackTrace = r.error.match(/^(?:(?!Error|[\/\\]ospec[\/\\]ospec\.js).)*$/m)
				console.error(r.context + ":\n" + highlight(r.message) + (stackTrace ? "\n\n" + stackTrace + "\n\n" : ""), hasProcess ? "" : "color:red", hasProcess ? "" : "color:black")
				status = 1
			}
		}
		console.log(
			results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " +
			"of which " + results.filter(function(result){return result.error}).length + " failed"
		)
		if (hasProcess && status === 1) process.exit(1)
	}

	return o
}
<commit_msg>[ospec] Improve the dupe detection code<commit_after>"use strict"

module.exports = new function init() {
	var spec = {}, subjects = [], results = [], only = null, ctx = spec, start, stack = 0, hasProcess = typeof process === "object"

	function o(subject, predicate) {
		if (predicate === undefined) return new Assert(subject)
		ctx[unique(subject)] = predicate
	}
	o.before = hook("__before")
	o.after = hook("__after")
	o.beforeEach = hook("__beforeEach")
	o.afterEach = hook("__afterEach")
	o.new = init
	o.spec = function(subject, predicate) {
		var parent = ctx
		ctx = ctx[unique(subject)] = {}
		predicate()
		ctx = parent
	}
	o.only = function(subject, predicate) {o(subject, only = predicate)}
	o.spy = function(fn) {
		var spy = function() {
			spy.this = this
			spy.args = [].slice.call(arguments)
			spy.callCount++

			if (fn) return fn.apply(this, arguments)
		}
		if (fn)
			Object.defineProperties(spy, {
				length: {value: fn.length},
				name: {value: fn.name}
			})
		spy.args = []
		spy.callCount = 0
		return spy
	}
	o.run = function() {
		start = new Date
		test(spec, [], [], report)

		function test(spec, pre, post, finalize) {
			pre = [].concat(pre, spec["__beforeEach"] || [])
			post = [].concat(spec["__afterEach"] || [], post)
			series([].concat(spec["__before"] || [], Object.keys(spec).map(function(key) {
				return function(done, timeout) {
					timeout(Infinity)

					if (key.slice(0, 2) === "__") return done()
					if (only !== null && spec[key] !== only && typeof only === typeof spec[key]) return done()
					subjects.push(key)
					var type = typeof spec[key]
					if (type === "object") test(spec[key], pre, post, pop)
					if (type === "function") series([].concat(pre, spec[key], post, pop))

					function pop() {
						subjects.pop()
						done()
					}
				}
			}), spec["__after"] || [], finalize))
		}

		function series(fns) {
			var cursor = 0
			next()

			function next() {
				stack++
				if (cursor === fns.length) return

				var fn = fns[cursor++]
				if (fn.length > 0) {
					var timeout = 0, delay = 200, s = new Date
					var isDone = false
					var body = fn.toString()
					var arg = (body.match(/\(([\w$]+)/) || body.match(/([\w$]+)\s*=>/) || []).pop()
					if (body.indexOf(arg) === body.lastIndexOf(arg)) throw new Error("`" + arg + "()` should be called at least once")
					try {
						fn(function done() {
							if (timeout !== undefined) {
								timeout = clearTimeout(timeout)
								if (delay !== Infinity) record(null)
								if (!isDone) next()
								else throw new Error("`" + arg + "()` should only be called once")
								isDone = true
							}
							else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms")
						}, function(t) {delay = t})
					}
					catch (e) {
						record(e.message, e)
						subjects.pop()
						next()
					}
					if (timeout === 0) {
						timeout = setTimeout(function() {
							timeout = undefined
							record("async test timed out")
							next()
						}, Math.min(delay, 2147483647))
					}
				}
				else {
					fn()
					if (stack < 5000) next()
					else (hasProcess ? process.nextTick : setTimeout)(next, stack = 0)
				}
			}
		}
	}
	var dedupeCounter = 0
	function unique(subject) {
		var res = subject
		if (ctx.hasOwnProperty(subject)) {
			console.warn("A test or a spec named `" + subject + "` was already defined")
			while (ctx.hasOwnProperty(res)) res = subject + ' (' + ++dedupeCounter + ')'
			dedupeCounter = 0
		}
		return res
	}
	function hook(name) {
		return function(predicate) {
			if (ctx[name]) throw new Error("This hook should be defined outside of a loop or inside a nested test group:\n" + predicate)
			ctx[name] = predicate
		}
	}

	define("equals", "should equal", function(a, b) {return a === b})
	define("notEquals", "should not equal", function(a, b) {return a !== b})
	define("deepEquals", "should deep equal", deepEqual)
	define("notDeepEquals", "should not deep equal", function(a, b) {return !deepEqual(a, b)})

	function isArguments(a) {
		if ("callee" in a) {
			for (var i in a) if (i === "callee") return false
			return true
		}
	}
	function deepEqual(a, b) {
		if (a === b) return true
		if (a === null ^ b === null || a === undefined ^ b === undefined) return false
		if (typeof a === "object" && typeof b === "object") {
			var aIsArgs = isArguments(a), bIsArgs = isArguments(b)
			if (a.constructor === Object && b.constructor === Object && !aIsArgs && !bIsArgs) {
				for (var i in a) {
					if ((!(i in b)) || !deepEqual(a[i], b[i])) return false
				}
				for (var i in b) {
					if (!(i in a)) return false
				}
				return true
			}
			if (a.length === b.length && (a instanceof Array && b instanceof Array || aIsArgs && bIsArgs)) {
				var aKeys = Object.getOwnPropertyNames(a), bKeys = Object.getOwnPropertyNames(b)
				if (aKeys.length !== bKeys.length) return false
				for (var i = 0; i < aKeys.length; i++) {
					if (!b.hasOwnProperty(aKeys[i]) || !deepEqual(a[aKeys[i]], b[aKeys[i]])) return false
				}
				return true
			}
			if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
			if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) {
				for (var i = 0; i < a.length; i++) {
					if (a[i] !== b[i]) return false
				}
				return true
			}
			if (a.valueOf() === b.valueOf()) return true
		}
		return false
	}

	function Assert(value) {this.value = value}
	function define(name, verb, compare) {
		Assert.prototype[name] = function assert(value) {
			if (compare(this.value, value)) record(null)
			else record(serialize(this.value) + "\n" + verb + "\n" + serialize(value))
			return function(message) {
				var result = results[results.length - 1]
				result.message = message + "\n\n" + result.message
			}
		}
	}
	function record(message, error) {
		var result = {pass: message === null}
		if (result.pass === false) {
			if (error == null) {
				error = new Error
				if (error.stack === undefined) new function() {try {throw error} catch (e) {error = e}}
			}
			result.context = subjects.join(" > ")
			result.message = message
			result.error = error.stack
		}
		results.push(result)
	}
	function serialize(value) {
		if (value === null || (typeof value === "object" && !(value instanceof Array)) || typeof value === "number") return String(value)
		else if (typeof value === "function") return value.name || "<anonymous function>"
		try {return JSON.stringify(value)} catch (e) {return String(value)}
	}
	function highlight(message) {
		return hasProcess ? "\x1b[31m" + message + "\x1b[0m" : "%c" + message + "%c "
	}

	function report() {
		var status = 0
		for (var i = 0, r; r = results[i]; i++) {
			if (!r.pass) {
				var stackTrace = r.error.match(/^(?:(?!Error|[\/\\]ospec[\/\\]ospec\.js).)*$/m)
				console.error(r.context + ":\n" + highlight(r.message) + (stackTrace ? "\n\n" + stackTrace + "\n\n" : ""), hasProcess ? "" : "color:red", hasProcess ? "" : "color:black")
				status = 1
			}
		}
		console.log(
			results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " +
			"of which " + results.filter(function(result){return result.error}).length + " failed"
		)
		if (hasProcess && status === 1) process.exit(1)
	}

	return o
}
<|endoftext|>


<commit_before>var Browser = require('../interfaces/browser.js');
var objectAssign = require('object-assign');

function WebDriverIOAdaptor(webdriverioInstance) {
  this._webdriverio = webdriverioInstance;
}

WebDriverIOAdaptor.prototype = objectAssign({}, Browser, {
  takeScreenshot: this._webdriverio.takeScreenshot
});

module.exports = WebDriverIOAdaptor;
<commit_msg>takeScreenshot is saveScreenshot in webdriverio<commit_after>var Browser = require('../interfaces/browser.js');
var objectAssign = require('object-assign');

function WebDriverIOAdaptor(webdriverioInstance) {
  this._webdriverio = webdriverioInstance;
}

WebDriverIOAdaptor.prototype = objectAssign({}, Browser, {
  takeScreenshot: this._webdriverio.saveScreenshot
});

module.exports = WebDriverIOAdaptor;
<|endoftext|>


<commit_before>"use strict";

let PageCustom = Vue.component(
	'page-custom',
	{
		template: `

Custom Orders

I am currently accepting commissions and other custom orders! Commissions are expected to continue through October 2017 for fan art and mid-2018 for everything else.

Current Commission Slots:

1. rock

2. tseng

3. empty!

4. empty!

5. empty!

Types of Commissions

Fan Art: Any character from a commercial franchise. (See the <router-link :to="\{ path: '/catalog'}">General Catalog</router-link>.) Characters can be from anime, comics, games, television shows, movies, or anything else provided the parent company (and rights holder) is tolerant of fan art. This category is for characters that have a broad fanbase and that could be sold in the Artist Alley at anime conventions.

OCs: Short for “original characters,” this category includes any original design of your own, including self-insert characters for commercial franchises. Common examples include fursonas, MLP ponysonas, and customized characters for popular anime. These are unique characters of your own design that would not be sold in the Artist Alley at anime conventions.

People [head only]: This category is for drawings made from photos of you or anyone else you know in real life. For the time being, I will only draw heads from photos like this, meaning you will have to use bodies from other characters (to “cosplay”).

Pets / Animals: I will draw your pet or a generic animal in a mini size.

Color Edits: Any already-existing drawing can have its colors altered. (Lineart alterations count as a new commission in one of the above categories, though a reduced rate may be arranged depending on the scope of the requested alteration.)

Other Special Orders: Other requests will be handled the same way as the next most-similar commission. E.g., a head-on-body non-decapitation order will be handled and charged like a POD magnet order.

Prices

							Size
							POD
							Drawing
							Cost
							Bulk Rate
							Bulk Cost
							Examples

							Fan Art
							large
							no
							$40
							$4

							Sephiroth, Vash the Stampede

							mini
							no
							$30
							$2

							Tony Tony Chopper, Raichu

							OCs
							large
							yes
							$60
							$6
							6+
							$5
							a fursona, a DnD character

							People [head only]
							large
							yes
							$25
							$4
							10+
							$3
							you, your fiance, your children, your BFF

							Pets / Animals
							mini
							yes
							$30
							$4
							12+
							$3
							your pet dog, a Red-tailed Hawk

							Color Edits
							large
							yes
							$10
							$6
							6+
							$5
							game pallet swaps, hair “dye”

							mini
							yes
							$10
							$4
							12+
							$3
							shiny Pokemon, pet breed color variations

Remember that shipping is not included in the above chart, and will be at the regular rates. (See the <router-link :to="\{ path: '/order'}">Order</router-link> section for current rates.)

Size: Cosplay Scramble magnets come in two sizes: large and small/mini. Generally humans and humanoids are the large size, while animals or small creatures, like Pokemon, are the small size.

POD: This column indicates whether magnets are print-on-demand (as opposed to pre-made). Print-on-demand magnets are not made until ordered, and are hand cut unless ordered in bulk.

Drawing: This is how much it costs to get the drawing made. The drawing cost also includes one copy of the finished drawing as a cut magnet.

Cost: The first magnet is free, so this column is the cost of each magnet after the first if you want more than one. Special cases can be made for POD magnets that are ordered in bulk, which can be cost-effectively cut by machine. For the purposes of ordering in bulk, POD magnets that have the same cuttable outline count toward the total.

Examples: Some examples of each category.

Commission Procedure

Each commissions slot allows someone to commission up to three new characters. Note that slots may be finished out of order depending on the drawings’ difficulty.

While I may decline or delay the commission due to subject matter or logistics issues, I will try to get as many done as I can this year. In advance I will say that I won’t draw anything controversial (i.e. lewd, politically charged, excessively violent).

There are limitations on poses and characters due to the format, so arms cannot be above the character’s head and thin pieces like tails or antenna will have to be thickened so the magnet won’t break off. In addition, Cosplay Scramble magnets cannot be made from characters without a workable jawline, such as characters whose entire body is their head (e.g. Jigglypuff) or bulky characters without anything resembling the anatomy of a neck (e.g. Totoro).

Reserve a Slot

To reserve a slot, email me with the words “reserve a slot” in the subject line.

I’ll also need to know who or what you’d like me to draw. If your commission is fan art, you’ll need to let me know if there’s a specific outfit you would prefer. If your commission is an original character, I’ll need head-to-toe reference. Attach or link any relevant images at this step. I might have questions about specific details, particularly for characters from franchises I’m not familiar with.

If you have any special requests, let me know at this point in case it will affect the final price. Special requests can include custom packaging (perhaps for a party), shipping to multiple addresses, non-decapitation, etc.

Once we’ve agreed on what I will be drawing, your slot is reserved! I will pick a codeword to represent your order in the Commissions Slot list above.

Payment

Once I’ve agreed to your commission, I will send a Paypal invoice to your email address. Paypal will let you pay in several ways, including with a credit card, so you won’t need a Paypal account to pay. The shipping rates are the same as for a general order — see the <router-link :to="\{ path: '/order'}">Order</router-link> section for current shipping prices.

(Magnets from the General Catalog can be ordered and shipped with your commission. Just let me know which sets you want, and I’ll include them on the invoice. You’ll receive all ordered magnets together.)

You may cancel your commission at any point, but the refund may be reduced depending on how far along I am. (E.g., if I haven’t started drawing yet you will get a full refund, but if I am done drawing but haven’t made the magnet yet you will only get the magnet manufacturing cost back.)

I do insist that the full commission cost is paid in advance. If payment takes too long, there’s a good change I will ask you to give up your slot (at least for the moment) so that others may have a turn.

Production

Once payment clears, I’ll begin. Note: because some commissions will be more difficult than others, I may finish them out of order. I want them all to turn out well, and sometimes I need extra time to make sure I get them right.

When I’m done drawing, I’ll send you the finished image to see what you think. I can still make changes at this point, so it’s important to make sure I got everything about the character correct. (If I haven’t seen the anime the character is from, for example, I might miss an important detail.) Then, once we are both satisfied, I will print, magnet-ify, and cut the finished drawing(s). I’ll ship them as soon as they’re done, you’ll receive them in the mail!

Fan art commissions will appear in the General Catalog at this point, and will also be available at any future conventions (while supplies last).

Contact

Questions? Send me an email me or convo me on Etsy.

		`
	}
);
<commit_msg>fixed pricing error<commit_after>"use strict";

let PageCustom = Vue.component(
	'page-custom',
	{
		template: `

Custom Orders

I am currently accepting commissions and other custom orders! Commissions are expected to continue through October 2017 for fan art and mid-2018 for everything else.

Current Commission Slots:

1. rock

2. tseng

3. empty!

4. empty!

5. empty!

Types of Commissions

Fan Art: Any character from a commercial franchise. (See the <router-link :to="\{ path: '/catalog'}">General Catalog</router-link>.) Characters can be from anime, comics, games, television shows, movies, or anything else provided the parent company (and rights holder) is tolerant of fan art. This category is for characters that have a broad fanbase and that could be sold in the Artist Alley at anime conventions.

OCs: Short for “original characters,” this category includes any original design of your own, including self-insert characters for commercial franchises. Common examples include fursonas, MLP ponysonas, and customized characters for popular anime. These are unique characters of your own design that would not be sold in the Artist Alley at anime conventions.

People [head only]: This category is for drawings made from photos of you or anyone else you know in real life. For the time being, I will only draw heads from photos like this, meaning you will have to use bodies from other characters (to “cosplay”).

Pets / Animals: I will draw your pet or a generic animal in a mini size.

Color Edits: Any already-existing drawing can have its colors altered. (Lineart alterations count as a new commission in one of the above categories, though a reduced rate may be arranged depending on the scope of the requested alteration.)

Other Special Orders: Other requests will be handled the same way as the next most-similar commission. E.g., a head-on-body non-decapitation order will be handled and charged like a POD magnet order.

Prices

							Size
							POD
							Drawing
							Cost
							Bulk Rate
							Bulk Cost
							Examples

							Fan Art
							large
							no
							$40
							$4

							Sephiroth, Vash the Stampede

							mini
							no
							$30
							$2

							Tony Tony Chopper, Raichu

							OCs
							large
							yes
							$60
							$6
							6+
							$5
							a fursona, a DnD character

							People [head only]
							large
							yes
							$25
							$4
							10+
							$3
							you, your fiance, your children, your BFF

							Pets / Animals
							mini
							yes
							$30
							$4
							12+
							$3
							your pet dog, a Red-tailed Hawk

							Color Edits
							large
							yes
							$15
							$6
							6+
							$5
							game pallet swaps, hair “dye”

							mini
							yes
							$10
							$4
							12+
							$3
							shiny Pokemon, pet breed color variations

Remember that shipping is not included in the above chart, and will be at the regular rates. (See the <router-link :to="\{ path: '/order'}">Order</router-link> section for current rates.)

Size: Cosplay Scramble magnets come in two sizes: large and small/mini. Generally humans and humanoids are the large size, while animals or small creatures, like Pokemon, are the small size.

POD: This column indicates whether magnets are print-on-demand (as opposed to pre-made). Print-on-demand magnets are not made until ordered, and are hand cut unless ordered in bulk.

Drawing: This is how much it costs to get the drawing made. The drawing cost also includes one copy of the finished drawing as a cut magnet.

Cost: The first magnet is free, so this column is the cost of each magnet after the first if you want more than one. Special cases can be made for POD magnets that are ordered in bulk, which can be cost-effectively cut by machine. For the purposes of ordering in bulk, POD magnets that have the same cuttable outline count toward the total.

Examples: Some examples of each category.

Commission Procedure

Each commissions slot allows someone to commission up to three new characters. Note that slots may be finished out of order depending on the drawings’ difficulty.

While I may decline or delay the commission due to subject matter or logistics issues, I will try to get as many done as I can this year. In advance I will say that I won’t draw anything controversial (i.e. lewd, politically charged, excessively violent).

There are limitations on poses and characters due to the format, so arms cannot be above the character’s head and thin pieces like tails or antenna will have to be thickened so the magnet won’t break off. In addition, Cosplay Scramble magnets cannot be made from characters without a workable jawline, such as characters whose entire body is their head (e.g. Jigglypuff) or bulky characters without anything resembling the anatomy of a neck (e.g. Totoro).

Reserve a Slot

To reserve a slot, email me with the words “reserve a slot” in the subject line.

I’ll also need to know who or what you’d like me to draw. If your commission is fan art, you’ll need to let me know if there’s a specific outfit you would prefer. If your commission is an original character, I’ll need head-to-toe reference. Attach or link any relevant images at this step. I might have questions about specific details, particularly for characters from franchises I’m not familiar with.

If you have any special requests, let me know at this point in case it will affect the final price. Special requests can include custom packaging (perhaps for a party), shipping to multiple addresses, non-decapitation, etc.

Once we’ve agreed on what I will be drawing, your slot is reserved! I will pick a codeword to represent your order in the Commissions Slot list above.

Payment

Once I’ve agreed to your commission, I will send a Paypal invoice to your email address. Paypal will let you pay in several ways, including with a credit card, so you won’t need a Paypal account to pay. The shipping rates are the same as for a general order — see the <router-link :to="\{ path: '/order'}">Order</router-link> section for current shipping prices.

(Magnets from the General Catalog can be ordered and shipped with your commission. Just let me know which sets you want, and I’ll include them on the invoice. You’ll receive all ordered magnets together.)

You may cancel your commission at any point, but the refund may be reduced depending on how far along I am. (E.g., if I haven’t started drawing yet you will get a full refund, but if I am done drawing but haven’t made the magnet yet you will only get the magnet manufacturing cost back.)

I do insist that the full commission cost is paid in advance. If payment takes too long, there’s a good change I will ask you to give up your slot (at least for the moment) so that others may have a turn.

Production

Once payment clears, I’ll begin. Note: because some commissions will be more difficult than others, I may finish them out of order. I want them all to turn out well, and sometimes I need extra time to make sure I get them right.

When I’m done drawing, I’ll send you the finished image to see what you think. I can still make changes at this point, so it’s important to make sure I got everything about the character correct. (If I haven’t seen the anime the character is from, for example, I might miss an important detail.) Then, once we are both satisfied, I will print, magnet-ify, and cut the finished drawing(s). I’ll ship them as soon as they’re done, you’ll receive them in the mail!

Fan art commissions will appear in the General Catalog at this point, and will also be available at any future conventions (while supplies last).

Contact

Questions? Send me an email me or convo me on Etsy.

		`
	}
);
<|endoftext|>


<commit_before>'use strict';

const authenticate = require('./authenticate');
const csrf = require('./csrf');
const error = require('./error');
const exclusions = require('./exclusions');
const logError = require('./log-error');
const login = require('./login');
const logout = require('./logout');
const notifications = require('./notifications');
const originalUrl = require('./original-url');
const setLinzModel = require('./set-linz-model');
const setLinzNamespace = require('./set-linz-namespace');
const setModelForm = require('./set-model-form');
const setRecord = require('./set-record');

module.exports = {
    authenticate,
    csrf,
    error,
    exclusions,
    logError,
    login,
    logout,
    notifications,
    originalUrl,
    setLinzModel,
    setLinzNamespace,
    setModelForm,
    setRecord,
};
<commit_msg>remove old csrf requires<commit_after>'use strict';

const authenticate = require('./authenticate');
const error = require('./error');
const exclusions = require('./exclusions');
const logError = require('./log-error');
const login = require('./login');
const logout = require('./logout');
const notifications = require('./notifications');
const originalUrl = require('./original-url');
const setLinzModel = require('./set-linz-model');
const setLinzNamespace = require('./set-linz-namespace');
const setModelForm = require('./set-model-form');
const setRecord = require('./set-record');

module.exports = {
    authenticate,
    error,
    exclusions,
    logError,
    login,
    logout,
    notifications,
    originalUrl,
    setLinzModel,
    setLinzNamespace,
    setModelForm,
    setRecord,
};
<|endoftext|>


<commit_before>var request = require('request'), // For making HTTP requests
    cheerio = require('cheerio'), // For parsing the DOM
    events = require('events'), // For emitters and listeners
    chalk = require('chalk'), // For coloring cli outputs
    fs = require('fs'), // For writing to file
    URI = require('URIjs'); // For using URIs as objects

var SiteMapCrawler = function(url, protocol, timeout, outputFile) {
  this.eventEmitter = new events.EventEmitter();

  this.initialUrl = url;
  this.protocol = protocol;
  this.timeout = parseInt(timeout) || 3000; // 3 seconds
  this.retries = 5;

  this.uriList = [];
  this.uriQueue = [];
  this.uriAssetsList = [];
  this.retryList = [];
  this.invalidLinkCount = 0;

  this.domain = new URI(url).domain();
  console.log('Domain crawled is: ' + this.domain);

  // Initialize our listener
  this.printFinalUrlList(outputFile);
};

SiteMapCrawler.prototype.setInfoLevel = function(info) {
  this.info = info;
};

SiteMapCrawler.prototype.setRetries = function(retry) {
  this.retries = retry;
};

SiteMapCrawler.prototype.crawlUrl = function(url) {
  siteMapCrawler = this;
  var anchorList = [];
  var imgList = [];
  var options = {
    method: 'GET',
    url: url,
    timeout: siteMapCrawler.timeout,
  };

  // Log what site we are crawling on -i option
  if(siteMapCrawler.info) {
    console.log('Crawling ' + url);
  // Otherwise mark our action (for console visual aid)
  } else { process.stdout.write('.'); }

  var req = request(options, function(err, res, body) {

    if (res) {
      if (res.statusCode >= 200 && res.statusCode < 300) { // Make sure we have a valid response
        if (body) {
          var $ = cheerio.load(body);  //Let's use JQuery selectors
          $('a').each( function(i, elem) { // Loop over anchor tags in our body
            var link = $(elem).attr('href'); // Extract the link

            // Make sure the link exists and is not already in our list
            if (link && anchorList.indexOf(link) === -1) {
              anchorList.push(link);
            }
          });

          // Do the same for all image links
          $('img').each( function(i, elem) {
            var img = $(elem).attr('src');
            // Make sure the link exists and is not already in our list
            if (img && imgList.indexOf(img) === -1) {
              // Keep only images on our own domain
              var imgUri = URI(img);
              if(imgUri.is('relative'))
                imgList.push(img);
            }
          });

          // Find static css and js files the same way

          // Get the uri from the origin of the response
          // This is useful if we were redirected
          var requestUri = res.request.uri.href;
          // Call our method to parse the anchors
          siteMapCrawler.updateUrlList(anchorList, requestUri, imgList);

        }
      }
      else if (res.statusCode >= 400) {
        console.log(chalk.bold.yellow('\nGET returned status code: %s for %s'), res.statusCode, url);
        siteMapCrawler.invalidLinkCount++;
      }
      siteMapCrawler.eventEmitter.emit('urlProccessed', url);
    }
    // Something went wrong with the request
    if (err) {
      // Retry if this was a read error on our part
      // This happens because we're pooling through our connections too fast
      if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
        var retryList = siteMapCrawler.retryList;
        var retriedLink = {};
        var isFound = false;

        // console.log(chalk.cyan("ETIMEDOUT occured"));

        for(var i=0; i < retryList.length; i++){
          if(retryList[i].url === url) {
            isFound = true;
            retryList[i].try++;
            retriedLink = retryList[i];
          }
        }

        if(isFound === true) {
          if(retriedLink.try > siteMapCrawler.retries) {
            console.log(chalk.bold.yellow('\nRetries exceeded for %s'), url);
            siteMapCrawler.eventEmitter.emit('urlProccessed', url);
            return; // Because we don't want to start a new crawl
          }
        } else {
          // Make a retry link
          retriedLink = {
            url: url,
            try: 1
          };

          // Add link to list of retried url
          siteMapCrawler.retryList.push(retriedLink);
        }
        siteMapCrawler.crawlUrl(url);

      } else {
        // The url actually doesn't work, take it out of the queue
        console.log(chalk.red('\n' + err));
        siteMapCrawler.eventEmitter.emit('urlProccessed', url);
      }
    }
  });
};

SiteMapCrawler.prototype.updateUrlList = function(anchorList, originUrl, assetList) {
  siteMapCrawler = this;
  for(var i=0; i < anchorList.length; i++) {
    // Make new URIs from our anchor list
    var tempUri = URI(anchorList[i]);
    // Remove any trailing query strings
    tempUri = tempUri.search("");
    // Remove any trailing hashes
    tempUri = tempUri.fragment("");

    if (tempUri.is('relative')) { // Ex: Relative would be "/about"
      // Append our relative uri to our main one
      var encodedPath = URI(tempUri.href()).pathname(true);
      tempUri = URI(originUrl).pathname(encodedPath);
    }

    // Normalize the subdomain if not specified
    if(tempUri.subdomain() === '') {
      tempUri = tempUri.subdomain('www');
    }

    // Normalize the protocol
    tempUri = tempUri.protocol(siteMapCrawler.protocol);

    // Remove irregularities in URL (like capital letters)
    tempUri = tempUri.normalize();
    //tempUri = tempUri.encode();

    // Verify we are in the same domain
    if(tempUri.domain() === siteMapCrawler.domain) {
      if(siteMapCrawler.uriList.indexOf(tempUri.href()) === -1) {
        // Then add to our uri list
        siteMapCrawler.uriList.push(tempUri.href());

        // Make a new object we will save for priting later
        var uriAndAssets = {
          url: tempUri.href(),
          assets: assetList
        };
        siteMapCrawler.uriAssetsList.push(uriAndAssets);

        // Add our uris to the queue
        siteMapCrawler.uriQueue.push(tempUri.href());
        siteMapCrawler.crawlUrl(tempUri.href());
      }
    }
  }
  // console.log(siteMapCrawler.uriList); // debug
};

SiteMapCrawler.prototype.proccessQueue = function(startUrl) {
  siteMapCrawler = this;
  var queue = siteMapCrawler.uriQueue;
  // Initial run
  siteMapCrawler.crawlUrl(startUrl);

  // Listen for the end of each url crawls
  siteMapCrawler.eventEmitter.on('urlProccessed', function(url) {
    // Start a new crawl with the first element of the queue
    var next = queue.shift();
    if(next) {
      // Still has stuff to do
    } else { // If the queue is empty we're done crawling
      siteMapCrawler.eventEmitter.emit('queueProccessed');
    }
  });

};

SiteMapCrawler.prototype.printFinalUrlList = function(output) {
  siteMapCrawler = this;

  siteMapCrawler.eventEmitter.on('queueProccessed', function() {
    console.log(chalk.cyan('\nDone!'));
    console.log(chalk.bold.green('The crawler found %s unique urls'), siteMapCrawler.uriList.length);
    console.log(chalk.bold.yellow('%s invalid links were found'), siteMapCrawler.invalidLinkCount);

    var finalList = siteMapCrawler.uriList.sort();
    var outputFile = output || "results.txt";

    // Create the file, crushes old one
    fs.writeFileSync(outputFile, '');
    console.log(chalk.cyan(outputFile + ' has been created'));

    if (outputFile) {
      for(var i=0; i < finalList.length; i++) {
        var link = finalList[i];

        // Find this list in the list with assets and print it
        for (var j=0; j < siteMapCrawler.uriAssetsList.length; j++) {
          if(siteMapCrawler.uriAssetsList[j].url === finalList[i]) {
            fs.appendFileSync(outputFile, link + '\n');

            // Print all of the assets for this link
            var linkAssets = siteMapCrawler.uriAssetsList[j].assets;
            for(var k=0; k < linkAssets.length; k++) {
              fs.appendFileSync(outputFile, '\t' + linkAssets[k] + '\n');
            }

          }
        }
      }
    }

    // We're done here
    process.exit(0);
  });

  // on SIGINT event, print what we found
  process.on('SIGINT', function() {
    siteMapCrawler.eventEmitter.emit('queueProccessed');
  });

};

module.exports = SiteMapCrawler;
<commit_msg>Cleaned up code inconsistencies<commit_after>var request = require('request'), // For making HTTP requests
    cheerio = require('cheerio'), // For parsing the DOM
    events = require('events'), // For emitters and listeners
    chalk = require('chalk'), // For coloring cli outputs
    fs = require('fs'), // For writing to file
    URI = require('URIjs'); // For using URIs as objects

var SiteMapCrawler = function(url, protocol, timeout, outputFile) {
  this.eventEmitter = new events.EventEmitter();

  this.initialUrl = url;
  this.protocol = protocol;
  this.timeout = parseInt(timeout) || 3000; // 3 seconds
  this.retries = 5;

  this.uriList = [];
  this.uriQueue = [];
  this.uriAssetsList = [];
  this.retryList = [];
  this.invalidLinkCount = 0;

  this.domain = new URI(url).domain();
  console.log('Domain crawled is: ' + this.domain);

  // Initialize our listener
  this.printFinalUrlList(outputFile);
};

SiteMapCrawler.prototype.setInfoLevel = function(info) {
  this.info = info;
};

SiteMapCrawler.prototype.setRetries = function(retry) {
  this.retries = retry;
};

SiteMapCrawler.prototype.crawlUrl = function(url) {
  var siteMapCrawler = this;
  var anchorList = [];
  var imgList = [];
  var options = {
    method: 'GET',
    url: url,
    timeout: siteMapCrawler.timeout,
  };

  // Log what site we are crawling on -i option
  if(siteMapCrawler.info) {
    console.log('Crawling ' + url);
  // Otherwise mark our action (for console visual aid)
  } else { process.stdout.write('.'); }

  var req = request(options, function(err, res, body) {

    if(res) {
      if(res.statusCode >= 200 && res.statusCode < 300) { // Make sure we have a valid response
        if(body) {
          var $ = cheerio.load(body);  //Let's use JQuery selectors
          $('a').each( function(i, elem) { // Loop over anchor tags in our body
            var link = $(elem).attr('href'); // Extract the link

            // Make sure the link exists and is not already in our list
            if(link && anchorList.indexOf(link) === -1) {
              anchorList.push(link);
            }
          });

          // Do the same for all image links
          $('img').each( function(i, elem) {
            var img = $(elem).attr('src');
            // Make sure the link exists and is not already in our list
            if(img && imgList.indexOf(img) === -1) {
              // Keep only images on our own domain
              var imgUri = URI(img);
              if(imgUri.is('relative'))
                imgList.push(img);
            }
          });

          // Find static css and js files the same way

          // Get the uri from the origin of the response
          // This is useful if we were redirected
          var requestUri = res.request.uri.href;
          // Call our method to parse the anchors
          siteMapCrawler.updateUrlList(anchorList, requestUri, imgList);

        }
      }
      else if(res.statusCode >= 400) {
        console.log(chalk.bold.yellow('\nGET returned status code: %s for %s'), res.statusCode, url);
        siteMapCrawler.invalidLinkCount++;
      }
      siteMapCrawler.eventEmitter.emit('urlProccessed', url);
    }
    // Something went wrong with the request
    if(err) {
      // Retry if this was a read error on our part
      // This happens because we're pooling through our connections too fast
      if(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
        var retryList = siteMapCrawler.retryList;
        var retriedLink = {};
        var isFound = false;

        // console.log(chalk.cyan("ETIMEDOUT occured"));

        for(var i=0; i < retryList.length; i++){
          if(retryList[i].url === url) {
            isFound = true;
            retryList[i].try++;
            retriedLink = retryList[i];
          }
        }

        if(isFound === true) {
          if(retriedLink.try > siteMapCrawler.retries) {
            console.log(chalk.bold.yellow('\nRetries exceeded for %s'), url);
            siteMapCrawler.eventEmitter.emit('urlProccessed', url);
            return; // Because we don't want to start a new crawl
          }
        } else {
          // Make a retry link
          retriedLink = {
            url: url,
            try: 1
          };

          // Add link to list of retried url
          siteMapCrawler.retryList.push(retriedLink);
        }
        siteMapCrawler.crawlUrl(url);

      } else {
        // The url actually doesn't work, take it out of the queue
        console.log(chalk.red('\n' + err));
        siteMapCrawler.eventEmitter.emit('urlProccessed', url);
      }
    }
  });
};

SiteMapCrawler.prototype.updateUrlList = function(anchorList, originUrl, assetList) {
  var siteMapCrawler = this;
  for(var i=0; i < anchorList.length; i++) {
    // Make new URIs from our anchor list
    var tempUri = URI(anchorList[i]);
    // Remove any trailing query strings
    tempUri = tempUri.search("");
    // Remove any trailing hashes
    tempUri = tempUri.fragment("");

    if(tempUri.is('relative')) { // Ex: Relative would be "/about"
      // Append our relative uri to our main one
      var encodedPath = URI(tempUri.href()).pathname(true);
      tempUri = URI(originUrl).pathname(encodedPath);
    }

    // Normalize the subdomain if not specified
    if(tempUri.subdomain() === '') {
      tempUri = tempUri.subdomain('www');
    }

    // Normalize the protocol
    tempUri = tempUri.protocol(siteMapCrawler.protocol);

    // Remove irregularities in URL (like capital letters)
    tempUri = tempUri.normalize();
    //tempUri = tempUri.encode();

    // Verify we are in the same domain
    if(tempUri.domain() === siteMapCrawler.domain) {
      if(siteMapCrawler.uriList.indexOf(tempUri.href()) === -1) {
        // Then add to our uri list
        siteMapCrawler.uriList.push(tempUri.href());

        // Make a new object we will save for priting later
        var uriAndAssets = {
          url: tempUri.href(),
          assets: assetList
        };
        siteMapCrawler.uriAssetsList.push(uriAndAssets);

        // Add our uris to the queue
        siteMapCrawler.uriQueue.push(tempUri.href());
        siteMapCrawler.crawlUrl(tempUri.href());
      }
    }
  }
  // console.log(siteMapCrawler.uriList); // debug
};

SiteMapCrawler.prototype.proccessQueue = function(startUrl) {
  var siteMapCrawler = this;
  var queue = siteMapCrawler.uriQueue;
  // Initial run
  siteMapCrawler.crawlUrl(startUrl);

  // Listen for the end of each url crawls
  siteMapCrawler.eventEmitter.on('urlProccessed', function(url) {
    // Start a new crawl with the first element of the queue
    var next = queue.shift();
    if(next) {
      // Still has stuff to do
    } else { // If the queue is empty we're done crawling
      siteMapCrawler.eventEmitter.emit('queueProccessed');
    }
  });

};

SiteMapCrawler.prototype.printFinalUrlList = function(output) {
  var siteMapCrawler = this;

  siteMapCrawler.eventEmitter.on('queueProccessed', function() {
    console.log(chalk.cyan('\nDone!'));
    console.log(chalk.bold.green('The crawler found %s unique urls'), siteMapCrawler.uriList.length);
    console.log(chalk.bold.yellow('%s invalid links were found'), siteMapCrawler.invalidLinkCount);

    var finalList = siteMapCrawler.uriList.sort();
    var outputFile = output || "results.txt";

    // Create the file, crushes old one
    fs.writeFileSync(outputFile, '');
    console.log(chalk.cyan(outputFile + ' has been created'));

    if(outputFile) {
      for(var i=0; i < finalList.length; i++) {
        var link = finalList[i];

        // Find this list in the list with assets and print it
        for (var j=0; j < siteMapCrawler.uriAssetsList.length; j++) {
          if(siteMapCrawler.uriAssetsList[j].url === finalList[i]) {
            fs.appendFileSync(outputFile, link + '\n');

            // Print all of the assets for this link
            var linkAssets = siteMapCrawler.uriAssetsList[j].assets;
            for(var k=0; k < linkAssets.length; k++) {
              fs.appendFileSync(outputFile, '\t' + linkAssets[k] + '\n');
            }

          }
        }
      }
    }

    // We're done here
    process.exit(0);
  });

  // on SIGINT event, print what we found
  process.on('SIGINT', function() {
    siteMapCrawler.eventEmitter.emit('queueProccessed');
  });

};

module.exports = SiteMapCrawler;
<|endoftext|>