<commit_before>var resolve = require('path').resolve
var Data = require('workshop-data')
var Sequence = require('workshop-sequence')
var Exercises = require('workshop-pkg-exercises')
var mkdirp = require('mkdirp')
var spawn = require('child_process').spawn
var tmp = require('quick-tmp')
var once = require('once')
var messages = require('../messages')
var detectPath = require('../util').detectPath
var commandsPath = require('../').commandsPath
var showMenu = require('./menu')
var ansi = require('ansi')

module.exports = function Workshop(options) {
  options = options || {}
  var name = options.name || process.title

  var dataDir = options.dataDir || getDataDir(name)
  mkdirp.sync(dataDir)
  var data = Data(name, dataDir)

  var workingDir = options.workingDir || data.get('workingDir', tmp(options.name)())

  mkdirp.sync(workingDir)

  var data = Data(name, dataDir)

  var exercises = Exercises(options.exerciseList, function() {
    return name.replace()
  })
  var sequence = Sequence(exercises, data)
  var workshop = Object.create(sequence)

  workshop.name = name
  workshop.width = Number(options.width) || 80
  workshop.subtitle = options.subtitle || '\x1b[23mSelect a chapter and hit \x1b[3mEnter\x1b[23m to begin'
  workshop.title = options.title

  workshop.data = data
  workshop.exercises = exercises
  workshop.sequence = sequence
  workshop.exerciseList = options.exerciseList
  workshop.workingDir = workingDir
  workshop.dataDir = dataDir
  mkdirp.sync(workshop.workingDir)

  workshop.start = start
  workshop.remove = remove
  workshop.prune = prune
  workshop.verify = verify
  workshop.next = next
  workshop.prev = prev
  workshop.go = go
  workshop.reset = reset
  workshop.forwardExec = forwardExec
  workshop.clear = clear
  workshop.printMenu = printMenu

  return workshop
}

var STDIO = ['pipe', process.stdout, 'pipe']

function clear(done) {
  done = done || errFn
  done = once(done)
  var workshop = this
  spawn('clear', [], {
    env: augmentEnv(process.env, workshop),
    stdio: STDIO,
    cwd: resolve(__dirname, '..')
  })
  .on('exit', function(code) {
    done(null, code)
  })
  .on('error', function() {
    return done(err)
  })
}

function start(done) {
  done = done || errFn
  //done = once(done)
  var workshop = this
  workshop.clear(function() {
    spawn('npm', ['install', workshop.getCurrent().dir], {
      env: augmentEnv(process.env, workshop),
      stdio: 'inherit',//STDIO,
      killSignal: 'SIGTERM',
      cwd: resolve(__dirname, '..')
    })
    .on('error', function() {
      return done(err)
    })
    .on('exit', function(code) {
      if (code !== 0) {
        console.warn('install exited with error code', code)
        return done(null, code)
      }
      // remove npm install output
      var cursor = ansi(process.stdout)
      cursor.up(1)
      cursor.eraseLine()
      done(null, code)
    })
  })
}

function remove(done) {
  done = done || errFn
  this.prune(done)
}

function prune(done) {
  done = done || errFn
  done = once(done)
  spawn('npm', ['prune'], {
    env: augmentEnv(process.env, this),
    stdio: STDIO,
    cwd: resolve(__dirname, '..')
  }).on('exit', function(code) {
    done(null, code)
  })
  .on('error', function() {
    return done(err)
  })
}

function verify(args, done) {
  var workshop = this
  done = done || errFn
  var current = workshop.getCurrent()
  forwardExec('verify', args, function(err, code) {
    if (err) return done(err)
    if (code !== 0) return done(null, false)
    workshop.setComplete(current.name)
    return done(null, true)
  })
}

function next(done) {
  done = done || errFn
  var workshop = this
  workshop.remove(function(err) {
    if (err) return done(err)
    var prev = workshop.getCurrent()
    workshop.setNext()
    workshop.start(function(err) {
      if (err) return done(err)
      done(null, prev)
    })
  })
}

function prev(done) {
  done = done || errFn
  var workshop = this
  workshop.remove(function(err) {
    if (err) return done(err)
    workshop.setPrev()
    workshop.start(done)
  })
}

function go(exerciseName, done) {
  done = done || errFn
  var workshop = this
  var exercise = workshop.exercises[exerciseName]
  workshop.remove(function(err) {
    if (err) return done(err)
    workshop.setCurrent(exercise.name)
    workshop.start(done)
  })
}

function reset(done) {
  done = done || errFn
  this.data.clear()
  this.start(done)
}

function forwardExec(command, args, done) {
  done = done || errFn
  done = once(done)
  var PATH = process.env[detectPath()]
  PATH = PATH.split(':').filter(function(p) {
    return p != commandsPath
  }).join(':')
  process.env[detectPath()] = PATH
  spawn(command, args, {
    env: augmentEnv(process.env, this),
    stdio: 'inherit'
  }).on('exit', function(code) {
    return done(null, code)
  })
  .on('error', function(err) {
    return done(err)
  })
}

function errFn(err) {
  if (err) {
    if (!err.stack) console.trace()
    throw err
  }
}

function augmentEnv(env, workshop) {
  //env.npm_config_loglevel = "silent"
  return env
}

function getDataDir(namespace) {
  return getDataDir.dir = getDataDir.dir || resolve(
    process.env.HOME || process.env.USERPROFILE,
    '.'+namespace
  )
}

function printMenu() {
  var workshop = this
  var menu = showMenu({
    name: this.name,
    title: this.title,
    subtitle: this.subtitle,
    width: this.width,
    completed: this.data.get('completed', []),
    exercises: [].map.call(this.exercises, function(data) {
      var exercise = require(data.dir)
      if (typeof exercise.incomplete !== 'function') return data
      data.completed = (
        100 - exercise.incomplete().length / exercise.exercises.length * 100
      ).toFixed(0)
      return data
    })
  }).on('select', function(name) {
    require('../commands/goto')(name)
  }).on('help', function(name) {
    console.log(messages['help'])
  })
}
<commit_msg>throw error if trying to go to invalid exercise.<commit_after>var resolve = require('path').resolve
var Data = require('workshop-data')
var Sequence = require('workshop-sequence')
var Exercises = require('workshop-pkg-exercises')
var mkdirp = require('mkdirp')
var spawn = require('child_process').spawn
var tmp = require('quick-tmp')
var once = require('once')
var messages = require('../messages')
var detectPath = require('../util').detectPath
var commandsPath = require('../').commandsPath
var showMenu = require('./menu')
var ansi = require('ansi')

module.exports = function Workshop(options) {
  options = options || {}
  var name = options.name || process.title

  var dataDir = options.dataDir || getDataDir(name)
  mkdirp.sync(dataDir)
  var data = Data(name, dataDir)

  var workingDir = options.workingDir || data.get('workingDir', tmp(options.name)())

  mkdirp.sync(workingDir)

  var data = Data(name, dataDir)

  var exercises = Exercises(options.exerciseList, function() {
    return name.replace()
  })
  var sequence = Sequence(exercises, data)
  var workshop = Object.create(sequence)

  workshop.name = name
  workshop.width = Number(options.width) || 80
  workshop.subtitle = options.subtitle || '\x1b[23mSelect a chapter and hit \x1b[3mEnter\x1b[23m to begin'
  workshop.title = options.title

  workshop.data = data
  workshop.exercises = exercises
  workshop.sequence = sequence
  workshop.exerciseList = options.exerciseList
  workshop.workingDir = workingDir
  workshop.dataDir = dataDir
  mkdirp.sync(workshop.workingDir)

  workshop.start = start
  workshop.remove = remove
  workshop.prune = prune
  workshop.verify = verify
  workshop.next = next
  workshop.prev = prev
  workshop.go = go
  workshop.reset = reset
  workshop.forwardExec = forwardExec
  workshop.clear = clear
  workshop.printMenu = printMenu

  return workshop
}

var STDIO = ['pipe', process.stdout, 'pipe']

function clear(done) {
  done = done || errFn
  done = once(done)
  var workshop = this
  spawn('clear', [], {
    env: augmentEnv(process.env, workshop),
    stdio: STDIO,
    cwd: resolve(__dirname, '..')
  })
  .on('exit', function(code) {
    done(null, code)
  })
  .on('error', function() {
    return done(err)
  })
}

function start(done) {
  done = done || errFn
  //done = once(done)
  var workshop = this
  workshop.clear(function() {
    spawn('npm', ['install', workshop.getCurrent().dir], {
      env: augmentEnv(process.env, workshop),
      stdio: 'inherit',//STDIO,
      killSignal: 'SIGTERM',
      cwd: resolve(__dirname, '..')
    })
    .on('error', function() {
      return done(err)
    })
    .on('exit', function(code) {
      if (code !== 0) {
        console.warn('install exited with error code', code)
        return done(null, code)
      }
      // remove npm install output
      var cursor = ansi(process.stdout)
      cursor.up(1)
      cursor.eraseLine()
      done(null, code)
    })
  })
}

function remove(done) {
  done = done || errFn
  this.prune(done)
}

function prune(done) {
  done = done || errFn
  done = once(done)
  spawn('npm', ['prune'], {
    env: augmentEnv(process.env, this),
    stdio: STDIO,
    cwd: resolve(__dirname, '..')
  }).on('exit', function(code) {
    done(null, code)
  })
  .on('error', function() {
    return done(err)
  })
}

function verify(args, done) {
  var workshop = this
  done = done || errFn
  var current = workshop.getCurrent()
  forwardExec('verify', args, function(err, code) {
    if (err) return done(err)
    if (code !== 0) return done(null, false)
    workshop.setComplete(current.name)
    return done(null, true)
  })
}

function next(done) {
  done = done || errFn
  var workshop = this
  workshop.remove(function(err) {
    if (err) return done(err)
    var prev = workshop.getCurrent()
    workshop.setNext()
    workshop.start(function(err) {
      if (err) return done(err)
      done(null, prev)
    })
  })
}

function prev(done) {
  done = done || errFn
  var workshop = this
  workshop.remove(function(err) {
    if (err) return done(err)
    workshop.setPrev()
    workshop.start(done)
  })
}

function go(exerciseName, done) {
  done = done || errFn
  var workshop = this
  var exercise = workshop.exercises[exerciseName]
  if (!exercise) throw new Error('Invalid exercise: ' + exerciseName + '. \nValid: ' + [].map.call(workshop.exercises, function(e) {return e.name}).join(', '))
  workshop.remove(function(err) {
    if (err) return done(err)
    workshop.setCurrent(exercise.name)
    workshop.start(done)
  })
}

