<commit_before>/*
 * Copyright (c) 2017, David Ball
 * All rights reserved.
 *
 * 3-clause BSD
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

var exampleInfo

// ROS code
var ros = new ROSLIB.Ros()
var connectedROSLIB = false
var intervalActive = false
var interval
var intervalGetROSDetails

var viewer
var markerClient

var selectedTopicName = ['undefined', 'undefined']
var selectedParamName = ['undefined', 'undefined']
var topicListener

function tryROSConnection () {
  if (connectedROSLIB === false) {
    ros.connect('ws://' + document.getElementById('host_name').value + ':' + document.getElementById('rosbridge_port').value)
  }
}

ros.on('connection', function () {
  connectedROSLIB = true
  console.log('Connected to websocket server.')
  intervalActive = false
  clearInterval(interval)
  intervalGetROSDetails = setInterval(getROSDetails, 500)

  for (var id = 0; id < 2; id++) {
    // set the defaults from the xml file
    if (exampleInfo.example.feedback[id].tab == 'topics') {
      selectedTopicName[id] = exampleInfo.example.feedback[id].topic[0]
    } else if (exampleInfo.example.feedback[id].tab == 'viz') {
      if (typeof markerClient !== 'undefined') {
        markerClient = null
      }

      if (typeof viewer === 'undefined') {
        // TODO: The width and height need to come from somewhere meaningful
        viewer = new ROS3D.Viewer({
          divID: 'roslib_viewer' + id,
          width: 300,
          height: 200,
          antialias: true
        })
      }

      markerClient = new ROS3D.MarkerClient({
        ros: ros,
        tfClient: new ROSLIB.TFClient({
          ros: ros,
          angularThres: 0.01,
          transThres: 0.01,
          rate: 10.0,
          fixedFrame: exampleInfo.example.feedback[id].frame[0]
        }),
        topic: exampleInfo.example.feedback[id].topic[0],
        rootObject: viewer.scene
      })
    }
  }
})

ros.on('error', function (error) {
  console.log('Error connecting to websocket server: ', error)
})

ros.on('close', function () {
  clearInterval(intervalGetROSDetails)
  console.log('Connection to websocket server closed.')
})

function setTopicSub (topicName, id) {
  if (topicName === selectedTopicName[id] && typeof topicListener !== 'undefined') {
    return
  } else {
    selectedTopicName[id] = topicName
  }

  var topicType
  if (topicName === '/rosout' || topicName === 'rosout_agg') {
    topicType = 'rosgraph_msgs/Log'
  } else if (topicName === exampleInfo.example.feedback[id].topic[0]) {
    topicType = exampleInfo.example.feedback[id].msg_type[0]
  } else {
    // hope this works.
    topicType = 'std_msgs/String'
  }

  if (typeof topicListener !== 'undefined') {
    topicListener.unsubscribe()
    topicListener = null
    document.getElementById('show_topic' + id).value = ''
  }

    // TODO: Why do I need to set the messagetype for the listener?
  topicListener = new ROSLIB.Topic({
    ros: ros,
    name: topicName,
    messageType: topicType
  })

  topicListener.subscribe(function (message) {
    // set 2 space tabs and get rid of the ""
    // message = JSON.stringify(message, null, 2).replace(/\"([^(\")"]+)\":/g, '$1:')
    message = JSON.stringify(message, null, 2).replace(/([^()"]+):/g, '$1:')
    document.getElementById('show_topic' + id).value += message + '\r'
    document.getElementById('show_topic' + id).scrollTop = document.getElementById('show_topic' + id).scrollHeight
  })
}

function onTopicListSelect (element) {
  setTopicSub(element.value, element.id.slice(-1))
}

var paramROSLIB
function setParam (param) {
  selectedParamName = param
  if (typeof paramROSLIB !== 'undefined') {
    paramROSLIB = null
  }
  paramROSLIB = new ROSLIB.Param({
    ros: ros,
    name: selectedParamName
  })
}

function onParamListSelect (element) {
  var selValue = element.value
  setParam(selValue)
  paramROSLIB.get(function (value) {
    document.getElementById('show_param').value = value
  })
}

/**
 * Gets and updates the list of ROS topics, nodes, services, and parameters.
 * This is repeatidly called at an interval whilst the ROS bridge is connected.
 * TODO: It would be much better if this was pushed to us when a change happens.
 * At the least we could store the callback values and check if they have changed.
 */
function getROSDetails () {
  ros.getTopics(function (topics) {
    // TODO: It's a shame this doesn't also return the message types.
    for (var id = 0; id < 2; id++) {
      var sel = document.getElementById('topic_list' + id)
      sel.options.length = 0
      for (var topic in topics) {
        // TODO: Also work out a way to remove the tf's that appear ... but only if they shouldn't!
        // This might need to become a user configurable parameter.
        if (topics[topic].indexOf('tf2_web_republisher') === -1) {
          var opt = document.createElement('option')
          opt.innerHTML = topics[topic]
          opt.value = topics[topic]
          sel.appendChild(opt)
        }
      }
      sel.value = selectedTopicName[id]
      setTopicSub(selectedTopicName[id], id)
    }
  })
  ros.getNodes(function (nodes) {
    for (var id = 0; id < 2; id++) {
      var sel = document.getElementById('node_list' + id)
      sel.options.length = 0
      for (var node in nodes) {
        if (nodes[node].indexOf('tf2_web_republisher') === -1 &&
            nodes[node].indexOf('rosbridge_websocket') === -1) {
          var opt = document.createElement('option')
          opt.innerHTML = nodes[node]
          opt.value = nodes[node]
          sel.appendChild(opt)
        }
      }
    }
  })
  ros.getServices(function (services) {
    for (var id = 0; id < 2; id++) {
      var sel = document.getElementById('service_list' + id)
      sel.options.length = 0
      for (var service in services) {
        var opt = document.createElement('option')
        opt.innerHTML = services[service]
        opt.value = services[service]
        sel.appendChild(opt)
      }
    }
  })
  ros.getParams(function (params) {
    for (var id = 0; id < 2; id++) {
      var sel = document.getElementById('param_list' + id)
      sel.options.length = 0
      for (var param in params) {
        if (params[param].indexOf('rosbridge_websocket') === -1 &&
            params[param].indexOf('rosapi') === -1) {
          var opt = document.createElement('option')
          opt.innerHTML = params[param]
          opt.value = params[param]
          sel.appendChild(opt)
        }
      }
      if (typeof selectedParamName[id] !== 'undefined') {
        sel.value = selectedParamName[id]
      }
    }
  })
}

/**
 * This function updates the terminal feedback textbox.
 */
$(document).ready(function () {
  var socket = io()
  socket.on('compile_output', function (msg) {
    for (var id = 0; id < 2; id++) {
      document.getElementById('show_terminal' + id).value += msg
      document.getElementById('show_terminal' + id).scrollTop = document.getElementById('show_terminal' + id).scrollHeight
    }
  })
})

var files = {}
var editor = ace.edit('editor')
editor.setTheme('ace/theme/chrome')
var EditSession = ace.require('ace/edit_session').EditSession
var editorSessions = {}

var exampleName = location.search.split('name=')[1]

$.get('/get_files?name=' + exampleName, function (data) {
  // parse the data into the files for the editor and the general example information
  var parsedData = JSON.parse(data)
  files = parsedData[0]
  exampleInfo = parsedData[1]

   // load each file into its own editor
  var sel = document.getElementById('file_list')
  for (var filename in files) {
    var opt = document.createElement('option')
    opt.value = filename
    opt.innerHTML = filename
    sel.appendChild(opt)
    var filenameExtension = filename.split('.').pop()
    editorSessions[filename] = new EditSession(filename)
    if (filenameExtension === 'cpp' || filenameExtension === 'c') { editorSessions[filename].setMode('ace/mode/c_cpp') } else if (filenameExtension === 'xml') { editorSessions[filename].setMode('ace/mode/xml') } else { editorSessions[filename].setMode('ace/mode/text') }
    editorSessions[filename].setValue(files[filename], -1)
  }

   // set the initial file for editing
   // TODO: Test if this is set / valid
  editor.setSession(editorSessions[exampleInfo.example.start_file])
  sel.value = exampleInfo.example.start_file

   // set other parts of the page
  document.getElementById('run_cmd').innerHTML = exampleInfo.example.run_cmd
  document.getElementById('example_title').innerHTML = exampleInfo.example.title
  document.getElementById('description').innerHTML = exampleInfo.example.description
  for (var id = 0; id < 2; id++) {
    document.getElementById('tab_' + exampleInfo.example.feedback[id].tab + id).checked = true
  }
})

function onFileListSelect (element) {
  var selValue = element.value
  editor.setSession(editorSessions[selValue])
  if (selValue === 'CMakeLists.txt' || selValue === 'package.xml') {
    editor.setReadOnly(true)
  } else {
    editor.setReadOnly(false)
  }
}

var runningCode = false

$('#compile').on('click', function () {
  if (runningCode) { return }
  runningCode = true

  document.getElementById('compile').style.background = '#FF8C00'
  connectedROSLIB = false
  if (!intervalActive) {
    intervalActive = true
    interval = setInterval(tryROSConnection, 2000)
  }

  for (var id = 0; id < 2; id++) {
    document.getElementById('show_terminal' + id).value = ''
    document.getElementById('show_topic' + id).value = ''
  }

  var code = {}
  for (var filename in editorSessions) {
    code[filename] = editorSessions[filename].getValue()
  }

  var json = {
    code_files: code,
    rosbridge_port: document.getElementById('rosbridge_port').value
  }

  $.post('/compile?name=' + exampleName, json, function (data, error, xhr) {
    document.getElementById('compile').style.background = '#00FF00'
    runningCode = false
  })
})
<commit_msg>Fixed all style issues.<commit_after>/*
 * Copyright (c) 2017, David Ball
 * All rights reserved.
 *
 * 3-clause BSD
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

var exampleInfo

// ROS code
var ros = new ROSLIB.Ros()
var connectedROSLIB = false
var intervalActive = false
var interval
var intervalGetROSDetails

var viewer
var markerClient

var selectedTopicName = ['undefined', 'undefined']
var selectedParamName = ['undefined', 'undefined']
var topicListener

function tryROSConnection () {
  if (connectedROSLIB === false) {
    ros.connect('ws://' + document.getElementById('host_name').value + ':' + document.getElementById('rosbridge_port').value)
  }
}

ros.on('connection', function () {
  connectedROSLIB = true
  console.log('Connected to websocket server.')
  intervalActive = false
  clearInterval(interval)
  intervalGetROSDetails = setInterval(getROSDetails, 500)

  for (var id = 0; id < 2; id++) {
    // set the defaults from the xml file
    if (exampleInfo.example.feedback[id].tab[0] === 'topics') {
      selectedTopicName[id] = exampleInfo.example.feedback[id].topic[0]
    } else if (exampleInfo.example.feedback[id].tab[0] === 'viz') {
      if (typeof markerClient !== 'undefined') {
        markerClient = null
      }

      if (typeof viewer === 'undefined') {
        // TODO: The width and height need to come from somewhere meaningful
        viewer = new ROS3D.Viewer({
          divID: 'roslib_viewer' + id,
          width: 300,
          height: 200,
          antialias: true
        })
      }

      markerClient = new ROS3D.MarkerClient({
        ros: ros,
        tfClient: new ROSLIB.TFClient({
          ros: ros,
          angularThres: 0.01,
          transThres: 0.01,
          rate: 10.0,
          fixedFrame: exampleInfo.example.feedback[id].frame[0]
        }),
        topic: exampleInfo.example.feedback[id].topic[0],
        rootObject: viewer.scene
      })
    }
  }
})

ros.on('error', function (error) {
  console.log('Error connecting to websocket server: ', error)
})

ros.on('close', function () {
  clearInterval(intervalGetROSDetails)
  console.log('Connection to websocket server closed.')
})

function setTopicSub (topicName, id) {
  if (topicName === selectedTopicName[id] && typeof topicListener !== 'undefined') {
    return
  } else {
    selectedTopicName[id] = topicName
  }

  var topicType
  if (topicName === '/rosout' || topicName === 'rosout_agg') {
    topicType = 'rosgraph_msgs/Log'
  } else if (topicName === exampleInfo.example.feedback[id].topic[0]) {
    topicType = exampleInfo.example.feedback[id].msg_type[0]
  } else {
    // hope this works.
    topicType = 'std_msgs/String'
  }

  if (typeof topicListener !== 'undefined') {
    topicListener.unsubscribe()
    topicListener = null
    document.getElementById('show_topic' + id).value = ''
  }

    // TODO: Why do I need to set the messagetype for the listener?
  topicListener = new ROSLIB.Topic({
    ros: ros,
    name: topicName,
    messageType: topicType
  })

  topicListener.subscribe(function (message) {
    // set 2 space tabs and get rid of the ""
    // message = JSON.stringify(message, null, 2).replace(/\"([^(\")"]+)\":/g, '$1:')
    message = JSON.stringify(message, null, 2).replace(/([^()"]+):/g, '$1:')
    document.getElementById('show_topic' + id).value += message + '\r'
    document.getElementById('show_topic' + id).scrollTop = document.getElementById('show_topic' + id).scrollHeight
  })
}

function onTopicListSelect (element) { // eslint-disable-line
  setTopicSub(element.value, element.id.slice(-1))
}

var paramROSLIB
function setParam (param) {
  selectedParamName = param
  if (typeof paramROSLIB !== 'undefined') {
    paramROSLIB = null
  }
  paramROSLIB = new ROSLIB.Param({
    ros: ros,
    name: selectedParamName
  })
}

function onParamListSelect (element) { // eslint-disable-line
  var selValue = element.value
  setParam(selValue)
  paramROSLIB.get(function (value) {
    document.getElementById('show_param').value = value
  })
}

/**
 * Gets and updates the list of ROS topics, nodes, services, and parameters.
 * This is repeatidly called at an interval whilst the ROS bridge is connected.
 * TODO: It would be much better if this was pushed to us when a change happens.
 * At the least we could store the callback values and check if they have changed.
 */
function getROSDetails () {
  ros.getTopics(function (topics) {
    // TODO: It's a shame this doesn't also return the message types.
    for (var id = 0; id < 2; id++) {
      var sel = document.getElementById('topic_list' + id)
      sel.options.length = 0
      for (var topic in topics) {
        // TODO: Also work out a way to remove the tf's that appear ... but only if they shouldn't!
        // This might need to become a user configurable parameter.
        if (topics[topic].indexOf('tf2_web_republisher') === -1) {
          var opt = document.createElement('option')
          opt.innerHTML = topics[topic]
          opt.value = topics[topic]
          sel.appendChild(opt)
        }
      }
      sel.value = selectedTopicName[id]
      setTopicSub(selectedTopicName[id], id)
    }
  })
  ros.getNodes(function (nodes) {
    for (var id = 0; id < 2; id++) {
      var sel = document.getElementById('node_list' + id)
      sel.options.length = 0
      for (var node in nodes) {
        if (nodes[node].indexOf('tf2_web_republisher') === -1 &&
            nodes[node].indexOf('rosbridge_websocket') === -1) {
          var opt = document.createElement('option')
          opt.innerHTML = nodes[node]
          opt.value = nodes[node]
          sel.appendChild(opt)
        }
      }
    }
  })
  ros.getServices(function (services) {
    for (var id = 0; id < 2; id++) {
      var sel = document.getElementById('service_list' + id)
      sel.options.length = 0
      for (var service in services) {
        var opt = document.createElement('option')
        opt.innerHTML = services[service]
        opt.value = services[service]
        sel.appendChild(opt)
      }
    }
  })
  ros.getParams(function (params) {
    for (var id = 0; id < 2; id++) {
      var sel = document.getElementById('param_list' + id)
      sel.options.length = 0
      for (var param in params) {
        if (params[param].indexOf('rosbridge_websocket') === -1 &&
            params[param].indexOf('rosapi') === -1) {
          var opt = document.createElement('option')
          opt.innerHTML = params[param]
          opt.value = params[param]
          sel.appendChild(opt)
        }
      }
      if (typeof selectedParamName[id] !== 'undefined') {
        sel.value = selectedParamName[id]
      }
    }
  })
}

/**
 * This function updates the terminal feedback textbox.
 */
$(document).ready(function () {
  var socket = io()
  socket.on('compile_output', function (msg) {
    for (var id = 0; id < 2; id++) {
      document.getElementById('show_terminal' + id).value += msg
      document.getElementById('show_terminal' + id).scrollTop = document.getElementById('show_terminal' + id).scrollHeight
    }
  })
})

var files = {}
var editor = ace.edit('editor')
editor.setTheme('ace/theme/chrome')
var EditSession = ace.require('ace/edit_session').EditSession
var editorSessions = {}

var exampleName = location.search.split('name=')[1]

$.get('/get_files?name=' + exampleName, function (data) {
  // parse the data into the files for the editor and the general example information
  var parsedData = JSON.parse(data)
  files = parsedData[0]
  exampleInfo = parsedData[1]

   // load each file into its own editor
  var sel = document.getElementById('file_list')
  for (var filename in files) {
    var opt = document.createElement('option')
    opt.value = filename
    opt.innerHTML = filename
    sel.appendChild(opt)
    var filenameExtension = filename.split('.').pop()
    editorSessions[filename] = new EditSession(filename)
    if (filenameExtension === 'cpp' || filenameExtension === 'c') { editorSessions[filename].setMode('ace/mode/c_cpp') } else if (filenameExtension === 'xml') { editorSessions[filename].setMode('ace/mode/xml') } else { editorSessions[filename].setMode('ace/mode/text') }
    editorSessions[filename].setValue(files[filename], -1)
  }

   // set the initial file for editing
   // TODO: Test if this is set / valid
  editor.setSession(editorSessions[exampleInfo.example.start_file])
  sel.value = exampleInfo.example.start_file

   // set other parts of the page
  document.getElementById('run_cmd').innerHTML = exampleInfo.example.run_cmd
  document.getElementById('example_title').innerHTML = exampleInfo.example.title
  document.getElementById('description').innerHTML = exampleInfo.example.description
  for (var id = 0; id < 2; id++) {
    document.getElementById('tab_' + exampleInfo.example.feedback[id].tab + id).checked = true
  }
})

function onFileListSelect (element) { // eslint-disable-line
  var selValue = element.value
  editor.setSession(editorSessions[selValue])
  if (selValue === 'CMakeLists.txt' || selValue === 'package.xml') {
    editor.setReadOnly(true)
  } else {
    editor.setReadOnly(false)
  }
}

var runningCode = false