function reset(done) {
  done = done || errFn
  this.data.clear()
  this.start(done)
}

function forwardExec(command, args, done) {
  done = done || errFn
  done = once(done)
  var PATH = process.env[detectPath()]
  PATH = PATH.split(':').filter(function(p) {
    return p != commandsPath
  }).join(':')
  process.env[detectPath()] = PATH
  spawn(command, args, {
    env: augmentEnv(process.env, this),
    stdio: 'inherit'
  }).on('exit', function(code) {
    return done(null, code)
  })
  .on('error', function(err) {
    return done(err)
  })
}

function errFn(err) {
  if (err) {
    if (!err.stack) console.trace()
    throw err
  }
}

function augmentEnv(env, workshop) {
  //env.npm_config_loglevel = "silent"
  return env
}

function getDataDir(namespace) {
  return getDataDir.dir = getDataDir.dir || resolve(
    process.env.HOME || process.env.USERPROFILE,
    '.'+namespace
  )
}

function printMenu() {
  var workshop = this
  var menu = showMenu({
    name: this.name,
    title: this.title,
    subtitle: this.subtitle,
    width: this.width,
    completed: this.data.get('completed', []),
    exercises: [].map.call(this.exercises, function(data) {
      var exercise = require(data.dir)
      if (typeof exercise.incomplete !== 'function') return data
      data.completed = (
        100 - exercise.incomplete().length / exercise.exercises.length * 100
      ).toFixed(0)
      return data
    })
  }).on('select', function(name) {
    require('../commands/goto')(name)
  }).on('help', function(name) {
    console.log(messages['help'])
  })
}
<|endoftext|>


<commit_before>var net        = require('net'),
    http       = require('http'),
    spawn      = require('child_process').spawn,
    path       = require('path'),
    fs         = require('fs'),
    netBinding = process.binding('net'),
    posix      = require(__dirname + '/../../build/default/posixtools.node');

if (!Array.prototype.remove) {
  // Array Remove - By John Resig (MIT Licensed)
  Array.prototype.remove = function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
  };
}

KILLSIGS = ["SIGINT","SIGTERM"];