$('#compile').on('click', function () {
  if (runningCode) { return }
  runningCode = true

  document.getElementById('compile').style.background = '#FF8C00'
  connectedROSLIB = false
  if (!intervalActive) {
    intervalActive = true
    interval = setInterval(tryROSConnection, 2000)
  }

  for (var id = 0; id < 2; id++) {
    document.getElementById('show_terminal' + id).value = ''
    document.getElementById('show_topic' + id).value = ''
  }

  var code = {}
  for (var filename in editorSessions) {
    code[filename] = editorSessions[filename].getValue()
  }

  var json = {
    code_files: code,
    rosbridge_port: document.getElementById('rosbridge_port').value
  }

  $.post('/compile?name=' + exampleName, json, function (data, error, xhr) {
    document.getElementById('compile').style.background = '#00FF00'
    runningCode = false
  })
})
<|endoftext|>


<commit_before>import bs from 'brisky-stamp'
import { toServer, sendMeta } from '../send'
import WebSocket from './websocket'
import { parse, subscribe } from 'brisky-struct'
import { serialize } from '../subscription'
import hash from 'quick-hash'
// need to get acces to fast subscribe as well

const connect = (hub, url, reconnect) => {
  const socket = new WebSocket(url)
  const id = hub.id

  hub.set({ clients: { [id]: { id: id } } }, false)
  // bit weird to make the socket different...

  const client = hub.clients[id]
  hub.set({ client }, false)
  hub.reconnect = null

  const close = () => {
    hub.socket = null
    const stamp = bs.create('disconnect', hub.id)
    hub.set({ connected: false }, stamp)
    bs.close()
    if (!socket.blockReconnect) {
      reconnect = Math.min((reconnect * 1.5) | 0, 2000)
      hub.reconnect = setTimeout(connect, reconnect, hub, url, reconnect)
    }
  }

  socket.onclose = close
  socket.onerror = () => close()

  socket.onopen = () => {
    const stamp = bs.create('connected', hub.id)
    sendMeta(hub)
    hub.set({ connected: true }, stamp)
    bs.close()
  }

  socket.onmessage = ({ data }) => {
    // const bytes = encodeURI(data).split(/%..|./).length - 1
    const stamp = bs.create('receive')
    hub.set(JSON.parse(data), stamp)
    bs.close()
  }

  hub.socket = socket
}

const sendToServer = (val, stamp, struct) => {
  const hub = struct.parent(hub => hub.url && hub)
  if (hub && bs.type(stamp) !== 'receive') { toServer(hub, val, stamp, struct) }
}

const url = (hub, val, stamp) => {
  if (val !== hub.url) {
    if (hub.socket) {
      if (hub.reconnect) {
        clearTimeout(hub.reconnect)
        hub.reconnect = null
      }
      hub.socket.blockReconnect = true
      hub.socket.close()
    }
    if (val === null) {
      // remove all props spwaned from this one
    }
    let i = -1
    hub.parent(() => { i++ })
    hub.urlIndex = i
    hub.url = val
    hub.set({ connected: false }, stamp)
    connect(hub, val, 50)
  }
}

// const clients => (val, stamp) {
//   const hub = this.cParent()
//   if (this.compute() === false && hub.upstream) {
//     // put this in clients (the file)
//     const clients = hub.clients
//     if (clients && clients.keys().length > 1) {
//       clients.each((client) => {
//         if (
//           client.val !== null &&
//           !client.socket &&
//           client.key != hub.id // eslint-disable-line
//         ) {
//           client.remove(stamp)
//         }
//       })
//     }
//   }
// }

const props = {
  reconnect: true,
  socket: true,
  urlIndex: true,
  upstreamSubscriptions: true,
  url,
  connected: {
    type: 'struct'
    // on: { data: { } } //clients switching
  }
}

// struct

const stub = () => {}

const define = {
  subscribe (subs, cb, raw) {
    console.log(' \n<<<<<<===== GO SUBSCRIBE ====>>>>>>')
    // just use the internal one here
    if (!raw) { subs = parse(subs) }

    if (this.url) {
      const parsed = serialize(this, subs)
      if (parsed) {
        const key = hash(JSON.stringify(subs))
        // why not just add them to client... so much easier
        if (!this.upstreamSubscriptions) {
          this.upstreamSubscriptions = { [key]: subs }
          sendMeta(this)
        } else if (!this.upstreamSubscriptions[key]) {
          this.upstreamSubscriptions[key] = subs
          sendMeta(this)
        }
      }
    }
    return subscribe(this, subs, cb || stub)
  }
}

const on = { data: { sendToServer } }

export { props, on, define }
<commit_msg>clean up<commit_after>import bs from 'brisky-stamp'
import { toServer, sendMeta } from '../send'
import WebSocket from './websocket'
import { parse, subscribe } from 'brisky-struct'
import { serialize } from '../subscription'
import hash from 'quick-hash'
// need to get acces to fast subscribe as well

const connect = (hub, url, reconnect) => {
  const socket = new WebSocket(url)
  const id = hub.id

  hub.set({ clients: { [id]: { id: id } } }, false)
  // bit weird to make the socket different...

  const client = hub.clients[id]
  hub.set({ client }, false)
  hub.reconnect = null

  const close = () => {
    hub.socket = null
    const stamp = bs.create('disconnect', hub.id)
    hub.set({ connected: false }, stamp)
    bs.close()
    if (!socket.blockReconnect) {
      reconnect = Math.min((reconnect * 1.5) | 0, 2000)
      hub.reconnect = setTimeout(connect, reconnect, hub, url, reconnect)
    }
  }

  socket.onclose = close
  socket.onerror = () => close()

  socket.onopen = () => {
    const stamp = bs.create('connected', hub.id)
    sendMeta(hub)
    hub.set({ connected: true }, stamp)
    bs.close()
  }

  socket.onmessage = ({ data }) => {
    // const bytes = encodeURI(data).split(/%..|./).length - 1
    hub.set(JSON.parse(data), bs.create('upstream'))
    bs.close()
  }

  hub.socket = socket
}

const sendToServer = (val, stamp, struct) => {
  const hub = struct.parent(hub => hub.url && hub)
  if (hub && bs.type(stamp) !== 'upstream') { toServer(hub, val, stamp, struct) }
}

const url = (hub, val, stamp) => {
  if (val !== hub.url) {
    if (hub.socket) {
      if (hub.reconnect) {
        clearTimeout(hub.reconnect)
        hub.reconnect = null
      }
      hub.socket.blockReconnect = true
      hub.socket.close()
    }
    if (val === null) {
      // remove all props spwaned from this one
    }
    let i = -1
    hub.parent(() => { i++ })
    hub.urlIndex = i
    hub.url = val
    hub.set({ connected: false }, stamp)
    connect(hub, val, 50)
  }
}

// const clients => (val, stamp) {
//   const hub = this.cParent()
//   if (this.compute() === false && hub.upstream) {
//     // put this in clients (the file)
//     const clients = hub.clients
//     if (clients && clients.keys().length > 1) {
//       clients.each((client) => {
//         if (
//           client.val !== null &&
//           !client.socket &&
//           client.key != hub.id // eslint-disable-line
//         ) {
//           client.remove(stamp)
//         }
//       })
//     }
//   }
// }

const props = {
  reconnect: true,
  socket: true,
  urlIndex: true,
  upstreamSubscriptions: true,
  url,
  connected: {
    type: 'struct'
    // on: { data: { } } //clients switching
  }
}

// struct

const stub = () => {}

const define = {
  subscribe (subs, cb, raw) {
    console.log(' \n<<<<<<===== GO SUBSCRIBE ====>>>>>>')
    // just use the internal one here
    if (!raw) { subs = parse(subs) }

    if (this.url) {
      const parsed = serialize(this, subs)
      if (parsed) {
        const key = hash(JSON.stringify(subs))
        // why not just add them to client... so much easier
        if (!this.upstreamSubscriptions) {
          this.upstreamSubscriptions = { [key]: subs }
          sendMeta(this)
        } else if (!this.upstreamSubscriptions[key]) {
          this.upstreamSubscriptions[key] = subs
          sendMeta(this)
        }
      }
    }
    return subscribe(this, subs, cb || stub)
  }
}

const on = { data: { sendToServer } }

export { props, on, define }
<|endoftext|>


<commit_before>"use strict";

var helpers = require('../helpers'),
    Request = require('../request'),
    printer = require('../printer'),
    fetch = require('node-fetch'),
    chalk = require('chalk'),
    qs = require('qs'),
    fs = require('fs'),
    FormData = require('form-data'),
    Promise = require('es6-promise').Promise;

//TODO handle case sensitivity in yml keys
//TODO handle missing yml keys/values
//TODO pass in config as arg to exec from commmand?
class RequestCommand {

  constructor(program, config) {
      this.program = program;
      this.config = config;
      this.queue = [];
      this.stats = {
        statusCodes: {},
      };
  }

  exec() {
      this.queue = this._createRequestQueue();
      this._executeRequests().then(() => {
          if (this.program.info) {
            console.log(chalk.white.underline('stats:'));
            console.log(JSON.stringify(this.stats, null, 4));
          }
      });
  }

  _createRequestQueue() {
      var env = this.config.envs[this.program.env] || this.config.envs.default;
      return this.program.args.reduce((queue, curr) => {
          if (!this.config.requests[curr]) {
              console.log(`[info] request \"${curr}\" not found in requests.yml. Skipping.`);
              return queue;
          };
          queue.push(new Request(curr, this.config.requests[curr], { env: env, query: this.program.query }));
          return queue;
      }, []);
  }

  _executeRequests() {
      if  (this.program.async) {
          return this._executeRequestsAsync();
      } else {
          return this._executeRequestsSync();
      }
  }

  _executeRequestsSync() {
      return this.queue.reduce((sequence, request) => {
          return sequence.then(() => {
              return fetch(request.url, request.config);
          }).then((resp) =>{
              return this._processResponse({ url: request.url, headers: request.config.headers, body: request.config.body }, resp);
          }).catch((e) =>{
              console.log(e);
          });
      }, Promise.resolve());
  }

  _executeRequestsAsync() {

      var requestPromises = [];

      this.queue.reduce((requests, request) => {
          var requestPromise = fetch(request.url, request.config)
              .then((resp) => this._processResponse({ url: request.url, headers: request.config.headers, body: request.config.body }, resp))
              .catch((e) => console.log(e));

          requestPromises.push(requestPromise);
      }, requestPromises);

      return Promise.all(requestPromises);
  }

  _processResponse(request, resp) {

      this._updateStats(resp.status);

      if (resp.headers.get('content-type') === 'application/json') {
          return resp.json().then((json) => {
              printer.print(this.program, request, resp, json);
          });
      }
      return resp.text().then((text) => {
          printer.print(this.program, request, resp, text);
      });
  }

  _updateStats(status) {
    this.stats.statusCodes[status] ? this.stats.statusCodes[status]++ :
        this.stats.statusCodes[status] = 1;
  }
}

module.exports = RequestCommand;
<commit_msg>formatting<commit_after>"use strict";

var helpers = require('../helpers'),
    Request = require('../request'),
    printer = require('../printer'),
    fetch = require('node-fetch'),
    chalk = require('chalk'),
    qs = require('qs'),
    fs = require('fs'),
    FormData = require('form-data'),
    Promise = require('es6-promise').Promise;

//TODO handle case sensitivity in yml keys
//TODO handle missing yml keys/values
//TODO pass in config as arg to exec from commmand?
class RequestCommand {

  constructor(program, config) {
      this.program = program;
      this.config = config;
      this.queue = [];
      this.stats = {
        statusCodes: {},
      };
  }

  exec() {

      this.queue = this._createRequestQueue();

      this._executeRequests().then(() => {

          if (this.program.info) {
            console.log(chalk.white.underline('stats:'));
            console.log(JSON.stringify(this.stats, null, 4));
          }

      });
  }

  _createRequestQueue() {

      var env = this.config.envs[this.program.env] || this.config.envs.default;

      return this.program.args.reduce((queue, curr) => {

          if (!this.config.requests[curr]) {
              console.log(`[info] request \"${curr}\" not found in requests.yml. Skipping.`);
              return queue;
          };

          queue.push(
            new Request(
              curr,
              this.config.requests[curr],
              { env: env, query: this.program.query })
          );

          return queue;

      }, []);
  }

  _executeRequests() {
      if  (this.program.async) {
          return this._executeRequestsAsync();
      } else {
          return this._executeRequestsSync();
      }
  }

  _executeRequestsSync() {

      return this.queue.reduce((sequence, request) => {

          return sequence.then(() => {

              return fetch(request.url, request.config);

          }).then((resp) => {

              return this._processResponse(
                { url: request.url,
                  headers: request.config.headers,
                  body: request.config.body },
                resp
              );

          }).catch((e) => console.log(e));

      }, Promise.resolve());
  }

  _executeRequestsAsync() {

      var requestPromises = [];

      this.queue.reduce((requests, request) => {

          var requestPromise = fetch(request.url, request.config)
              .then((resp) => {

                  this._processResponse(
                    { url: request.url,
                      headers: request.config.headers,
                      body: request.config.body },
                    resp)

              }).catch((e) => console.log(e));

          requestPromises.push(requestPromise);

      }, requestPromises);

      return Promise.all(requestPromises);
  }

  _processResponse(request, resp) {

      this._updateStats(resp.status);

      if (resp.headers.get('content-type') === 'application/json') {
          return resp.json().then((json) => {
              printer.print(this.program, request, resp, json);
          });
      }
      return resp.text().then((text) => {
          printer.print(this.program, request, resp, text);
      });
  }

  _updateStats(status) {
    this.stats.statusCodes[status] ? this.stats.statusCodes[status]++ :
        this.stats.statusCodes[status] = 1;
  }
}

module.exports = RequestCommand;
<|endoftext|>


<commit_before>'use strict';
exports.info = {
  desc: 'Manage channels to post welcome/part messages to.',
  usage: '<add | remove | list>',
  aliases: [],
};

const logger = require('winston');
const utils = require('../utilities');

// <add | remove | list> channels to post join / part messages to
exports.run = (client, msg, params = []) => {
  if (msg.channel.type === 'dm') {
    msg.reply('Unable to use **welcome** command from DM channels.');
    return;
  }
  // Lowercase all the parameters
  params.forEach(x => x.toLowerCase());
  if (params[0] === 'list') {
    // Get list of all welcome messages for the msg guild only
    client.mongo.welcomeChannels.find({ server_id: msg.guild.id })
      .then(channels => {
        logger.debug('welcome channels list', JSON.stringify(channels, null, 2));
        let str = 'Welcome messages are posted in channel(s):\n\n';
        const names = channels.map(c => {
          const channel = msg.guild.channels.get(c.channel_id);
          if (channel) {
            return `*#${channel.name}*`;
          } else {
            return '';
          }
        });
        if (names.length === 0) {
          msg.channel.sendMessage('No channels on this server currently post a welcome message.');
          utils.finish(client, msg, exports.name);
          return;
        }
        str += names.join('\n');
        msg.channel.sendMessage(str);
        utils.finish(client, msg, exports.name);
      })
      .catch(err => {
        logger.error('Error getting welcome channels from mongoDB', err);
      });
  } else {
    // Search database for existing entry
    client.mongo.welcomeChannels.find({ channel_id: msg.channel.id })
      .then(channels => {
        logger.debug('welcome channels match results', JSON.stringify(channels, null, 2));
        switch (params[0]) {
          case 'add':
            if (channels.length === 0) {
              const entry = client.mongo.welcomeChannels({
                server_id: msg.guild.id,
                channel_id: msg.channel.id,
              });
              entry.save(e => {
                if (e) {
                  logger.error('Error Saving welcome channel to the mongoDB', e);
                  return;
                }
                msg.channel.sendMessage('This channel will now post welcome messages.');
                utils.finish(client, msg, exports.name);
              });
            } else {
              msg.channel.sendMessage('This channel already posts welcome messages.');
            }
            utils.finish(client, msg, exports.name);
            break;
          case 'remove':
            if (channels.length === 0) {
              msg.channel.sendMessage('This channel doesn\'t currently post welcome messages.');
              utils.finish(client, msg, exports.name);
              return;
            }
            client.mongo.welcomeChannels.remove({ _id: channels[0]._id }, e => {
              if (e) {
                logger.error('Error removing welcome channel from the mongoDB', e);
                return;
              }
              msg.channel.sendMessage('This channel will no longer post welcome messages.');
              utils.finish(client, msg, exports.name);
            });
            break;
          default:
            msg.channel.sendMessage(`\`\`${client.config.prefix}welcome < add | remove | list >\`\``);
            break;
        }
      })
      .catch(err => {
        logger.error('Error getting welcome channels from mongoDB', err);
      });
  }
};
<commit_msg>Use the msg.prefix we added so it's guild specific<commit_after>'use strict';
exports.info = {
  desc: 'Manage channels to post welcome/part messages to.',
  usage: '<add | remove | list>',
  aliases: [],
};

const logger = require('winston');
const utils = require('../utilities');

// <add | remove | list> channels to post join / part messages to
exports.run = (client, msg, params = []) => {
  if (msg.channel.type === 'dm') {
    msg.reply('Unable to use **welcome** command from DM channels.');
    return;
  }
  // Lowercase all the parameters
  params.forEach(x => x.toLowerCase());
  if (params[0] === 'list') {
    // Get list of all welcome messages for the msg guild only
    client.mongo.welcomeChannels.find({ server_id: msg.guild.id })
      .then(channels => {
        logger.debug('welcome channels list', JSON.stringify(channels, null, 2));
        let str = 'Welcome messages are posted in channel(s):\n\n';
        const names = channels.map(c => {
          const channel = msg.guild.channels.get(c.channel_id);
          if (channel) {
            return `*#${channel.name}*`;
          } else {
            return '';
          }
        });
        if (names.length === 0) {
          msg.channel.sendMessage('No channels on this server currently post a welcome message.');
          utils.finish(client, msg, exports.name);
          return;
        }
        str += names.join('\n');
        msg.channel.sendMessage(str);
        utils.finish(client, msg, exports.name);
      })
      .catch(err => {
        logger.error('Error getting welcome channels from mongoDB', err);
      });
  } else {
    // Search database for existing entry
    client.mongo.welcomeChannels.find({ channel_id: msg.channel.id })
      .then(channels => {
        logger.debug('welcome channels match results', JSON.stringify(channels, null, 2));
        switch (params[0]) {
          case 'add':
            if (channels.length === 0) {
              const entry = client.mongo.welcomeChannels({
                server_id: msg.guild.id,
                channel_id: msg.channel.id,
              });
              entry.save(e => {
                if (e) {
                  logger.error('Error Saving welcome channel to the mongoDB', e);
                  return;
                }
                msg.channel.sendMessage('This channel will now post welcome messages.');
                utils.finish(client, msg, exports.name);
              });
            } else {
              msg.channel.sendMessage('This channel already posts welcome messages.');
            }
            utils.finish(client, msg, exports.name);
            break;
          case 'remove':
            if (channels.length === 0) {
              msg.channel.sendMessage('This channel doesn\'t currently post welcome messages.');
              utils.finish(client, msg, exports.name);
              return;
            }
            client.mongo.welcomeChannels.remove({ _id: channels[0]._id }, e => {
              if (e) {
                logger.error('Error removing welcome channel from the mongoDB', e);
                return;
              }
              msg.channel.sendMessage('This channel will no longer post welcome messages.');
              utils.finish(client, msg, exports.name);
            });
            break;
          default:
            msg.channel.sendMessage(`\`\`${msg.prefix}welcome < add | remove | list >\`\``);
            break;
        }
      })
      .catch(err => {
        logger.error('Error getting welcome channels from mongoDB', err);
      });
  }
};
<|endoftext|>