function launchServer(config) {

  var pidfile = config.pidfile;
  var workers = [];
  var mastersigs = ['SIGINT', 'SIGTERM', 'SIGCHLD', 'SIGHUP', 'SIGQUIT', 'SIGTTIN', 'SIGTTIN', 'SIGWINCH'];
  var sigqueue = [];
  var server_socket = null;
  var pipeFDs = null;
  var reader = null;
  var writer = null;
  var workerProcesses = config.workers;
  var reexec_pid = null;
  var workerScript = config.worker_app;
  var sigchld = false;

  function exitMaster() {
    if (sigchld) return;
    console.error("master received: the kill");
    process.exit(0);
  }

  function wakeupMaster() {
    sigchld = true;
    console.error("SIGCHLD, wake :" + posix.getpid());
    writer.write("."); // wake up
  }
  function sigQueue() {
    console.error("queue: " + this);
    sigqueue.push(this);
    wakeupMaster();
  }

  function tearDownMaster() {
    console.error("Exiting(" + process.pid + "), " + pidfile);
    // signal all our child processes to exit also
    signalWorkers('SIGTERM');
    if (path.existsSync(pidfile)) { fs.unlinkSync(pidfile); }
  }

  function killWorker(pid, sig) {
    console.error("send(%d): %d, %s", posix.getpid(), pid, sig);
    try { process.kill(pid, sig); } catch(e) { console.error(e); }
  }

  function signalWorkers(sig) {
    workers.forEach(function(cpid) {
      killWorker(cpid, sig);
    });
  }

  function reapWorkers() {
    // reap workers
    var update_pids = [];
    var pid;
    console.error("reaping: %d", workers.length);

    for (var index = 0, len = workers.length; index < len; ++index) {
      var pid = workers[index];
      var status = posix.isalive(pid);
      if (!status) {
        console.error("pid: %d is dead with: %d", pid, status);
        if ((pid=forkWorker(index)) == 0) {
          return true;  // child's done
        }
        update_pids.push([index, pid]);
      }
    }
    update_pids.forEach(function(set) {
      workers[set[0]] = set[1];
    });
    console.error(workers);
    return false;
  }

  function runWorker(fd,id) {
    try {
      if (process.platform != 'darwin') { // setting the process title on Mac is not really safe...
        process.title = "node worker[" + id + "]";
      }
      var ppid = posix.getppid();
      console.error(posix.getpid() + ", parent is: %d", posix.getppid());

      // start polling our parent pid and die if our parent changes
      setInterval(function() {
        // verify our parent process is still the same
        if (ppid != posix.getppid()) {
          console.error("parent changed die");
          if (path.existsSync(pidfile)) { fs.unlinkSync(pidfile); } // try to cleanup for master
          process.exit(1); // parent changed die
          return;
        }
      }, 1000);

      require(workerScript).run(config, function(server) {

        server.listenFD(fd);

        process.on("SIGQUIT", function() {
          console.error("%d, got sigquit", posix.getpid());
          server.close(); // stop listening for new connections
          setTimeout(function() {
            process.exit(1);
          }, 30000); // really quit in 30 seconds if we haven't already
        });

        // last thing after fork is to trigger callback
        if (config.after_fork) { try { config.after_fork(config, posix.getpid(), posix.getppid(), id); } catch(e) { console.error(e); } }
      });

    } catch(e) {
      console.error(e);
    }
  }

  // unregister events setup by master
  function resetWorker() {
    sigqueue = [];
    mastersigs.forEach(process.removeAllListeners.bind(process));
    process.removeAllListeners('exit');
  }

  function workerSignals() {
    KILLSIGS.forEach(function(sig) {
      process.on(sig, function() { console.error("worker: %d received the kill\n", posix.getpid()); process.exit(0); });
    });
  }

  function forkWorker(id) {
    if (config.before_fork) { try { config.before_fork(config, id); } catch(e) { console.error(e); } }
    var pid = posix.fork();
    if (pid == 0) {
      posix.setsid();
      resetWorker();
      workerSignals();
      runWorker(server_socket, id);
      return 0;
    }
    console.error("save pid: %d, %d", pid, id);
    return pid;
  }

  function reloadConfig() {
    // reload config
    // restart workers
    signalWorkers('SIGQUIT'); // send them the quit signal and master will later reboot them
  }

  function shutdownGraceful() {
    workerProcesses = 0; // bring the count down to 0, this way we don't try to revive these workers
    signalWorkers('SIGQUIT'); // send them the quit signal
    // wait longer?
    //netBinding.close(server_socket);
    process.exit(0);
  }

  function pathSearch(binary) {
    if (binary.match(/^\//)) { return binary; } // already absolute
    var binpath = null;
    process.env['PATH'].split(':').some(function(p) {
      var bin = path.resolve(p, binary);
      if (path.existsSync(bin)) {
        binpath = bin;
        return true;
      }
      return false;
    });
    return binpath;
  }

  // used this resource: http://www.linuxforums.org/forum/programming-scripting/54613-how-access-open-file-descriptors-after-doing-exec.html
  // read this too: http://www.cim.mcgill.ca/~franco/OpSys-304-427/lecture-notes/node27.html
  // also read this: https://github.com/pgte/fugue/wiki/How-Fugue-Works
  // and read this: http://stackoverflow.com/questions/1643304/how-to-set-close-on-exec-by-default
  function reexecuteMaster() {
    console.error("call: reexecuteMaster as %d", posix.getpid());
    var binary = pathSearch(process.argv[0]);
    var envp = [];
    var argv = process.argv.map(function(v) { return v; });

    for (var k in process.env) {
      envp.push(k + "=" + process.env[k]);
    }
    // set the original master pid in the new master's enviornment
    // this will also indicate to the new master process that it should not
    // try to rebind, but instead reuse the existing server socket
    envp.push("__NIX_FD=" + server_socket);

    argv.shift(); // shift the original node off execve expects argv[0] to be the js file

    if (config.before_exec) { try { config.before_exec(config); } catch(e) { console.error(e); } }

    if (!pidfile.match(/oldbin$/)) { pidfile += ".oldbin"; } // if we're oldbin already don't change...
    // create a new pidfile for our process
    fs.writeFileSync(pidfile, posix.getpid().toString());

    reexec_pid = posix.fork(false); // don't clear the event loop since we'll be execv soon

    if (reexec_pid == 0) {
      posix.fd_open_on_exec(server_socket); // keep the server socket alive
      posix.execve(binary, argv, envp);
    }

    // update current master as old a new one is starting up
    process.title = "node master (old)";
  }

  function runMaster(workers) {

    pipeFDs = netBinding.pipe();
    reader = new net.Stream();
    writer = new net.Stream();

    process.on('exit', tearDownMaster);
    process.on('SIGCHLD', wakeupMaster);

    process.on('SIGHUP',   sigQueue.bind({sig:'SIGHUP'}));
    process.on('SIGQUIT',  sigQueue.bind({sig:'SIGQUIT'}));
    process.on('SIGTTIN',  sigQueue.bind({sig:'SIGTTIN'}));
    process.on('SIGTTOU',  sigQueue.bind({sig:'SIGTTOU'}));
    process.on('SIGWINCH', sigQueue.bind({sig:'SIGWINCH'}));
    process.on('SIGUSR2',  sigQueue.bind({sig:'SIGUSR2'}));

    KILLSIGS.forEach(function(sig) { process.on(sig, exitMaster); });

    if (!pidfile) { pidfile = "/tmp/server." + posix.getpid() + ".pid"; }

    // create the pidfile
    fs.writeFileSync(pidfile, posix.getpid().toString());

    // control signals and reapworkers
    reader.on("data", function() {
      var sig = null;
      if (reapWorkers()) { return; } // new child, exit
      while (sig=sigqueue.pop()) {
        console.log(sig);
        switch(sig.sig) {
        case 'SIGHUP':
          // reload the configuration and restart all the workers
          reloadConfig();
          break;
        case 'SIGQUIT':
          shutdownGraceful();
          break;
        case 'SIGTTIN':
          // increase the numner of worker processes by 1
          ++workerProcesses;
          break;
        case 'SIGTTOU':
          // decrease the numner of worker processes by 1
          if (workerProcesses > 0) { --workerProcesses; }
          break;
        case 'SIGWINCH':
          // tell all workers to quit
          workerProcesses = 0; // bring the count down to 0
          break;
        case 'SIGUSR2':
          // reexecute the running binary.  A QUIT or TERM signal can be sent to the current process to have the new process take it's place.
          reexecuteMaster();
          return;
        default:
          break;
        }
      }
      sigchld = false;
      maintainWorkerCount();
    });

    reader.open(pipeFDs[0]);
    writer.open(pipeFDs[1]);
    reader.resume();
  }

  function maintainWorkerCount() {
    var off = workers.length - workerProcesses;
    console.error("worker count difference: %d", off);
    if (!off) { return; }
    if (off < 0) { return startMissingWorkers(); }
    workers = workers.filter(function(pid, i) {
      if (i >= workerProcesses) {
        killWorker(pid, 'SIGQUIT');
        return false;
      }
      return true;
    });
    console.error(workers);
  }

  function startMissingWorkers() {
    console.error("start missing workers");
    var pid;
    for (var i = 0; i < workerProcesses; ++i) {
      if (workers[i]) { continue; }
      if ((pid=forkWorker(i)) == 0) {
        return true;  // child's done
      }
      workers[i] = pid;
    }
    return false;
  }

  // start workers
  var pid;
  var newMaster = false;

  if (process.env['__NIX_FD']) { // reexec from old master
    newMaster = true;
    server_socket  = parseInt(process.env['__NIX_FD']);
    console.error("new master is up: %d from %d:%d\n", process.pid, posix.getppid(), server_socket);
  }
  else {
    // fresh process create a new socket
    server_socket = netBinding.socket(config.tcp);
    if (config.listen_port && config.listen_addr) {
      netBinding.bind(server_socket, config.listen_port, config.listen_addr);
    } /*else if (config.listen_sock) {
      netBinding.bind(server_socket, config.listen_sock);
    }*/
  }

  // start listening on server socket with backlog of 128
  netBinding.listen(server_socket, config.backlog);

  console.error("launching: %d workers\n", workerProcesses);

  // boot up the workers
  if (startMissingWorkers()) { return; } // child exits

  process.title = "node master"

  runMaster(workers);

}

exports.Tiamat = launchServer;
<commit_msg>graceful shutdowns, logrotate USR1, and cfg reloads HUP<commit_after>var net        = require('net'),
    http       = require('http'),
    spawn      = require('child_process').spawn,
    path       = require('path'),
    fs         = require('fs'),
    netBinding = process.binding('net'),
    posix      = require(__dirname + '/../../build/default/posixtools.node'),
    Config     = require("tiamat/config_loader.js").ConfigLoader;

if (!Array.prototype.remove) {
  // Array Remove - By John Resig (MIT Licensed)
  Array.prototype.remove = function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
  };
}

KILLSIGS = ["SIGINT","SIGTERM"];

function launchServer(config) {

  var pidfile = config.pidfile;
  var workers = [];
  var mastersigs = ['SIGINT', 'SIGTERM', 'SIGCHLD', 'SIGHUP', 'SIGQUIT', 'SIGTTIN', 'SIGTTIN', 'SIGWINCH'];
  var sigqueue = [];
  var server_socket = null;
  var pipeFDs = null;
  var reader = null;
  var writer = null;
  var workerProcesses = config.workers;
  var reexec_pid = null;
  var sigchld = false;
  var teardown = false;

  function exitMaster() {
    if (sigchld) return;
    teardown = true;
    console.log("master(%d) received: the kill", posix.getpid());
    // signal all our child processes to exit also
    signalWorkers('SIGTERM'); // kill our children
    process.exit(0);
  }

  function wakeupMaster() {
    sigchld = true;
    console.log("master(%d), wake", posix.getpid());
    writer.write("."); // wake up
  }
  function sigQueue() {
    sigqueue.push(this);
    wakeupMaster();
  }

  function tearDownMaster() {
    console.log("master(%d) exiting with %s", posix.getpid(), pidfile);
    if (path.existsSync(pidfile)) { fs.unlinkSync(pidfile); }
  }

  function killWorker(pid, sig) {
    //console.log("send(%d): %d, %s", posix.getpid(), pid, sig);
    try { process.kill(pid, sig); } catch(e) { console.error(e); }
  }

  function signalWorkers(sig) {
    workers.forEach(function(cpid) {
      killWorker(cpid, sig);
    });
  }

  function reapWorkers() {
    // reap workers
    var update_pids = [];
    var pid;
    console.log("reaping: %d", workers.length);

    for (var index = 0, len = workers.length; index < len; ++index) {
      var pid = workers[index];
      var status = posix.isalive(pid);
      if (!status) {
        console.log("pid: %d is dead with: %d", pid, status);
        if ((pid=forkWorker(index)) == 0) {
          return true;  // child's done
        }
        update_pids.push([index, pid]);
      }
    }
    update_pids.forEach(function(set) {
      workers[set[0]] = set[1];
    });
    console.log(workers);
    return false;
  }

  function rotateLogFiles() {
    posix.reopen_stdio(config.stdout_path, config.stderr_path);
    signalWorkers("SIGUSR1");
    //signalWorkers('SIGQUIT'); // send them the quit signal and master will later reboot them
  }

  function runWorker(fd,id) {
    try {
      var ppid = posix.getppid();

      // start polling our parent pid and die if our parent changes
      setInterval(function() {
        // verify our parent process is still the same
        if (ppid != posix.getppid()) {
          console.log("\tworker(%d) parent(%d) changed to, die", posix.getpid(), ppid, posix.getppid());
          process.exit(1); // parent changed die
          return;
        }
      }, 1000);

      console.log("\tworker(%d) alive with parent(%d) at %s", posix.getpid(), posix.getppid(), config.worker_app);

      if (process.platform != 'darwin') { // setting the process title on Mac is not really safe...
        process.title = "node worker[" + id + "]";
      }

      // load application
      require(config.worker_app).run(config, function(server) {
        console.log("\tworker(%d) got sigquit with %d connections", posix.getpid(), server.connections);

        process.on("SIGQUIT", function() {
          // stop accepting
          server.watcher.stop();

          if (server.connections) {
            // start polling until all connections are exhausted
            setInterval(function() { server.connections || process.exit(0); }, 1500);
            // set a timeout if we live past this point kill
            setTimeout(function() { process.exit(1); }, config.timeout);
          }
          else {
            process.exit(0);
          }

        });

        process.on("SIGUSR1", function() {
          posix.reopen_stdio(config.stdout_path, config.stderr_path);
        });

        server.listenFD(fd);

        // once application is loaded call the after fork helper
        if (config.after_fork) { try { config.after_fork(config, posix.getpid(), posix.getppid(), id); } catch(e) { console.error(e); } }
      });

    } catch(e) {
      console.error(e);
    }
  }

  // unregister events setup by master
  function resetWorker() {
    sigqueue = [];
    mastersigs.forEach(process.removeAllListeners.bind(process));
    process.removeAllListeners('exit');
  }

  function workerSignals() {
    KILLSIGS.forEach(function(sig) {
      console.log("\tworker(%d) register (%s)", posix.getpid(), sig);
      process.on(sig, function() { console.log("\tworker(%d) received (%s)", posix.getpid(), this.sig); process.exit(0); }.bind({sig:sig}));
    });
  }

  function forkWorker(id) {
    if (config.before_fork) { try { config.before_fork(config, id); } catch(e) { console.error(e); } }
    var pid = posix.fork();
    if (pid == 0) {
      console.log("\tworker(%d) alive for master(%d)", posix.getpid(), posix.getppid());
      posix.setsid();
      resetWorker();
      workerSignals();
      runWorker(server_socket, id);
      return 0;
    }
    console.log("master(%d) save pid: %d, %d", posix.getpid(), pid, id);
    return pid;
  }

  function reloadConfig() {
    // reload config
    if (config.config_path) {
      var configLoader = new Config();
      configLoader.on("loaded", function(cfg) {
        cfg = configLoader.defaults(cfg);
        if (configLoader.verify(cfg)) {
          // update masters configuration
          config = cfg;
          // restart workers
          signalWorkers('SIGQUIT'); // reboot workers
        }
      });
      configLoader.load(config.config_path);
    }
  }

  function shutdownGraceful() {
    console.log("master(%d) received shutdown", posix.getpid());
    try {
      teardown = true; // mark the process for teardown
      workerProcesses = 0; // bring the count down to 0, this way we don't try to revive these workers
      process.removeAllListeners("SIGCHLD"); // don't try to restart workers
      //signalWorkers('SIGQUIT'); // send them the quit signal

      // start master shutdown cycle...
      netBinding.close(server_socket); // stop receiving on the socket
    } catch(e) {
      console.error("master(%d) error during shutdown", posix.getpid());
    }
  }

  function pathSearch(binary) {
    if (binary.match(/^\//)) { return binary; } // already absolute
    var binpath = null;
    process.env['PATH'].split(':').some(function(p) {
      var bin = path.resolve(p, binary);
      if (path.existsSync(bin)) {
        binpath = bin;
        return true;
      }
      return false;
    });
    return binpath;
  }

  // used this resource: http://www.linuxforums.org/forum/programming-scripting/54613-how-access-open-file-descriptors-after-doing-exec.html
  // read this too: http://www.cim.mcgill.ca/~franco/OpSys-304-427/lecture-notes/node27.html
  // also read this: https://github.com/pgte/fugue/wiki/How-Fugue-Works
  // and read this: http://stackoverflow.com/questions/1643304/how-to-set-close-on-exec-by-default
  function reexecuteMaster() {
    console.log("master(%d) call reexecuteMaster", posix.getpid());
    var binary = pathSearch(process.argv[0]);
    var envp = [];
    var argv = process.argv.map(function(v) { return v; });

    for (var k in process.env) {
      envp.push(k + "=" + process.env[k]);
    }
    // set the original master pid in the new master's enviornment
    // this will also indicate to the new master process that it should not
    // try to rebind, but instead reuse the existing server socket
    envp.push("__NIX_FD=" + server_socket);

    argv.shift(); // shift the original node off execve expects argv[0] to be the js file

    if (config.before_exec) { try { config.before_exec(config); } catch(e) { console.error(e); } }

    if (!pidfile.match(/oldbin$/)) { pidfile += ".oldbin"; } // if we're oldbin already don't change...
    // create a new pidfile for our process
    fs.writeFileSync(pidfile, posix.getpid().toString());

    reexec_pid = posix.fork(false); // don't clear the event loop since we'll be execv soon

    if (reexec_pid == 0) {
      posix.fd_open_on_exec(server_socket); // keep the server socket alive
      posix.execve(binary, argv, envp);
    }

    // update current master as old a new one is starting up
    process.title = "node master (old)";
  }

  function runMaster(workers) {

    pipeFDs = netBinding.pipe();
    reader = new net.Stream();
    writer = new net.Stream();

    process.on('SIGHUP',   sigQueue.bind({sig:'SIGHUP'}));
    process.on('SIGQUIT',  sigQueue.bind({sig:'SIGQUIT'}));
    process.on('SIGTTIN',  sigQueue.bind({sig:'SIGTTIN'}));
    process.on('SIGTTOU',  sigQueue.bind({sig:'SIGTTOU'}));
    process.on('SIGWINCH', sigQueue.bind({sig:'SIGWINCH'}));
    process.on('SIGUSR2',  sigQueue.bind({sig:'SIGUSR2'}));
    process.on('SIGUSR1',  sigQueue.bind({sig:'SIGUSR1'}));
    process.on('SIGCHLD',  wakeupMaster);
    process.on('exit',     tearDownMaster);

    KILLSIGS.forEach(function(sig) { process.on(sig, exitMaster); });

    if (!pidfile) { pidfile = "/tmp/server." + posix.getpid() + ".pid"; }

    // create the pidfile
    fs.writeFileSync(pidfile, posix.getpid().toString());

    // control signals and reapworkers
    reader.on("data", function() {
      var sig = null;
      if (!reexec_pid) {
        if (teardown || reapWorkers()) { return; } // new child, exit or we're exiting master
      }
      while (sig=sigqueue.pop()) {
        //console.log(sig);
        switch(sig.sig) {
        case 'SIGHUP':
          // reload the configuration and restart all the workers
          reloadConfig();
          break;
        case 'SIGQUIT':
          shutdownGraceful();
          break;
        case 'SIGTTIN':
          // increase the numner of worker processes by 1
          ++workerProcesses;
          break;
        case 'SIGTTOU':
          // decrease the numner of worker processes by 1
          if (workerProcesses > 0) { --workerProcesses; }
          break;
        case 'SIGWINCH':
          // tell all workers to quit
          workerProcesses = 0; // bring the count down to 0
          break;
        case 'SIGUSR1':
          rotateLogFiles();
          break;
        case 'SIGUSR2':
          // reexecute the running binary.  A QUIT or TERM signal can be sent to the current process to have the new process take it's place.
          reexecuteMaster();
          return;
        default:
          break;
        }
      }
      sigchld = false;
      maintainWorkerCount();
    });

    reader.open(pipeFDs[0]);
    writer.open(pipeFDs[1]);
    reader.resume();
  }

  function maintainWorkerCount() {
    var off = workers.length - workerProcesses;
    console.log("master(%d) worker count difference: %d", posix.getpid(), off);
    if (!off) { return; }
    if (off < 0) { return startMissingWorkers(); }
    workers = workers.filter(function(pid, i) {
      if (i >= workerProcesses) {
        killWorker(pid, 'SIGQUIT');
        return false;
      }
      return true;
    });
    console.log("master(%d) workers(%d): %s", posix.getpid(), workers.length, workers.toString());
    if (workers.length == 0 && teardown) {
      process.exit(0);
    }
  }

  function startMissingWorkers() {
    console.log("master(%d) start missing workers", posix.getpid());
    var pid;
    for (var i = 0; i < workerProcesses; ++i) {
      if (workers[i]) { continue; }
      if ((pid=forkWorker(i)) == 0) {
        return true;  // child's done
      }
      workers[i] = pid;
    }
    return false;
  }

  // start workers
  var pid;
  var newMaster = false;

  if (process.env['__NIX_FD']) { // reexec from old master
    newMaster = true;
    server_socket  = parseInt(process.env['__NIX_FD']);
    delete process.env.__NIX_FD;
    console.log("new master is up: %d from %d with socket:%d\n", process.pid, posix.getppid(), server_socket);
  }
  else {
    // fresh process create a new socket
    server_socket = netBinding.socket(config.tcp);
    if (config.listen_port && config.listen_addr) {
      netBinding.bind(server_socket, config.listen_port, config.listen_addr);
    } /*else if (config.listen_sock) {
      netBinding.bind(server_socket, config.listen_sock);
    }*/
  }

  // start listening on server socket with backlog of 128
  netBinding.listen(server_socket, config.backlog);

  console.log("master(%d) launching: %d workers", posix.getpid(), workerProcesses);

  // boot up the workers
  if (maintainWorkerCount()) { return; } // child exits

  console.log("master(%d) initializing", posix.getpid());
  process.title = "node master"

  runMaster(workers);

}

exports.Tiamat = launchServer;
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var AttributeTrigger = require('./attribute_trigger');

/**
 * Property.
 *
 * @since 1.0
 * @public
 * @constructor Property
 * @param {Function} [getter] The getter to use (also sets `readable`).
 * @param {Function} [setter] The setter to use (also sets `readable`).
 * @param {Boolean} [options.readable=true] Whether the property is readable.
 * @param {Boolean} [options.writable=false] Whether the property is writable.
 */
var Property = AttributeTrigger.extend(/** @lends Property# */{
  init: function() {
    var args = Array.prototype.slice.call(arguments);
    var accessorArg = function(arg) {
      return arg === undefined || typeof arg === 'function';
    };
    var get = accessorArg(args[0]) ? args.shift() : undefined;
    var set = accessorArg(args[0]) ? args.shift() : undefined;
    var options = args.shift();
    this.get = get;
    this.set = set;
    this.options = options;
  },

  invoke: function(name, reopen, prototype) {
    var get = this.get;
    var set = this.set;
    var opts = _.defaults({}, this.options, {
      readable: true,
      writable: false,
      property: '_' + name
    });
    var property = opts.property;
    var standardGet = function()  { return this[property]; };
    var standardSet = function(v) { this[property] = v; };
    if (!get && opts.readable) { get = standardGet; }
    if (!set && opts.writable) { set = standardSet; }

    Object.defineProperty(prototype, name, {
      enumerable: true,
      get: get,
      set: set,
    });
  }
});

Property.reopenClass({
  fn: Property.create.bind(Property)
});

module.exports = Property.reopenClass({ __name__: 'Property' });
<commit_msg>Property function is now a property itself that is aware of the class it's called on.<commit_after>'use strict';

var _ = require('lodash');
var AttributeTrigger = require('./attribute_trigger');

/**
 * Property.
 *
 * @since 1.0
 * @public
 * @constructor Property
 * @param {Function} [getter] The getter to use (also sets `readable`).
 * @param {Function} [setter] The setter to use (also sets `readable`).
 * @param {Boolean} [options.readable=true] Whether the property is readable.
 * @param {Boolean} [options.writable=false] Whether the property is writable.
 */
var Property = AttributeTrigger.extend(/** @lends Property# */{
  init: function() {
    var args = Array.prototype.slice.call(arguments);
    var accessorArg = function(arg) {
      return arg === undefined || typeof arg === 'function';
    };
    var get = accessorArg(args[0]) ? args.shift() : undefined;
    var set = accessorArg(args[0]) ? args.shift() : undefined;
    var options = args.shift();
    this.get = get;
    this.set = set;
    this.options = options;
  },

  invoke: function(name, reopen, prototype) {
    var get = this.get;
    var set = this.set;
    var opts = _.defaults({}, this.options, {
      readable: true,
      writable: false,
      property: '_' + name
    });
    var property = opts.property;
    var standardGet = function()  { return this[property]; };
    var standardSet = function(v) { this[property] = v; };
    if (!get && opts.readable) { get = standardGet; }
    if (!set && opts.writable) { set = standardSet; }

    Object.defineProperty(prototype, name, {
      enumerable: true,
      get: get,
      set: set,
    });
  }
});

Property.reopenClass({

  /**
   * A function that will allow creation of a property without having to use
   * {@link Class.create}.
   *
   * @type {Function} A function that will create the property.
   */
  fn: Property.create(function() {
    return this.create.bind(this);
  })
});

module.exports = Property.reopenClass({ __name__: 'Property' });
<|endoftext|>


<commit_before>var env = process.env;
var fs = require('fs');
var cache = require(env.Z_PATH_CACHE + 'cache');

var viewsCache = cache('views');
var snippetCache = cache('snippets');

module.exports = setup;

function setup (instance) {

    // setup view
    instance.on('view>', factoryService);

    // return view factory
    return factory;
}

// get html snipptets (ws)
function getSnipped (path, callback) {
    var self = this;

    if (!path) {
        return callback();
    }

    path = env.Z_PATH_PROCESS_MARKUP + path.replace(/[^a-z0-9\/\.\-_]|\.\.\//gi, "");

    // check cache
    var snippet = snippetCache.get(path);
    if (snippet) {
        return callback(null, snippet);
    }

    // read snippet file
    fs.readFile(path, {encoding: 'utf8'}, function (err, data) {

        if (err) {
            return callback(err);
        }

        // save snippet in cache
        snippetCache.set(path, data);

        callback(null, data);
    });
}

function factory (config, callback) {
    var self = this;
     // create cache key
    var cacheKey = (config.project || '') + self.Z.name + config.name;

    // check cache
    var view = viewsCache.get(cacheKey);
    if (view) {
        return callback(null, view);
    }

    // get system store to fetch models
    self.Z.model({name: env.Z_VIEWS_MODEL, project: config.project}, function (err, views) {

        if (err) {
            return callback(err);
        }

        views.request({m: 'findOne', q: {name: config.name, instance: self.Z.name}}, function (err, view) {

            if (err || !view) {
                return callback(err || 'View "' + config.name + '" of "' + self.Z.name + '" not found');
            }

            // get here the html snipped
            getSnipped(view.html, function (err, snipped) {

                if (err) {
                    return callback(err);
                }

                // add snipped to view
                if (snipped) {
                    view.html = snipped;
                }

                // save view in cache
                viewsCache.set(cacheKey, view);

                callback(null, view);
            });
        });
    });
}

function factoryService (err, message) {
    var self = this;

    // check message
    if (!message) {
        return self.emit('<view', 'Bad message');
    }

    // create factory config
    if (typeof message === 'string') {
        message = {name: message};
    } else {
        message = {
            name: message[0],
            project: message[1]
        };
    }

    // create model
    factory.call(self, message, function (err, view) {

        if (err) {
            return self.emit('<view', err);
        }

        // create client view
        var clientView = {
            name: view.name
        };

        // add client config
        if (view.config && view.config.client) {
            clientView.config = view.config.client;
        }

        if (view.O) {
            clientView.O = view.O;
        }

        if (view.L) {
            clientView.L = view.L;
        }

        if (view.html) {
            clientView.html = view.html;
        }

        if (view.to) {
            clientView.to = view.to;
        }

        if (view.css) {
            clientView.css = view.css;
        }

        self.emit('<view', null, clientView);
    });
}
<commit_msg>adapt to new cache api and make views events configurable<commit_after>var env = process.env;
var fs = require('fs');
var cache = require(env.Z_PATH_CACHE + 'cache');

var pojoViews = cache.pojo('views');
var compViews = cache.comp('views');
var snippetCache = cache.pojo('snippets');

var events = {
    ask: 'V>',
    send: '<V',
};

module.exports = setup;

function setup (instance) {

    // setup view
    instance.on(events.ask, factoryService);

    // return view factory
    return factory;
}

// get html snipptets (ws)
function getSnipped (path, callback) {
    var self = this;

    if (!path) {
        return callback();
    }

    path = env.Z_PATH_PROCESS_MARKUP + path.replace(/[^a-z0-9\/\.\-_]|\.\.\//gi, "");

    // check cache
    var snippet = snippetCache.get(path);
    if (snippet) {
        return callback(null, snippet);
    }

    // read snippet file
    fs.readFile(path, {encoding: 'utf8'}, function (err, data) {

        if (err) {
            return callback(err);
        }

        // save snippet in cache
        snippetCache.set(path, data);

        callback(null, data);
    });
}

function factory (config, callback) {
    var self = this;
     // create cache key
    var cacheKey = (config.project || '') + self.Z.name + config.name;

    // check cache
    var view = pojoViews.get(cacheKey);
    if (view) {
        return callback(null, view);
    }

    // get system store to fetch models
    self.Z.model({name: env.Z_VIEWS_MODEL, project: config.project}, function (err, views) {

        if (err) {
            return callback(err);
        }

        views.request({m: 'findOne', q: {name: config.name, instance: self.Z.name}}, function (err, view) {

            if (err || !view) {
                return callback(err || 'View "' + config.name + '" of "' + self.Z.name + '" not found');
            }

            // get here the html snipped
            getSnipped(view.html, function (err, snipped) {

                if (err) {
                    return callback(err);
                }

                // add snipped to view
                if (snipped) {
                    view.html = snipped;
                }

                // save view in cache
                pojoViews.set(cacheKey, view);

                callback(null, view);
            });
        });
    });
}

function factoryService (err, message) {
    var self = this;

    // check message
    if (!message) {
        return self.emit(events.send, 'Bad message');
    }

    // create factory config
    if (typeof message === 'string') {
        message = {name: message};
    } else {
        message = {
            name: message[0],
            project: message[1]
        };
    }

    // create model
    factory.call(self, message, function (err, view) {

        if (err) {
            return self.emit(events.send, err);
        }

        // create client view
        var clientView = {
            name: view.name
        };

        // add client config
        if (view.config && view.config.client) {
            clientView.config = view.config.client;
        }

        if (view.O) {
            clientView.O = view.O;
        }

        if (view.L) {
            clientView.L = view.L;
        }

        if (view.html) {
            clientView.html = view.html;
        }

        if (view.to) {
            clientView.to = view.to;
        }

        if (view.css) {
            clientView.css = view.css;
        }

        self.emit(events.send, null, clientView);
    });
}
<|endoftext|>


<commit_before>var M = process.mono;
var fs = require('fs');
var viewCollection = "m_views";

module.exports = setup;

function setup (instance) {

    // setup view
    instance.on('view>', factoryService);

    // return view factory
    return factory;
}

// get html snipptets (ws)
function getSnipped (path, callback) {
    var self = this;

    if (!path) {
        return callback('No path given');
    }

    path = M.config.paths.TEMPLATE_ROOT + path.replace(/[^a-z0-9\/\.\-_]|\.\.\//gi, "");

    // check cache
    var snippet = M.cache.snippets.get(path);
    if (snippet) {
        return callback(null, snippet);
    }

    // read snippet file
    fs.readFile(path, {encoding: 'utf8'}, function (err, data) {

        if (err) {
            return callback('File not found');
        }

        // save snippet in cache
        M.cache.snippets.save(path, data);

        callback(null, data);
    });
}

function factory (config, callback) {

     // create cache key
    var cacheKey = (config.project || '') + config.name;

    // check cache
    var view = M.cache.views.get(cacheKey);
    if (view) {
        return callback(null, view);
    }

    // get system store to fetch models
    M.store(M.config.store.systemStoreName, config.project, function (err, systemStore) {

        if (err) {
            return callback(err);
        }

        // get view config
        // TODO check access
        systemStore.collection(viewCollection).findOne({name: config.name}, function (err, view) {

            if (err || !view) {
                return callback(err || 'View not found');
            }

            // get here the html snipped
            getSnipped(view.client.html, function (err, snipped) {

                if (err) {
                    return callback(err);
                }

                // add snipped to view
                if (snipped) {
                    view.client.html = snipped;
                }

                // save view in cache
                M.cache.views.save(cacheKey, view);

                callback(null, view);
            });
        });
    });
}

function factoryService (err, message) {
    var self = this;

    // check message
    if (!message) {
        return self.emit('<view', 'Bad message');
    }

    // create factory config
    if (typeof message === 'string') {
        message = {name: message};
    } else {
        message = {
            name: message[0],
            project: message[1]
        };
    }

    // create model
    factory(message, function (err, view) {

        if (err) {
            return self.emit('<view', err);
        }

        // create client view
        var clientView = view.client;
        clientView.name = view.name;

        self.emit('<view', null, clientView);
    });
}
<commit_msg>namespace views with instance name<commit_after>var M = process.mono;
var fs = require('fs');
var viewCollection = "m_views";

module.exports = setup;

function setup (instance) {

    // setup view
    instance.on('view>', factoryService);

    // return view factory
    return factory;
}

// get html snipptets (ws)
function getSnipped (path, callback) {
    var self = this;

    if (!path) {
        return callback();
    }

    path = M.config.paths.TEMPLATE_ROOT + path.replace(/[^a-z0-9\/\.\-_]|\.\.\//gi, "");

    // check cache
    var snippet = M.cache.snippets.get(path);
    if (snippet) {
        return callback(null, snippet);
    }

    // read snippet file
    fs.readFile(path, {encoding: 'utf8'}, function (err, data) {

        if (err) {
            return callback(err);
        }

        // save snippet in cache
        M.cache.snippets.save(path, data);

        callback(null, data);
    });
}

function factory (config, callback) {
    var self = this;
     // create cache key
    var cacheKey = (config.project || '') + config.name;

    // check cache
    var view = M.cache.views.get(cacheKey);
    if (view) {
        return callback(null, view);
    }

    // get system store to fetch models
    M.store(M.config.store.systemStoreName, config.project, function (err, systemStore) {

        if (err) {
            return callback(err);
        }

        // get view config
        // TODO check access
        //console.log('ACCESS ROLE:', self.link.ws.session.rid);
        systemStore.collection(viewCollection).findOne({name: config.name, instance: self.mono.name}, function (err, view) {

            if (err || !view) {
                return callback(err || 'View not found');
            }

            // get here the html snipped
            getSnipped(view.client.html, function (err, snipped) {

                if (err) {
                    return callback(err);
                }

                // add snipped to view
                if (snipped) {
                    view.client.html = snipped;
                }

                // save view in cache
                M.cache.views.save(cacheKey, view);

                callback(null, view);
            });
        });
    });
}

function factoryService (err, message) {
    var self = this;

    // check message
    if (!message) {
        return self.emit('<view', 'Bad message');
    }

    // create factory config
    if (typeof message === 'string') {
        message = {name: message};
    } else {
        message = {
            name: message[0],
            project: message[1]
        };
    }

    // create model
    factory.call(self, message, function (err, view) {

        if (err) {
            return self.emit('<view', err);
        }

        // create client view
        var clientView = view.client;
        clientView.name = view.name;

        self.emit('<view', null, clientView);
    });
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the MIT license
 * If a copy of the MIT license was not distributed with this file, you can
 * obtain one at http://www.mozillapopcorn.org/butter-license.txt */

define( [], function(){
  var ZOOM_LEVELS = 6;

  return function( zoomCallback ){

    var _element = document.createElement( "div" ),
        _handle = document.createElement( "div" ),
        _rect,
        _mousePos,
        _handleWidth,
        _elementWidth,
        _this = this;

    _element.className = "zoom-bar scroll-bar";
    _handle.className = "scroll-handle";
    _handle.title = "Change the zoom level of the timeline";

    _element.appendChild( _handle );

    function onMouseUp(){
      window.removeEventListener( "mouseup", onMouseUp, false );
      window.removeEventListener( "mousemove", onMouseMove, false );
      _handle.addEventListener( "mousedown", onMouseDown, false );
      zoomCallback( _handle.offsetLeft / ( _rect.width - _handle.clientWidth ) );
    } //onMouseUp

    function onMouseMove( e ){
      var diff = e.pageX - _mousePos;
      diff = Math.max( 0, Math.min( diff, _elementWidth - _handleWidth ) );
      _handle.style.left = diff + "px";
      zoomCallback( _handle.offsetLeft / ( _rect.width - _handle.clientWidth ) );
    } //onMouseMove

    function onMouseDown( e ){
      if( e.button === 0 ){
        var handleX = _handle.offsetLeft;
        _mousePos = e.pageX - handleX;
        window.addEventListener( "mouseup", onMouseUp, false );
        window.addEventListener( "mousemove", onMouseMove, false );
        _handle.removeEventListener( "mousedown", onMouseDown, false );
      } //if
    } //onMouseDown

    _handle.addEventListener( "mousedown", onMouseDown, false );

    this.update = function( level ) {
      _rect = _element.getBoundingClientRect();
      _handleWidth = ( _rect.width / ZOOM_LEVELS );
      _handle.style.width = _handleWidth + "px";
      _elementWidth = _rect.width;
      if( level !== undefined ){
        _handle.style.left = ( _rect.width - _handle.clientWidth ) * level + "px"; //( level * _handleWidth ) + "px";
        zoomCallback( _handle.offsetLeft / ( _rect.width - _handle.clientWidth ) );
      } //if
    };

    _element.addEventListener( "click", function( e ) {
      // bail early if this event is coming from the handle
      if( e.srcElement === _handle ) {
        return;
      }

      var posX = e.pageX,
          handleRect = _handle.getBoundingClientRect(),
          elementRect = _element.getBoundingClientRect();

      if( posX > handleRect.right ) {
        _handle.style.left = ( ( posX - elementRect.left ) - _handleWidth ) + "px";
      } else {
        _handle.style.left = posX - elementRect.left + "px";
      }

      onMouseMove( e );
    }, false);

    _element.addEventListener( "resize", function( e ){
      _this.update();
    }, false );

    Object.defineProperties( this, {
      element: {
        enumerable: true,
        get: function(){
          return _element;
        }
      }
    });

  }; //ZoomBar
});
<commit_msg>[#435] removed comment.<commit_after>/* This Source Code Form is subject to the terms of the MIT license
 * If a copy of the MIT license was not distributed with this file, you can
 * obtain one at http://www.mozillapopcorn.org/butter-license.txt */

define( [], function(){
  var ZOOM_LEVELS = 6;

  return function( zoomCallback ){

    var _element = document.createElement( "div" ),
        _handle = document.createElement( "div" ),
        _rect,
        _mousePos,
        _handleWidth,
        _elementWidth,
        _this = this;

    _element.className = "zoom-bar scroll-bar";
    _handle.className = "scroll-handle";
    _handle.title = "Change the zoom level of the timeline";

    _element.appendChild( _handle );

    function onMouseUp(){
      window.removeEventListener( "mouseup", onMouseUp, false );
      window.removeEventListener( "mousemove", onMouseMove, false );
      _handle.addEventListener( "mousedown", onMouseDown, false );
      zoomCallback( _handle.offsetLeft / ( _rect.width - _handle.clientWidth ) );
    } //onMouseUp

    function onMouseMove( e ){
      var diff = e.pageX - _mousePos;
      diff = Math.max( 0, Math.min( diff, _elementWidth - _handleWidth ) );
      _handle.style.left = diff + "px";
      zoomCallback( _handle.offsetLeft / ( _rect.width - _handle.clientWidth ) );
    } //onMouseMove

    function onMouseDown( e ){
      if( e.button === 0 ){
        var handleX = _handle.offsetLeft;
        _mousePos = e.pageX - handleX;
        window.addEventListener( "mouseup", onMouseUp, false );
        window.addEventListener( "mousemove", onMouseMove, false );
        _handle.removeEventListener( "mousedown", onMouseDown, false );
      } //if
    } //onMouseDown

    _handle.addEventListener( "mousedown", onMouseDown, false );

    this.update = function( level ) {
      _rect = _element.getBoundingClientRect();
      _handleWidth = ( _rect.width / ZOOM_LEVELS );
      _handle.style.width = _handleWidth + "px";
      _elementWidth = _rect.width;
      if( level !== undefined ){
        _handle.style.left = ( _rect.width - _handle.clientWidth ) * level + "px";
        zoomCallback( _handle.offsetLeft / ( _rect.width - _handle.clientWidth ) );
      } //if
    };

    _element.addEventListener( "click", function( e ) {
      // bail early if this event is coming from the handle
      if( e.srcElement === _handle ) {
        return;
      }

      var posX = e.pageX,
          handleRect = _handle.getBoundingClientRect(),
          elementRect = _element.getBoundingClientRect();

      if( posX > handleRect.right ) {
        _handle.style.left = ( ( posX - elementRect.left ) - _handleWidth ) + "px";
      } else {
        _handle.style.left = posX - elementRect.left + "px";
      }

      onMouseMove( e );
    }, false);

    _element.addEventListener( "resize", function( e ){
      _this.update();
    }, false );

    Object.defineProperties( this, {
      element: {
        enumerable: true,
        get: function(){
          return _element;
        }
      }
    });

  }; //ZoomBar
});
<|endoftext|>


<commit_before>/**
 * ER-Track
 * Copyright 2013 Baidu Inc. All rights reserved.
 *
 * @file 百度统计追踪器
 * @author otakustay
 */
define(
    function (require) {
        function logLine(entries) {
            if (!window.console) {
                return;
            }

            // IE的`console.log`不是函数，不支持`apply`，且不支持多个参数
            if (typeof console.log === 'function') {
                console.log.apply(console, entries);
            }
            else {
                console.log(entries.join(' '));
            }
        }

        function logWithGroup(groupName) {
            if (!window.console) {
                return;
            }

            if (console.groupCollapsed) {
                console.groupCollapsed(groupName);
                for (var i = 1; i < arguments.length; i++) {
                    logLine(arguments[i]);
                }
                console.groupEnd(groupName);
            }
            else {
                console.log('➤' + groupName);
                var prefix = '├───';
                for (var i = 1; i < arguments.length; i++) {
                    if (i === arguments.length - 1) {
                        prefix = '└───';
                    }
                    var entry = arguments[i];
                    if (typeof entry === 'string') {
                        entry = prefix + entry;
                    }
                    else {
                        entry[0] = prefix + entry[0];
                    }
                    logLine(entry);
                }
            }
        }

        var exports = { name: 'console' };

        exports.create = function (config) {
            return {
                name: 'console',

                trackException: function (context) {
                    var entries = [
                        '我靠，有个Promise出异常了',
                        ['出事的Deferred对象: ', context.deferred],
                        ['出事时给的参数: ', context.args],
                        ['出事的原因大概是: ' + context.reason]
                    ];
                    if (context.reason instanceof Error) {
                        entries.push(
                            '好像是一个异常对象，所以把调用堆栈给你看看好了：\n'
                                + context.reason.stack
                        );
                    }
                    logWithGroup.apply(null, entries);
                },

                trackEnterAction: function (context) {
                    var model = context.action.model;
                    var view = context.action.view;

                    logWithGroup(
                        '亲你正在进入"' + context.url + '"',
                        ['Action：', context.action],
                        ['Model：', model],
                        ['Model里的数据：', model && model.dump()],
                        ['View：', view],
                        ['DOM容器：', view && view.getContainerElement()]
                    );
                },

                trackLeaveAction: function (context) {
                    logWithGroup(
                        '亲你已经离开"' + context.action.context.url + '"',
                        ['当前的Action：', context.action],
                        ['前往的URL：' + context.to.url]
                    );
                },

                load: function (callback) {
                    callback();
                }
            };
        };

        return exports;
    }
);
<commit_msg>fixed apply arguments bug<commit_after>/**
 * ER-Track
 * Copyright 2013 Baidu Inc. All rights reserved.
 *
 * @file 百度统计追踪器
 * @author otakustay
 */
define(
    function (require) {
        function logLine(entries) {
            if (!window.console) {
                return;
            }

            // IE的`console.log`不是函数，不支持`apply`，且不支持多个参数
            if (typeof console.log === 'function') {
                console.log.apply(console, [].concat(entries));
            }
            else {
                console.log(entries.join(' '));
            }
        }

        function logWithGroup(groupName) {
            if (!window.console) {
                return;
            }

            if (console.groupCollapsed) {
                console.groupCollapsed(groupName);
                for (var i = 1; i < arguments.length; i++) {
                    logLine(arguments[i]);
                }
                console.groupEnd(groupName);
            }
            else {
                console.log('➤' + groupName);
                var prefix = '├───';
                for (var i = 1; i < arguments.length; i++) {
                    if (i === arguments.length - 1) {
                        prefix = '└───';
                    }
                    var entry = arguments[i];
                    if (typeof entry === 'string') {
                        entry = prefix + entry;
                    }
                    else {
                        entry[0] = prefix + entry[0];
                    }
                    logLine(entry);
                }
            }
        }

        var exports = { name: 'console' };

        exports.create = function (config) {
            return {
                name: 'console',

                trackException: function (context) {
                    var entries = [
                        '我靠，有个Promise出异常了',
                        ['出事的Deferred对象: ', context.deferred],
                        ['出事时给的参数: ', context.args],
                        ['出事的原因大概是: ' + context.reason]
                    ];
                    if (context.reason instanceof Error) {
                        entries.push(
                            '好像是一个异常对象，所以把调用堆栈给你看看好了：\n'
                                + context.reason.stack
                        );
                    }
                    logWithGroup.apply(null, entries);
                },

                trackEnterAction: function (context) {
                    var model = context.action.model;
                    var view = context.action.view;

                    logWithGroup(
                        '亲你正在进入"' + context.url + '"',
                        ['Action：', context.action],
                        ['Model：', model],
                        ['Model里的数据：', model && model.dump()],
                        ['View：', view],
                        ['DOM容器：', view && view.getContainerElement()]
                    );
                },

                trackLeaveAction: function (context) {
                    logWithGroup(
                        '亲你已经离开"' + context.action.context.url + '"',
                        ['当前的Action：', context.action],
                        ['前往的URL：' + context.to.url]
                    );
                },

                load: function (callback) {
                    callback();
                }
            };
        };

        return exports;
    }
);
<|endoftext|>


<commit_before>;(function (root) {

  'use strict';

  root.OpenEd = root.OpenEd || {};

  root.OpenEd.api = {

    tokenPrefix: '_openEd',

    apiHost: 'https://api.opened.io',

    openedHost: 'https://www.opened.com',

    _events: {},

    on: function (eventName, callback) {
      if (eventName && typeof callback === 'function') {
        this._events[eventName] = this._events[eventName] || [];
        this._events[eventName].push(callback);
      }
    },

    off: function (eventName, callback) {
      var events = this._events[eventName];

      if (events && typeof callback === 'function') {
        var callbackIndex = events.indexOf(callback);
        if (callbackIndex !== -1) {
          events.slice(callbackIndex, 1);
        }
      }
    },

    trigger: function () {
      var args = Array.prototype.slice.call(arguments);
      var eventName = args.slice(0)[0];
      var eventsArr = this._events[eventName];
      var data = args.slice(1);
      if (eventsArr && eventsArr.length) {
        eventsArr.forEach(function (callback) {
          callback.apply(null, data);
        });
      }
    },

    init: function (options, callback) {
      if (!options || !options.client_id) {
        throw new Error('Bad init options.');
      }
      this.options = options;
      var token = this.getToken();
      if (token && options.status) {
        //check logged in status
        var self = this;
        this.checkLoginStatus(function () {
          self.trigger('auth.userLoggedIn');
          callback && callback();
        });
      } else {
        callback && callback();
      }
    },

    runOnInit: function () {
      root.OpenEd.oninit && root.OpenEd.oninit()
    },

    silentLogin: function(signedRequest, callback, errorCallback) {
      var self = this;
      self.xhr({
        type: 'POST',
        url: self.apiHost + '/oauth/silent_login',
        raw_data: signedRequest,
        success: function(data){
          self.saveToken(data);
          callback && callback();
        },
        error: errorCallback
      }, errorCallback);
    },

    login: function (callback) {
      this._lastCallback = callback;
      var params = '?mode=implict';
      var self = this;
      ['client_id', 'redirect_uri'].forEach(function (paramName) {
        var paramValue = self.options[paramName];
        if (paramValue) {
          params += '&' + paramName + '=' + paramValue
        }
      });
      var popup = window.open(this.openedHost + '/oauth/authorize' + params, '_blank', 'width=500, height=300');
      popup.focus && popup.focus();
    },

    logout: function (callback) {
      var self = this;
      var token = self.getToken();
      if (token) {
        this.xhr({
          type: 'POST',
          url: self.apiHost + '/oauth/revoke',
          data: {token: token},
          success: function () {
            self.resetToken();
            callback && callback();
          },
          error: function () {
            self.resetToken();
            callback && callback();
          },
          headers: {
            Authorization: 'Bearer ' + self.getToken()
          }
        });
      } else {
        self.resetToken();
        callback && callback();
      }
    },

    resetToken: function () {
      localStorage.removeItem(this.tokenPrefix + '.access_token');
      this.trigger('auth.userSignedOut');
    },

    saveToken: function (tokenData) {
      for (var name in tokenData) {
        localStorage.setItem(this.tokenPrefix + '.' + name, tokenData[name]);
      }
    },

    getToken: function () {
      return localStorage.getItem(this.tokenPrefix + '.access_token');
    },

    xhr: function (options) {
      var xmlhttp=new XMLHttpRequest();
      xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4) {
          if (xmlhttp.status==200) {
            options.success(JSON.parse(xmlhttp.responseText));
          } else if (xmlhttp.status>=400) {
            var error = {error: 'Unknown error'};
            if (xmlhttp.responseText) {
              error = JSON.parse(xmlhttp.responseText)
            } else if (xmlhttp.statusText) {
              error = JSON.parse(xmlhttp.statusText)
            }
            options.error && options.error(error);
          }
        }
      };
      var type = options.type || 'GET';
      var url = options.url;
      if (type === 'GET') {
        var urlData = '?';
        var params = [];
        for (var a in options.data) {
          params.push(a + '=' + options.data[a]);
        }
        if (params.length) {
          url += '?' + (params.join('&'));
        }
      }
      xmlhttp.open(type, url, true);

      if (type === 'POST') {
        if (options.raw_data){
          xmlhttp.setRequestHeader('Content-Type', 'text/plain');
        }else{
          xmlhttp.setRequestHeader('Content-Type', 'application/json');
        }
      }

      if (options.headers) {
        for (var name in options.headers) {
          xmlhttp.setRequestHeader(name, options.headers[name]);
        }

      }
      if (type !== 'GET') {
        if(options.raw_data){
          xmlhttp.send(options.raw_data);
        }else{
          xmlhttp.send(this.prepareReqData(options.data));
        }
      } else {
        xmlhttp.send();
      }
    },

    request: function (api, data, callback, errorCallback) {
      var self = this;
      data.access_token = self.getToken();
      this.xhr({
        url: this.apiHost + api,
        data: data,
        success: callback,
        error: errorCallback,
        headers: {
          Authorization: 'Bearer ' + self.getToken()
        }
      });
    },

    verifyToken: function (callback) {
      var self = this;
      if (this.checkTokenDate()) {
        this.request('/oauth/token/info', null, function (token) {
          if (token && token.application && token.application.uid && token.application.uid === self.options.client_id) {
            callback();
          } else {
            self.resetToken();
            callback(new Error('Wrong client id'));
          }
        }, function (error) {
          self.resetToken();
          callback(error);
        });
      } else {
        callback(new Error('token has expired'));
      }
    },

    prepareReqData: function (data) {
      return JSON.stringify(data);
    },

    checkTokenDate: function () {
      var tokenDate = new Date(parseInt(localStorage.getItem(this.tokenPrefix + '.expires_in'), 10));
      var now = this.now();
      return now.getTime() < tokenDate.getTime();
    },

    expireDate: function (expiresIn) {
      var date = this.now();
      date.setTime(date.getTime() + (parseInt(expiresIn) * 1000, 10));
      return date;
    },

    now: function () {
      return new Date();
    },

    checkLoginStatus: function (callback) {
      this.verifyToken(function (err) {
        if (err) {
          callback(err);
        } else {
          callback();
        }
      });
    },

    _setToken: function (token) {
      this.saveToken(this.parseToken(token));
      var self = this;
      this.verifyToken(function (err) {
        if (!err) {
          self.trigger('auth.userLoggedIn', token);
        }
        self._lastCallback && self._lastCallback(err);
      })

    },

    parseToken: function (token) {
      var params = token.substr(1);
      var result = {};
      params.split('&').forEach(function (paramPairStr) {
        var paramPair = paramPairStr.split('=');
        result[paramPair[0]] = paramPair[1];
      });
      if (result.expires_in) {
        result.expires_in = this.expireDate(result.expires_in).getTime();
      }
      return result;
    }

  };

  root.OpenEd.api.runOnInit();

})(this);
<commit_msg>simplify trigger and improve readability<commit_after>;(function (root) {

  'use strict';

  root.OpenEd = root.OpenEd || {};

  root.OpenEd.api = {

    tokenPrefix: '_openEd',

    apiHost: 'https://api.opened.io',

    openedHost: 'https://www.opened.com',

    _events: {},

    on: function (eventName, callback) {
      if (eventName && typeof callback === 'function') {
        this._events[eventName] = this._events[eventName] || [];
        this._events[eventName].push(callback);
      }
    },

    off: function (eventName, callback) {
      var eventsArr = this._events[eventName];

      if (eventsArr && typeof callback === 'function') {
        var callbackIndex = eventsArr.indexOf(callback);
        if (callbackIndex !== -1) {
          eventsArr.slice(callbackIndex, 1);
        }
      }
    },

    trigger: function (eventName) {
      var data = Array.prototype.slice.call(arguments).shift(),
          eventsArr = this._events[eventName] || [];

        eventsArr.forEach(function (callback) {
          callback.apply(null, data);
        });
    },

    init: function (options, callback) {
      if (!options || !options.client_id) {
        throw new Error('Bad init options.');
      }
      this.options = options;
      var token = this.getToken();
      if (token && options.status) {
        //check logged in status
        var self = this;
        this.checkLoginStatus(function () {
          self.trigger('auth.userLoggedIn');
          callback && callback();
        });
      } else {
        callback && callback();
      }
    },

    runOnInit: function () {
      root.OpenEd.oninit && root.OpenEd.oninit()
    },

    silentLogin: function(signedRequest, callback, errorCallback) {
      var self = this;
      self.xhr({
        type: 'POST',
        url: self.apiHost + '/oauth/silent_login',
        raw_data: signedRequest,
        success: function(data){
          self.saveToken(data);
          callback && callback();
        },
        error: errorCallback
      }, errorCallback);
    },

    login: function (callback) {
      this._lastCallback = callback;
      var params = '?mode=implict';
      var self = this;
      ['client_id', 'redirect_uri'].forEach(function (paramName) {
        var paramValue = self.options[paramName];
        if (paramValue) {
          params += '&' + paramName + '=' + paramValue
        }
      });
      var popup = window.open(this.openedHost + '/oauth/authorize' + params, '_blank', 'width=500, height=300');
      popup.focus && popup.focus();
    },

    logout: function (callback) {
      var self = this;
      var token = self.getToken();
      if (token) {
        this.xhr({
          type: 'POST',
          url: self.apiHost + '/oauth/revoke',
          data: {token: token},
          success: function () {
            self.resetToken();
            callback && callback();
          },
          error: function () {
            self.resetToken();
            callback && callback();
          },
          headers: {
            Authorization: 'Bearer ' + self.getToken()
          }
        });
      } else {
        self.resetToken();
        callback && callback();
      }
    },

    resetToken: function () {
      localStorage.removeItem(this.tokenPrefix + '.access_token');
      this.trigger('auth.userSignedOut');
    },

    saveToken: function (tokenData) {
      for (var name in tokenData) {
        localStorage.setItem(this.tokenPrefix + '.' + name, tokenData[name]);
      }
    },

    getToken: function () {
      return localStorage.getItem(this.tokenPrefix + '.access_token');
    },

    xhr: function (options) {
      var xmlhttp=new XMLHttpRequest();
      xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4) {
          if (xmlhttp.status==200) {
            options.success(JSON.parse(xmlhttp.responseText));
          } else if (xmlhttp.status>=400) {
            var error = {error: 'Unknown error'};
            if (xmlhttp.responseText) {
              error = JSON.parse(xmlhttp.responseText)
            } else if (xmlhttp.statusText) {
              error = JSON.parse(xmlhttp.statusText)
            }
            options.error && options.error(error);
          }
        }
      };
      var type = options.type || 'GET';
      var url = options.url;
      if (type === 'GET') {
        var urlData = '?';
        var params = [];
        for (var a in options.data) {
          params.push(a + '=' + options.data[a]);
        }
        if (params.length) {
          url += '?' + (params.join('&'));
        }
      }
      xmlhttp.open(type, url, true);

      if (type === 'POST') {
        if (options.raw_data){
          xmlhttp.setRequestHeader('Content-Type', 'text/plain');
        }else{
          xmlhttp.setRequestHeader('Content-Type', 'application/json');
        }
      }

      if (options.headers) {
        for (var name in options.headers) {
          xmlhttp.setRequestHeader(name, options.headers[name]);
        }

      }
      if (type !== 'GET') {
        if(options.raw_data){
          xmlhttp.send(options.raw_data);
        }else{
          xmlhttp.send(this.prepareReqData(options.data));
        }
      } else {
        xmlhttp.send();
      }
    },

    request: function (api, data, callback, errorCallback) {
      var self = this;
      data.access_token = self.getToken();
      this.xhr({
        url: this.apiHost + api,
        data: data,
        success: callback,
        error: errorCallback,
        headers: {
          Authorization: 'Bearer ' + self.getToken()
        }
      });
    },

    verifyToken: function (callback) {
      var self = this;
      if (this.checkTokenDate()) {
        this.request('/oauth/token/info', null, function (token) {
          if (token && token.application && token.application.uid && token.application.uid === self.options.client_id) {
            callback();
          } else {
            self.resetToken();
            callback(new Error('Wrong client id'));
          }
        }, function (error) {
          self.resetToken();
          callback(error);
        });
      } else {
        callback(new Error('token has expired'));
      }
    },

    prepareReqData: function (data) {
      return JSON.stringify(data);
    },

    checkTokenDate: function () {
      var tokenDate = new Date(parseInt(localStorage.getItem(this.tokenPrefix + '.expires_in'), 10));
      var now = this.now();
      return now.getTime() < tokenDate.getTime();
    },

    expireDate: function (expiresIn) {
      var date = this.now();
      date.setTime(date.getTime() + (parseInt(expiresIn) * 1000, 10));
      return date;
    },

    now: function () {
      return new Date();
    },

    checkLoginStatus: function (callback) {
      this.verifyToken(function (err) {
        if (err) {
          callback(err);
        } else {
          callback();
        }
      });
    },

    _setToken: function (token) {
      this.saveToken(this.parseToken(token));
      var self = this;
      this.verifyToken(function (err) {
        if (!err) {
          self.trigger('auth.userLoggedIn', token);
        }
        self._lastCallback && self._lastCallback(err);
      })

    },

    parseToken: function (token) {
      var params = token.substr(1);
      var result = {};
      params.split('&').forEach(function (paramPairStr) {
        var paramPair = paramPairStr.split('=');
        result[paramPair[0]] = paramPair[1];
      });
      if (result.expires_in) {
        result.expires_in = this.expireDate(result.expires_in).getTime();
      }
      return result;
    }

  };

  root.OpenEd.api.runOnInit();

})(this);
<|endoftext|>


<commit_before>/*!
 * wwwdude.js an abstraction layer for http client stuff
 *
 * Copyright(c) 2011 Sven Pfleiderer.
 * MIT Licensed
 *
 * @author pfleidi
 */

/* Module dependencies */

var Sys = require('sys');
var Url = require('url');
var EventEmitter = require('events').EventEmitter;
var Child_process = require('child_process');

var Util = require('./util');
var statusCodes = Util.codes;
var nodeVersion = process.version.slice(0, 6).replace(/\d$/, 'x');
var NodeWrapper = require('./node-versions/' + nodeVersion);

var defaultHeaders = {
  'User-Agent': 'node-wwwdude'
};

exports.version = '0.0.7';

/**
 * Factory method to create a client
 *
 * @return {Object}
 * @api public
 */
var createClient = exports.createClient = function createClient(clientOptions) {
  clientOptions = clientOptions || {};

  var encoding = clientOptions.encoding || 'utf8';
  var clientHeaders = Util.mergeHeaders(defaultHeaders, clientOptions.headers);

  if (clientOptions.gzip) {
    clientHeaders['Accept-Encoding'] = 'gzip';
  }

  /**
   * Execute HTTP request
   *
   * @param {String} method
   * @param {String} url
   * @param {Object} options
   *
   * @return {Object}
   * @api private
   */
  function makeRequest(method, url, options) {

    var parsedUrl = Util.parseUrl(url);
    var emitter = new EventEmitter();

    /**
     * Emit events to the correct request object
     *
     * @param {Object} type
     * @param {Object} data
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _respond(type, data, response) {
      if (options.originalRequest) {
        options.originalRequest.emit(type, data, response);
      } else {
        emitter.emit(type, data, response);
      }
    }

    /**
     * Redirect to next location if feature is enabled
     *
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _redirect(response, method) {
      if (clientOptions.followRedirect !== false) {
        var nextLocation = Url.resolve(url, response.headers.location);
        options.originalUrl = url;
        options.originalRequest = emitter;
        makeRequest(method, nextLocation, options);
      }
    }

    /**
     * Emit events based on HTTP status codes
     *
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _handleResponse(response) {
      var code = response.statusCode;
      var data = response.rawData;

      _respond(code.toString().replace(/\d{2}$/, 'XX'), data, response);
      _respond(code.toString(), data, response);
      _respond(statusCodes[code.toString()], data, response);

      if (code >= 400) {
        _respond('http-error', data, response);
        if (code < 500) {
          _respond('http-client-error', data, response);
        } else {
          _respond('http-server-error', data, response);
        }
      } else if (code >= 300) {
        _respond('redirect', data, response);
        if (code === 301 || code === 302) {
          _redirect(response, method);
        } else if (code === 303) {
          _redirect(response, 'get');
        }
      } else if (code >= 200) {
        _respond('success', data, response);
      }

      _respond('complete', data, response);
    }

    /**
     * Decode gzip content with an external gunzip command
     * call request._handleResponse() when ready
     *
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _decodeGzip(response) {
      var body = '';

      var gunzip = Child_process.spawn('gunzip', ['-9']);

      gunzip.stdout.on('data', function (data) {
          body += data;
        });

      gunzip.on('exit', function (code) {
          response.rawData = body;
          _handleResponse(response);
        });

      response.on('data', function (chunk) {
          gunzip.stdin.write(chunk, 'binary');
        });

      response.on('end', function () {
          gunzip.stdin.end();
        });
    }

    /**
     * Handle responses of executed HTTP requests
     * delegate to request._decodeGzip() if needed
     *
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _responseHandler(response) {
      var body = '';
      var useGzip = /gzip/.test(response.headers['content-encoding']);

      if (useGzip) {
        _decodeGzip(response);
        return;
      }

      response.setEncoding('utf8');

      response.on('data', function (chunk) {
          body += chunk;
        });

      response.on('end', function () {
          response.rawData = body;
          _handleResponse(response);
        });
    }

    NodeWrapper.request({
        method: method,
        url: parsedUrl,
        options: options,
        respond: _respond,
        encoding: encoding,
        handler: _responseHandler
      });

    return emitter;
  }

  /* return the actual API */
  return {

    get: function get(url, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('GET', url, { headers: headers });
    },

    post: function post(url, payload, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('POST', url, { headers: headers, payload: payload });
    },

    put: function put(url, payload, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('PUT', url, { headers: headers, payload: payload });
    },

    del: function del(url, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('DELETE', url, { headers: headers });
    },

    head: function head(url, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('HEAD', url, { headers: headers });
    }

  };

};

/*
 * add support for require('wwwdude')(option)
 * additionally to
 * require('wwwdude').createClient(options)
 */
createClient.createClient = createClient;
module.exports = createClient;
<commit_msg>Implement simple content parsing mechanism<commit_after>/*!
 * wwwdude.js an abstraction layer for http client stuff
 *
 * Copyright(c) 2011 Sven Pfleiderer.
 * MIT Licensed
 *
 * @author pfleidi
 */

/* Module dependencies */

var Url = require('url');
var EventEmitter = require('events').EventEmitter;
var Child_process = require('child_process');

var Util = require('./util');
var statusCodes = Util.codes;
var nodeVersion = process.version.slice(0, 6).replace(/\d$/, 'x');
var NodeWrapper = require('./node-versions/' + nodeVersion);

var defaultHeaders = {
  'User-Agent': 'node-wwwdude'
};

exports.version = '0.0.7';

/**
 * Factory method to create a client
 *
 * @return {Object}
 * @api public
 */
var createClient = exports.createClient = function createClient(clientOptions) {
  clientOptions = clientOptions || {};

  var encoding = clientOptions.encoding || 'utf8';
  var clientHeaders = Util.mergeHeaders(defaultHeaders, clientOptions.headers);

  if (clientOptions.gzip) {
    clientHeaders['Accept-Encoding'] = 'gzip';
  }

  /**
   * Execute HTTP request
   *
   * @param {String} method
   * @param {String} url
   * @param {Object} options
   *
   * @return {Object}
   * @api private
   */
  function makeRequest(method, url, options) {

    var parsedUrl = Util.parseUrl(url);
    var emitter = new EventEmitter();

    /**
     * Emit events to the correct request object
     *
     * @param {Object} type
     * @param {Object} data
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _respond(type, data, response) {
      if (options.originalRequest) {
        options.originalRequest.emit(type, data, response);
      } else {
        emitter.emit(type, data, response);
      }
    }

    /**
     * Redirect to next location if feature is enabled
     *
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _redirect(response, method) {
      if (clientOptions.followRedirect !== false) {
        var nextLocation = Url.resolve(url, response.headers.location);
        options.originalUrl = url;
        options.originalRequest = emitter;
        makeRequest(method, nextLocation, options);
      }
    }

    /**
     * Emit events based on HTTP status codes
     *
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _handleResponse(response) {
      var code = response.statusCode;
      var parser = clientOptions.contentParser;
      // TODO: proper error-handling on parse errors
      var data = parser ? parser(response.rawData) : response.rawData;

      _respond(code.toString().replace(/\d{2}$/, 'XX'), data, response);
      _respond(code.toString(), data, response);
      _respond(statusCodes[code.toString()], data, response);

      if (code >= 400) {
        _respond('http-error', data, response);
        if (code < 500) {
          _respond('http-client-error', data, response);
        } else {
          _respond('http-server-error', data, response);
        }
      } else if (code >= 300) {
        _respond('redirect', data, response);
        if (code === 301 || code === 302) {
          _redirect(response, method);
        } else if (code === 303) {
          _redirect(response, 'get');
        }
      } else if (code >= 200) {
        _respond('success', data, response);
      }

      _respond('complete', data, response);
    }

    /**
     * Decode gzip content with an external gunzip command
     * call request._handleResponse() when ready
     *
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _decodeGzip(response) {
      var body = '';
      var gunzip = Child_process.spawn('gunzip', ['-9']);

      response.on('data', function (chunk) {
          gunzip.stdin.write(chunk, 'binary');
        });

      response.on('end', function () {
          gunzip.stdin.end();
        });

      gunzip.stdout.on('data', function (data) {
          body += data;
        });

      gunzip.on('exit', function (code) {
          response.rawData = body;
          _handleResponse(response);
        });
    }

    /**
     * Handle responses of executed HTTP requests
     * delegate to request._decodeGzip() if needed
     *
     * @param {Object} response
     * @return {undefined}
     * @api private
     */
    function _responseHandler(response) {
      var body = '';
      var useGzip = /gzip/.test(response.headers['content-encoding']);

      if (useGzip) {
        _decodeGzip(response);
        return;
      }

      response.setEncoding(encoding);

      response.on('data', function (chunk) {
          body += chunk;
        });

      response.on('end', function () {
          response.rawData = body;
          _handleResponse(response);
        });
    }

    NodeWrapper.request({
        method: method,
        url: parsedUrl,
        options: options,
        respond: _respond,
        encoding: encoding,
        handler: _responseHandler
      });

    return emitter;
  }

  /* return the actual API */
  return {

    get: function get(url, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('GET', url, { headers: headers });
    },

    post: function post(url, payload, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('POST', url, { headers: headers, payload: payload });
    },

    put: function put(url, payload, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('PUT', url, { headers: headers, payload: payload });
    },

    del: function del(url, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('DELETE', url, { headers: headers });
    },

    head: function head(url, requestHeaders) {
      var headers = Util.mergeHeaders(clientHeaders, requestHeaders);
      return makeRequest('HEAD', url, { headers: headers });
    }

  };

};

/*
 * add support for require('wwwdude')(option)
 * additionally to
 * require('wwwdude').createClient(options)
 */
createClient.createClient = createClient;
module.exports = createClient;
<|endoftext|>