<commit_before>var fs     = require('fs');
var path   = require('path');
var mkpath = require('mkpath');
// We don't want to include our own version of CoffeeScript since we don't know
// what version the parent relies on
var coffee;
try {
  coffee = require('coffee-script');
  // CoffeeScript 1.7 support
  if (typeof coffee.register === 'function') {
    coffee.register();
  }
} catch (e) {
  // No coffee-script installed in the project; warn them and stop
  process.stderr.write("coffee-cache: No coffee-script package found.\n");
  return;
}

// Directory to store compiled source
var cacheDir = process.env['COFFEE_CACHE_DIR'] || '.coffee';
// Storing coffee's require extension for backup use
var coffeeExtension = require.extensions['.coffee'];

// Set up an extension map for .coffee files -- we are completely overriding
// CoffeeScript's since it only returns the compiled module.
require.extensions['.coffee'] = function(module, filename) {
  // First, convert the filename to something more digestible and use our cache
  // folder
  var cachePath = path.join(cacheDir, path.relative('.', filename)).replace(/\.coffee$/, '.js');
  var mapPath = path.resolve(cachePath.replace(/\.js$/, '.map'));
  var content;

  // Try and stat the files for their last modified time
  try {
    var sourceTime = fs.statSync(filename).mtime;
    var cacheTime = fs.statSync(cachePath).mtime;
    if (cacheTime > sourceTime) {
      // We can return the cached version
      content = fs.readFileSync(cachePath, 'utf8');
    }
  } catch (err) {
    // If the cached file was not created yet, this will fail, and that is okay
  }

  // If we don't have the content, we need to compile ourselves
  if (!content) {
    try {
      // Read from disk and then compile
      var compiled = coffee.compile(fs.readFileSync(filename, 'utf8'), {
        filename: filename,
        sourceMap: true
      });
      content = compiled.js;

      // Since we don't know which version of CoffeeScript we have, make sure
      // we handle the older versions that return just the compiled version.
      if (content == null)
        content = compiled;

      // Try writing to cache
      mkpath.sync(path.dirname(cachePath));
      fs.writeFileSync(cachePath, content, 'utf8');
      if (mapPath)
        fs.writeFileSync(mapPath, compiled.v3SourceMap, 'utf8');
    } catch (err) {
      // Let's fail silently and use coffee's require if we need to
      if (!content)
        return coffeeExtension.apply(this, arguments);
    }
  }

  return module._compile(content, filename);
};

// Export settings
module.exports = {
  setCacheDir: function(dir){
    cacheDir = dir;
    return this;
  }
};
<commit_msg>Added ability to specifiy root directory<commit_after>var fs     = require('fs');
var path   = require('path');
var mkpath = require('mkpath');
// We don't want to include our own version of CoffeeScript since we don't know
// what version the parent relies on
var coffee;
try {
  coffee = require('coffee-script');
  // CoffeeScript 1.7 support
  if (typeof coffee.register === 'function') {
    coffee.register();
  }
} catch (e) {
  // No coffee-script installed in the project; warn them and stop
  process.stderr.write("coffee-cache: No coffee-script package found.\n");
  return;
}

// Directory to store compiled source
var cacheDir = process.env['COFFEE_CACHE_DIR'] || '.coffee';

// Root directory of project - use __dirname by default
var rootDir = process.env['COFFEE_ROOT_DIR'] || '.';

// Storing coffee's require extension for backup use
var coffeeExtension = require.extensions['.coffee'];

// Set up an extension map for .coffee files -- we are completely overriding
// CoffeeScript's since it only returns the compiled module.
require.extensions['.coffee'] = function(module, filename) {
  // First, convert the filename to something more digestible and use our cache
  // folder
  var cachePath = path.join(cacheDir, path.relative(rootDir, filename)).replace(/\.coffee$/, '.js');
  var mapPath = path.resolve(cachePath.replace(/\.js$/, '.map'));
  var content;

  // Try and stat the files for their last modified time
  try {
    var sourceTime = fs.statSync(filename).mtime;
    var cacheTime = fs.statSync(cachePath).mtime;
    if (cacheTime > sourceTime) {
      // We can return the cached version
      content = fs.readFileSync(cachePath, 'utf8');
    }
  } catch (err) {
    // If the cached file was not created yet, this will fail, and that is okay
  }

  // If we don't have the content, we need to compile ourselves
  if (!content) {
    try {
      // Read from disk and then compile
      var compiled = coffee.compile(fs.readFileSync(filename, 'utf8'), {
        filename: filename,
        sourceMap: true
      });
      content = compiled.js;

      // Since we don't know which version of CoffeeScript we have, make sure
      // we handle the older versions that return just the compiled version.
      if (content == null)
        content = compiled;

      // Try writing to cache
      mkpath.sync(path.dirname(cachePath));
      fs.writeFileSync(cachePath, content, 'utf8');
      if (mapPath)
        fs.writeFileSync(mapPath, compiled.v3SourceMap, 'utf8');
    } catch (err) {
      // Let's fail silently and use coffee's require if we need to
      if (!content)
        return coffeeExtension.apply(this, arguments);
    }
  }

  return module._compile(content, filename);
};

// Export settings
module.exports = {
  setCacheDir: function(dir, root){
    if(root) {
      rootDir = root;
    }

    cacheDir = dir;
    return this;
  }
};
<|endoftext|>


<commit_before>// Copyright 2014 Flávio Ribeiro <flavio@bem.tv>.
// All rights reserved.
// Use of this source code is governed by a Apache
// license that can be found in the LICENSE file.

var BaseObject = require('base_object');
var Storage = require('./storage');
var UploadHandler = require('./upload_handler')
var log = require('./log');

class Peer extends BaseObject {
  initialize(params) {
    this.storage = Storage.getInstance()
    this.ident = params.ident
    this.swarm = params.swarm
    this.dataChannel = params.dataChannel
    this.dataChannel.on("data", (data) => this.messageReceived(data))
    this.uploadHandler = UploadHandler.getInstance()
    this.score = 1000
    this.sendPing()
  }

  sendPing() {
    this.pingSentTime = Date.now()
    this.dataChannel.send("ping$$")
  }

  sendPong() {
    this.dataChannel.send("pong$$")
  }

  calculateRTT() {
    this.rtt = Date.now() - this.pingSentTime
    log.debug(this.ident + ': ping?pong! rtt: ' + this.rtt)
  }

  send(command, resource, content='') {
    var message = this.mountMessage(command, resource, content)
    this.dataChannel.send(message)
  }

  sendSatisfy(resource) {
    if (this.uploadHandler.getSlot(this.ident)) {
      this.send('satisfy', resource, this.storage.getItem(resource))
      this.swarm.chunksSent += 1
    } else {
      log.warn("cannot send satisfy, no upload slot available")
      this.send("busy", resource)
    }
  }

  interestedReceived(resource) {
    if (this.storage.contain(resource) && this.uploadHandler.getSlot(this.ident)) {
      this.send("contain", resource)
    } else {
      this.send("busy", resource)
    }
  }

  messageReceived(data) {
    this.processMessage(data)
  }

  processMessage(data) {
    var [command, resource, content] = data.split("$")
    switch (command) {
      case 'interested':
        this.interestedReceived(resource)
        break
      case 'contain':
        this.swarm.containReceived(this, resource)
        break
      case 'request':
        this.sendSatisfy(resource)
        break
      case 'choke':
        this.swarm.chokeReceived(resource)
        break
      case 'satisfy':
        log.debug('received _satisfy_')
        this.swarm.satisfyReceived(this, resource, content)
        break
      case 'busy':
        this.swarm.busyReceived(this)
      case 'ping':
        this.sendPong()
        break
      case 'pong':
        this.calculateRTT()
        break
    }
  }

  mountMessage(command, resource, content) {
    var msg = command + "$" + resource + "$"
    if (content) {
      msg = msg + content
    }
    return msg
  }
}

module.exports = Peer
<commit_msg>peer: fix switch/case and interestedReceived function<commit_after>// Copyright 2014 Flávio Ribeiro <flavio@bem.tv>.
// All rights reserved.
// Use of this source code is governed by a Apache
// license that can be found in the LICENSE file.

var BaseObject = require('base_object');
var Storage = require('./storage');
var UploadHandler = require('./upload_handler')
var log = require('./log');

class Peer extends BaseObject {
  initialize(params) {
    this.storage = Storage.getInstance()
    this.ident = params.ident
    this.swarm = params.swarm
    this.dataChannel = params.dataChannel
    this.dataChannel.on("data", (data) => this.messageReceived(data))
    this.uploadHandler = UploadHandler.getInstance()
    this.score = 1000
    this.sendPing()
  }

  sendPing() {
    this.pingSentTime = Date.now()
    this.dataChannel.send("ping$$")
  }

  sendPong() {
    this.dataChannel.send("pong$$")
  }

  calculateRTT() {
    this.rtt = Date.now() - this.pingSentTime
    log.debug(this.ident + ': ping?pong! rtt: ' + this.rtt)
  }

  send(command, resource, content='') {
    var message = this.mountMessage(command, resource, content)
    this.dataChannel.send(message)
  }

  sendSatisfy(resource) {
    if (this.uploadHandler.getSlot(this.ident)) {
      this.send('satisfy', resource, this.storage.getItem(resource))
      this.swarm.chunksSent += 1
    } else {
      log.warn("cannot send satisfy, no upload slot available")
      this.send("busy", resource)
    }
  }

  interestedReceived(resource) {
    if (this.storage.contain(resource)) {
      if (this.uploadHandler.getSlot(this.ident)) {
        this.send('contain', resource)
      } else {
        this.send('busy', resource)
      }
    } else {
      this.send("choke", resource)
    }
  }

  messageReceived(data) {
    this.processMessage(data)
  }

  processMessage(data) {
    var [command, resource, content] = data.split("$")
    switch (command) {
      case 'interested':
        this.interestedReceived(resource)
        break
      case 'contain':
        this.swarm.containReceived(this, resource)
        break
      case 'request':
        this.sendSatisfy(resource)
        break
      case 'choke':
        this.swarm.chokeReceived(resource)
        break
      case 'satisfy':
        log.debug('received _satisfy_')
        this.swarm.satisfyReceived(this, resource, content)
        break
      case 'busy':
        this.swarm.busyReceived(this)
        break
      case 'ping':
        this.sendPong()
        break
      case 'pong':
        this.calculateRTT()
        break
    }
  }

  mountMessage(command, resource, content) {
    var msg = command + "$" + resource + "$"
    if (content) {
      msg = msg + content
    }
    return msg
  }
}

module.exports = Peer
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs');
var crypto = require('crypto');

var externalRx = /^(\s*[a-z0-9\-]+:)?\/\//i;
var absRx = /^\s*\//;
var queryAndHashRx = /[\?\#].*$/;
var slashRx = /\\/g;

var textFileExt = ['.css', '.js', '.json', '.tmpl', '.txt', '.svg', '.html'];
var textType = ['script', 'style', 'template', 'json', 'l10n', 'xml', 'svg', 'text'];

var typeByExt = {
  '.js': 'script',
  '.css': 'style',
  '.tmpl': 'template',
  '.html': 'html',
  '.json': 'json',
  '.l10n': 'json',
  '.xml': 'xml',
  '.svg': 'svg',
  '.cur': 'image',
  '.ico': 'image',
  '.bmp': 'image',
  '.gif': 'image',
  '.png': 'image',
  '.jpg': 'image',
  '.jpeg': 'image',
  '.tiff': 'image',
  '.ttf': 'font',
  '.woff': 'font',
  '.eot': 'font'
};

var typeNotFoundHandler = {
  '.js': '/* Javascript file {filename} not found */',
  '.css': '/* CSS file {filename} not found */'
};

function getFileContentOnFailure(filename){
  return (typeNotFoundHandler[path.extname(filename)] || '').replace(/\{filename\}/, filename);
}

function unixpath(filename){
  return path.normalize(filename).replace(slashRx, '/').replace(/^[a-z]\:/i, '');
}

function abspath(baseURI, filename){
  return unixpath(path.resolve(baseURI, filename.replace(queryAndHashRx, '')).replace(/^[a-z]\:/i, ''));
}

function isExternal(uri){
  return externalRx.test(uri);
}

function getRef(seed, type){
  seed[type] = (type in seed ? seed[type] : -1) + 1;
  return seed[type].toString(36) + type;
}

/**
 * @class File
 */

function File(manager, cfg){
  this.manager = manager;
  this.linkTo = [];
  this.linkBack = [];

  for (var key in cfg)
    this[key] = cfg[key];

  if (!this.type)
    this.type = manager.typeByExt[this.ext] || typeByExt[this.ext] || 'unknown';

  if (!this.filename)
  {
    var type = (this.generated ? 'generated-' : '') + this.type;
    manager.inline[type] = (manager.inline[type] || 0) + 1;
    this.inlineId = '[' + (this.inline ? 'inline ' : '') + type + '#' + manager.inline[type] + ']';
  }
};

File.prototype = {
  resolve: function(filename){
    if (isExternal(filename))
      return filename;

    // remove everything after ? (query string) or # (hash)
    filename = filename.replace(queryAndHashRx, '');

    return unixpath(path.resolve(this.baseURI, filename));
  },

  // input filename
  get dirname(){
    return this.filename
      ? path.dirname(this.filename)
      : '';
  },
  get basename(){
    return this.filename
      ? path.basename(this.filename)
      : '';
  },
  get name(){
    return this.filename
      ? path.basename(this.filename, path.extname(this.filename))
      : '';
  },
  get ext(){
    return this.filename
      ? path.extname(this.filename)
      : '';
  },
  get relpath(){
    return this.filename
      ? unixpath(path.relative(this.manager.baseURI, this.filename))
      : this.sourceFilename || this.inlineId;
  },
  get reldir(){
    return this.filename
      ? path.dirname(this.relpath)
      : this.sourceFilename || this.inlineId;
  },
  get fsFilename(){
    return this.filename
      ? this.manager.getFSFilename(this.filename)
      : this.sourceFilename || this.inlineId;
  },

  // input baseURI
  get baseURI(){
    if (!this.baseURI_)
      this.baseURI_ = unixpath(this.filename ? path.dirname(this.filename) + '/' : '');
    return this.baseURI_;
  },
  set baseURI(uri){
    if (!this.filename)
      this.baseURI_ = unixpath(path.resolve(this.manager.baseURI, uri) + '/');
  },

  // output filename
  get outputFilename(){
    return this.outputFilename_;
  },
  set outputFilename(filename){
    this.outputFilename_ = unixpath(filename);
    this.inline = false;
  },
  get relOutputFilename(){
    return this.outputFilename || ('outputContent' in this ? this.inlineId : '[no output]');
  },

  // refs
  get jsRef(){
    if (!this.jsRef_)
      this.jsRef_ = this.manager.getRef(this.ext);
    return this.jsRef_;
  },
  set jsRef(ref){
    this.jsRef_ = ref;
  },

  // links
  link: function(file, token){
    var link = [file, token];
    token = token || null;

    this.linkTo.push(link);
    this.manager.links.push([this, file]);
    file.linkBack.push([this, token]);

    if (token)
    {
      if (!token.links)
        token.links = [];
      token.links.push(link);

      if (file.warn)
      {
        var loc = token.loc || '';
        var warn = {
          fatal: true,
          loc: loc,
          file: this.filename || this.relpath || loc.replace(/:\d+:\d+$/, ''),
          message: file.warn.message
        };

        if (!this.manager.flow.hasWarn(warn))
          this.manager.flow.warn(warn);
      }
    }
  },
  linkFrom: function(file){
    file.link(this);
  },
  unlink: function(file, token){
    token = token || null;

    for (var i = this.linkTo.length - 1; i >= 0; i--)
    {
      var link = this.linkTo[i];
      if (link[0] === file && link[1] === token)
        this.linkTo.splice(i, 1);
    }

    for (var i = this.linkBack.length - 1; i >= 0; i--)
    {
      var link = this.linkBack[i];
      if (link[0] === file && link[1] === token)
        this.linkBack.splice(i, 1);
    }
  },

  hasLinkTo: function(file){
    return this.linkTo.some(function(link){
      return link[0] == file;
    });
  },
  hasLinkFrom: function(file){
    return file.hasLinkTo(this);
  },
  hasLinkType: function(type){
    return this.linkBack.some(function(link){
      return link[0].type == type;
    });
  },

  // misc
  get digest(){
    if (!this.digest_)
    {
      var hash = crypto.createHash('md5');
      hash.update(this.outputContent || this.content || '');
      this.digest_ = hash.digest('base64')
        // remove trailing == which always appear on md5 digest, save 2 bytes
        .replace(/=+$/, '')
        // make digest web safe
        .replace(/\//g, '_')
        .replace(/\+/g, '-');
    }

    return this.digest_;
  },
  get encoding(){
    return textType.indexOf(this.type) == -1 && textFileExt.indexOf(this.ext) == -1 ? 'binary' : 'utf-8';
  },

  get hostFilename(){
    return this.embed ? this.embed.file : this.filename;
  },
  location: function(loc){
    if (loc)
    {
      if (this.inline && this.embed)
        loc = this.embed.start
          ? [
              this.embed.file,
              this.embed.start.line + loc.line,
              (loc.line ? 0 : this.embed.start.column) + loc.column
            ]
          : [
              this.embed.file
            ];
      else
        loc = [
          this.filename,
          loc.line,
          loc.column
        ];

      return loc.join(':');
    }
  }
};

/**
 * @class FileManager
 */

var FileManager = function(baseURI, relBaseURI, console, flow){
  this.baseURI = abspath('/', relBaseURI);
  this.fsBaseURI = baseURI;
  this.console = console;
  this.preprocess = {};
  this.typeByExt = {};
  this.refSeed = {};
  this.inline = {};
  this.flow = flow;

  this.map = {};
  this.queue = [];
  this.links = [];
  this.warns = [];

  this.readInfo = [];
};

FileManager.prototype = {
  resolve: function(filename, baseURI){
    return abspath(abspath(baseURI || '.', this.baseURI), filename);
  },

  relative: function(filename){
    return abspath('/', path.relative(this.fsBaseURI, filename));
  },

 /**
  * Returns absolute filesystem filename.
  * @param {string} filename
  * @return {string}
  */
  getFSFilename: function(filename){
    return path.resolve(this.fsBaseURI, filename.replace(/^[\\\/]/, ''));
  },

 /**
  * Generate reference for ext.
  * @param {string} ext
  * @return {string}
  */
  getRef: function(ext){
    return getRef(this.refSeed, ext || '.inline');
  },

 /**
  * Get reference for File by filename.
  * @param {string} filename Path to file.
  * @returns {File} Returns file if exists.
  */
  get: function(filename){
    return this.map[abspath(this.baseURI, filename)];
  },

  read: function(filename){
    filename = abspath(this.baseURI, filename);

    var fsFilename = this.getFSFilename(filename);
    var file = this.map[filename];
    var content = '';

    if (file)
      return file.content;

    if (fs.existsSync(fsFilename) && fs.statSync(fsFilename).isFile())
    {
      file = new File(this, { filename: filename });
      content = fs.readFileSync(fsFilename, file.encoding);

      // collect data to produce content digest
      this.readInfo.push({
        filename: filename,
        encoding: file.encoding,
        content: content
      });

      // preprocessing by extension
      var preprocessors = this.preprocess[file.ext] || [];
      for (var i = 0, processor; processor = preprocessors[i]; i++)
        content = processor(content, file, this.fsBaseURI, this.console);

      // preprocessing by type
      var preprocessors = this.preprocess[file.type] || [];
      for (var i = 0, processor; processor = preprocessors[i]; i++)
        content = processor(content, file, this.fsBaseURI, this.console);
    }
    else
    {
      if (this.contextFile_)
      {
        var warn = {
          fatal: true,
          file: this.contextFile_,
          message: fs.existsSync(fsFilename)
            ? '`' + filename + '` is not a file'
            : 'File `' + filename + '` not found'
        };
        this.warns.push(warn);
        this.flow.warn(warn);
      }
    }

    return content;
  },

 /**
  * Create new file or return existing. It can returns undefined if filename is external reference.
  * @param {object} data Config object to create new file.
  * @return {File|undefined}
  *
  * TODO: extend file with ?, if it already exists
  * TODO: create file with uri, if it's external?
  */
  add: function(data){
    function getLocation(initiator){
      if (!initiator)
        return null;

      if (initiator.loc)
        return initiator.loc;

      var result = initiator.file.filename;
      if (initiator.start)
        result += ':' + initiator.start.line + ':' + initiator.start.column;
      return result;
    }

    var initiator = data.initiator;
    var file;

    delete data.initiator;

    if (!data.filename)
    {
      file = new File(this, data);
      this.console.log('[+] ' + file.relpath + ' (' + file.type + (file.generatedFrom ? ', source: ' + file.generatedFrom : '') + ')');
    }
    else
    {
      // ignore references for external resources
      if (isExternal(data.filename))
      {
        // external resource
        this.console.log('[i] External resource `' + data.filename + '` ignored');
        return;
      }

      var rawFilename = data.filename;
      var filename = abspath(this.baseURI, data.filename);
                     // remove everything after ? (query string) or # (hash)
                     // and normalize

      if (this.map[filename]) // ignore duplicates
      {
        this.console.log('[ ] ' + unixpath(path.relative(this.baseURI, filename)) + ' (already in queue)');
        return this.map[filename];
      }

      // create file
      data.filename = filename;
      file = new File(this, data);

      // read content
      var fsFilename = this.getFSFilename(filename);
      if (fs.existsSync(fsFilename))
      {
        if (fs.statSync(fsFilename).isFile())
        {
          this.console.start('[+] ' + filename + ' (' + file.type + ')');

          file.content = this.read(filename);

          this.console.end();
        }
        else
        {
          file.warn = '`' + filename + '` is not a file';
        }
      }
      else
      {
        file.warn = 'File `' + filename + '` not found';
      }

      if (file.warn)
      {
        file.warn = {
          fatal: true,
          file: initiator ? initiator.file.filename : file.relpath,
          message: file.warn + (initiator && initiator.token ? ': ' + initiator.token : ''),
          loc: getLocation(initiator)
        };
        this.warns.push(file.warn);
        this.flow.warn(file.warn);
        file.content = getFileContentOnFailure(filename);
      }

      this.map[filename] = file;
    }

    this.queue.push(file);

    return file;
  },

 /**
  * Remove a file from manager and break all links between files.
  * @param {File|string} fileRef File name or File instance to be removed.
  */
  remove: function(fileRef){
    var filename;
    var file;

    if (fileRef instanceof File)
    {
      file = fileRef;
      filename = file.filename;
    }
    else
    {
      filename = abspath(this.baseURI, fileRef);
      file = this.map[filename];

      if (!file)
      {
        this.flow.warn({
          file: filename,
          message: 'File `' + fileRef + '` not found in map'
        });
        return;
      }
    }

    // remove links
    for (var i = file.linkTo.length, linkTo; linkTo = file.linkTo[i]; i--)
      file.unlink(linkTo);

    for (var i = file.linkBack.length, linkBack; linkBack = file.linkBack[i]; i--)
      linkBack.unlink(file);

    // remove from queue
    var index = this.queue.indexOf(file);

    if (index != -1)
      this.queue.splice(index, 1);

    // remove from map
    if (filename)
      delete this.map[filename];
  },

 /**
  * Remove all files
  */
  clear: function(){
    this.queue.slice().forEach(this.remove, this);
  },

  mkdir: function(dirpath){
    if (!fs.existsSync(dirpath))
    {
      this.console.log('Create folder ' + dirpath);
      fs.mkdirSync(dirpath);
    }
  }
};

//
// export
//

module.exports = FileManager;
<commit_msg>refactoring of FileManager#read<commit_after>var path = require('path');
var fs = require('fs');
var crypto = require('crypto');

var externalRx = /^(\s*[a-z0-9\-]+:)?\/\//i;
var absRx = /^\s*\//;
var queryAndHashRx = /[\?\#].*$/;
var slashRx = /\\/g;

var textFileExt = ['.css', '.js', '.json', '.tmpl', '.txt', '.svg', '.html'];
var textType = ['script', 'style', 'template', 'json', 'l10n', 'xml', 'svg', 'text'];

var typeByExt = {
  '.js': 'script',
  '.css': 'style',
  '.tmpl': 'template',
  '.html': 'html',
  '.json': 'json',
  '.l10n': 'json',
  '.xml': 'xml',
  '.svg': 'svg',
  '.cur': 'image',
  '.ico': 'image',
  '.bmp': 'image',
  '.gif': 'image',
  '.png': 'image',
  '.jpg': 'image',
  '.jpeg': 'image',
  '.tiff': 'image',
  '.ttf': 'font',
  '.woff': 'font',
  '.eot': 'font'
};

var typeNotFoundHandler = {
  '.js': '/* Javascript file {filename} not found */',
  '.css': '/* CSS file {filename} not found */'
};

function getFileContentOnFailure(filename){
  return (typeNotFoundHandler[path.extname(filename)] || '').replace(/\{filename\}/, filename);
}

function unixpath(filename){
  return path.normalize(filename).replace(slashRx, '/').replace(/^[a-z]\:/i, '');
}

function abspath(baseURI, filename){
  return unixpath(path.resolve(baseURI, filename.replace(queryAndHashRx, '')).replace(/^[a-z]\:/i, ''));
}

function isExternal(uri){
  return externalRx.test(uri);
}

function getRef(seed, type){
  seed[type] = (type in seed ? seed[type] : -1) + 1;
  return seed[type].toString(36) + type;
}

/**
 * @class File
 */

function File(manager, cfg){
  this.manager = manager;
  this.linkTo = [];
  this.linkBack = [];

  for (var key in cfg)
    this[key] = cfg[key];

  if (!this.type)
    this.type = manager.typeByExt[this.ext] || typeByExt[this.ext] || 'unknown';

  if (!this.filename)
  {
    var type = (this.generated ? 'generated-' : '') + this.type;
    manager.inline[type] = (manager.inline[type] || 0) + 1;
    this.inlineId = '[' + (this.inline ? 'inline ' : '') + type + '#' + manager.inline[type] + ']';
  }
};

File.prototype = {
  resolve: function(filename){
    if (isExternal(filename))
      return filename;

    // remove everything after ? (query string) or # (hash)
    filename = filename.replace(queryAndHashRx, '');

    return unixpath(path.resolve(this.baseURI, filename));
  },

  // input filename
  get dirname(){
    return this.filename
      ? path.dirname(this.filename)
      : '';
  },
  get basename(){
    return this.filename
      ? path.basename(this.filename)
      : '';
  },
  get name(){
    return this.filename
      ? path.basename(this.filename, path.extname(this.filename))
      : '';
  },
  get ext(){
    return this.filename
      ? path.extname(this.filename)
      : '';
  },
  get relpath(){
    return this.filename
      ? unixpath(path.relative(this.manager.baseURI, this.filename))
      : this.sourceFilename || this.inlineId;
  },
  get reldir(){
    return this.filename
      ? path.dirname(this.relpath)
      : this.sourceFilename || this.inlineId;
  },
  get fsFilename(){
    return this.filename
      ? this.manager.getFSFilename(this.filename)
      : this.sourceFilename || this.inlineId;
  },

  // input baseURI
  get baseURI(){
    if (!this.baseURI_)
      this.baseURI_ = unixpath(this.filename ? path.dirname(this.filename) + '/' : '');
    return this.baseURI_;
  },
  set baseURI(uri){
    if (!this.filename)
      this.baseURI_ = unixpath(path.resolve(this.manager.baseURI, uri) + '/');
  },

  // output filename
  get outputFilename(){
    return this.outputFilename_;
  },
  set outputFilename(filename){
    this.outputFilename_ = unixpath(filename);
    this.inline = false;
  },
  get relOutputFilename(){
    return this.outputFilename || ('outputContent' in this ? this.inlineId : '[no output]');
  },

  // refs
  get jsRef(){
    if (!this.jsRef_)
      this.jsRef_ = this.manager.getRef(this.ext);
    return this.jsRef_;
  },
  set jsRef(ref){
    this.jsRef_ = ref;
  },

  // links
  link: function(file, token){
    var link = [file, token];
    token = token || null;

    this.linkTo.push(link);
    this.manager.links.push([this, file]);
    file.linkBack.push([this, token]);

    if (token)
    {
      if (!token.links)
        token.links = [];
      token.links.push(link);

      if (file.warn)
      {
        var loc = token.loc || '';
        var warn = {
          fatal: true,
          loc: loc,
          file: this.filename || this.relpath || loc.replace(/:\d+:\d+$/, ''),
          message: file.warn.message
        };

        if (!this.manager.flow.hasWarn(warn))
          this.manager.flow.warn(warn);
      }
    }
  },
  linkFrom: function(file){
    file.link(this);
  },
  unlink: function(file, token){
    token = token || null;

    for (var i = this.linkTo.length - 1; i >= 0; i--)
    {
      var link = this.linkTo[i];
      if (link[0] === file && link[1] === token)
        this.linkTo.splice(i, 1);
    }

    for (var i = this.linkBack.length - 1; i >= 0; i--)
    {
      var link = this.linkBack[i];
      if (link[0] === file && link[1] === token)
        this.linkBack.splice(i, 1);
    }
  },

  hasLinkTo: function(file){
    return this.linkTo.some(function(link){
      return link[0] == file;
    });
  },
  hasLinkFrom: function(file){
    return file.hasLinkTo(this);
  },
  hasLinkType: function(type){
    return this.linkBack.some(function(link){
      return link[0].type == type;
    });
  },

  // misc
  get digest(){
    if (!this.digest_)
    {
      var hash = crypto.createHash('md5');
      hash.update(this.outputContent || this.content || '');
      this.digest_ = hash.digest('base64')
        // remove trailing == which always appear on md5 digest, save 2 bytes
        .replace(/=+$/, '')
        // make digest web safe
        .replace(/\//g, '_')
        .replace(/\+/g, '-');
    }

    return this.digest_;
  },
  get encoding(){
    return textType.indexOf(this.type) == -1 && textFileExt.indexOf(this.ext) == -1 ? 'binary' : 'utf-8';
  },

  get hostFilename(){
    return this.embed ? this.embed.file : this.filename;
  },
  location: function(loc){
    if (loc)
    {
      if (this.inline && this.embed)
        loc = this.embed.start
          ? [
              this.embed.file,
              this.embed.start.line + loc.line,
              (loc.line ? 0 : this.embed.start.column) + loc.column
            ]
          : [
              this.embed.file
            ];
      else
        loc = [
          this.filename,
          loc.line,
          loc.column
        ];

      return loc.join(':');
    }
  }
};

/**
 * @class FileManager
 */

var FileManager = function(baseURI, relBaseURI, console, flow){
  this.baseURI = abspath('/', relBaseURI);
  this.fsBaseURI = baseURI;
  this.console = console;
  this.preprocess = {};
  this.typeByExt = {};
  this.refSeed = {};
  this.inline = {};
  this.flow = flow;

  this.map = {};
  this.queue = [];
  this.links = [];
  this.warns = [];

  this.readInfo = [];
};

FileManager.prototype = {
  resolve: function(filename, baseURI){
    return abspath(abspath(baseURI || '.', this.baseURI), filename);
  },

  relative: function(filename){
    return abspath('/', path.relative(this.fsBaseURI, filename));
  },

 /**
  * Returns absolute filesystem filename.
  * @param {string} filename
  * @return {string}
  */
  getFSFilename: function(filename){
    return path.resolve(this.fsBaseURI, filename.replace(/^[\\\/]/, ''));
  },

 /**
  * Generate reference for ext.
  * @param {string} ext
  * @return {string}
  */
  getRef: function(ext){
    return getRef(this.refSeed, ext || '.inline');
  },

 /**
  * Get reference for File by filename.
  * @param {string} filename Path to file.
  * @returns {File} Returns file if exists.
  */
  get: function(filename){
    return this.map[abspath(this.baseURI, filename)];
  },

  read: function(filename){
    function warn(message){
      if (this.contextFile_)
      {
        var warn = {
          fatal: true,
          file: this.contextFile_,
          message: message
        };

        this.warns.push(warn);
        this.flow.warn(warn);
      }
    }

    filename = abspath(this.baseURI, filename);

    var fsFilename = this.getFSFilename(filename);
    var file = this.map[filename];
    var content = '';

    if (file)
      return file.content;

    if (!fs.existsSync(fsFilename))
    {
      warn('File `' + filename + '` not found');
      return '';
    }

    var stat = fs.statSync(fsFilename);
    if (!stat.isFile())
    {
      warn('`' + filename + '` is not a file');
      return '';
    }

    file = new File(this, { filename: filename });
    file.mtime = stat.mtime;

    content = fs.readFileSync(fsFilename, file.encoding);

    // collect data to produce content digest
    this.readInfo.push({
      filename: filename,
      encoding: file.encoding,
      content: content
    });

    // preprocessing by extension
    var preprocessors = this.preprocess[file.ext] || [];
    for (var i = 0, processor; processor = preprocessors[i]; i++)
      content = processor(content, file, this.fsBaseURI, this.console);

    // preprocessing by type
    var preprocessors = this.preprocess[file.type] || [];
    for (var i = 0, processor; processor = preprocessors[i]; i++)
      content = processor(content, file, this.fsBaseURI, this.console);

    return content;
  },

 /**
  * Create new file or return existing. It can returns undefined if filename is external reference.
  * @param {object} data Config object to create new file.
  * @return {File|undefined}
  *
  * TODO: extend file with ?, if it already exists
  * TODO: create file with uri, if it's external?
  */
  add: function(data){
    function getLocation(initiator){
      if (!initiator)
        return null;

      if (initiator.loc)
        return initiator.loc;

      var result = initiator.file.filename;
      if (initiator.start)
        result += ':' + initiator.start.line + ':' + initiator.start.column;
      return result;
    }

    var initiator = data.initiator;
    var file;

    delete data.initiator;

    if (!data.filename)
    {
      file = new File(this, data);
      this.console.log('[+] ' + file.relpath + ' (' + file.type + (file.generatedFrom ? ', source: ' + file.generatedFrom : '') + ')');
    }
    else
    {
      // ignore references for external resources
      if (isExternal(data.filename))
      {
        // external resource
        this.console.log('[i] External resource `' + data.filename + '` ignored');
        return;
      }

      var rawFilename = data.filename;
      var filename = abspath(this.baseURI, data.filename);
                     // remove everything after ? (query string) or # (hash)
                     // and normalize

      if (this.map[filename]) // ignore duplicates
      {
        this.console.log('[ ] ' + unixpath(path.relative(this.baseURI, filename)) + ' (already in queue)');
        return this.map[filename];
      }

      // create file
      data.filename = filename;
      file = new File(this, data);

      // read content
      var fsFilename = this.getFSFilename(filename);
      if (fs.existsSync(fsFilename))
      {
        if (fs.statSync(fsFilename).isFile())
        {
          this.console.start('[+] ' + filename + ' (' + file.type + ')');

          file.content = this.read(filename);

          this.console.end();
        }
        else
        {
          file.warn = '`' + filename + '` is not a file';
        }
      }
      else
      {
        file.warn = 'File `' + filename + '` not found';
      }

      if (file.warn)
      {
        file.warn = {
          fatal: true,
          file: initiator ? initiator.file.filename : file.relpath,
          message: file.warn + (initiator && initiator.token ? ': ' + initiator.token : ''),
          loc: getLocation(initiator)
        };
        this.warns.push(file.warn);
        this.flow.warn(file.warn);
        file.content = getFileContentOnFailure(filename);
      }

      this.map[filename] = file;
    }

    this.queue.push(file);

    return file;
  },

 /**
  * Remove a file from manager and break all links between files.
  * @param {File|string} fileRef File name or File instance to be removed.
  */
  remove: function(fileRef){
    var filename;
    var file;

    if (fileRef instanceof File)
    {
      file = fileRef;
      filename = file.filename;
    }
    else
    {
      filename = abspath(this.baseURI, fileRef);
      file = this.map[filename];

      if (!file)
      {
        this.flow.warn({
          file: filename,
          message: 'File `' + fileRef + '` not found in map'
        });
        return;
      }
    }

    // remove links
    for (var i = file.linkTo.length, linkTo; linkTo = file.linkTo[i]; i--)
      file.unlink(linkTo);

    for (var i = file.linkBack.length, linkBack; linkBack = file.linkBack[i]; i--)
      linkBack.unlink(file);

    // remove from queue
    var index = this.queue.indexOf(file);

    if (index != -1)
      this.queue.splice(index, 1);

    // remove from map
    if (filename)
      delete this.map[filename];
  },

 /**
  * Remove all files
  */
  clear: function(){
    this.queue.slice().forEach(this.remove, this);
  },

  mkdir: function(dirpath){
    if (!fs.existsSync(dirpath))
    {
      this.console.log('Create folder ' + dirpath);
      fs.mkdirSync(dirpath);
    }
  }
};

//
// export
//

module.exports = FileManager;
<|endoftext|>