<commit_before>/**
 * A job takes care of a branch on a repo.
 * Its config is stored in workspace/name_of_job.js
 * The corresponding Git repo used for tests is workspace/name_of_job
 *
 */
var config = require('./config')
  , fs = require('fs')
  , app = require('../app')
  , customUtils = require('./customUtils')
  , validation = require('./validation')
  , childProcess = require('child_process')
  , spawn = require('child_process').spawn
  , async = require('async')
  ;

/**
 * Constructor. Not to be called directly, use Job.getJob instead.
 */
function Job (options) {
  var keys = Object.keys(options)
    , i;

  for (i = 0; i < keys.length; i += 1) {
    this[keys[i]] = options[keys[i]];
  }
}

/**
 * Pull the repo, and create it if it doesn't exist
 * @param {WritableStream} out Stream were everything that happens needs to be logged
 * @param {Function} callback Required callback
 */
Job.prototype.pullRepo = function (out, callback) {
  var self = this;

  if (! this.name) { return callback("This job doesn't seem to have a name ..."); }

  async.waterfall([
    function (cb) {   // Ensure the repo exists, create it if needed
      customUtils.ensureFolderExists(Job.getRepoPath(self.name), function (err) {
        if (err) { return cb(err); }

        fs.readdir(Job.getRepoPath(self.name), function (err, files) {
          if (err) { return cb(err); }
          if (files.length > 0) { return cb(); }   // Files were found, we assume it's a Git repo. TODO: check for .git folder

          childProcess.exec('git clone ' + self.repoSSHUrl + ' .', {  cwd: Job.getRepoPath(self.name)  }, function (err, stdout, stderr) {
            out && out.write("=== First build, cloning the repository ===\n");
            out && out.write(stdout);
            out && out.write(stderr);
            return cb(err);
          })
        });
      });
    }
  , function (cb) {   // Pull it (will fail if branch doesn't exist)
      childProcess.exec('set -e; git checkout ' + self.branch + '; git pull', {  cwd: Job.getRepoPath(self.name)  }, function (err, stdout, stderr) {
        out && out.write(stdout);
        out && out.write(stderr);
        return cb(err);
      })
    }
  ], callback);
};

/**
 * Check if we need to reinstall the dependencies, that's what takes most of the build time
 * @param {WritableStream} out Stream were everything that happens needs to be logged
 * @param {Function} callback Required callback. Signature: err, true/false
 */
Job.prototype.checkIfDependenciesNeedToBeReinstalled = function(out, callback) {
  var self = this;
  fs.readFile(Job.getRepoPath(self.name) + '/.gitignore', 'utf8', function (err, data) {
    var lines = data.split('\n')
      , nodeModulesIsGitignored = false;

    lines.forEach(function (line) {
      if (line.match(/^\/?node_modules\/?$/)) {
        nodeModulesIsGitignored = true;
      }
    });

    if (!nodeModulesIsGitignored) {
      out.write("=== node_modules is checked in Git so no need to reinstall them ===\n");
      return callback(null, false);
    }

    customUtils.checkIfFilesAreIdentical(Job.getDependenciesInfoDir(self.name) + '/npm-shrinkwrap.json', Job.getRepoPath(self.name) + '/npm-shrinkwrap.json', function (err, sameN) {
      if (err) { return callback(err); }
      customUtils.checkIfFilesAreIdentical(Job.getDependenciesInfoDir(self.name) + '/package.json', Job.getRepoPath(self.name) + '/package.json', function (err, sameP) {
        if (err) { return callback(err); }
        if (sameN && sameP) {
          out.write("=== node_modules is gitignored but package.json and npm-shrinkwrap.json didn't change, no need to reinstall ===\n");
          return callback(null, false);
        } else {
          out.write("=== node_modules is gitignored and package.json or npm-shrinkwrap.json changed, we need to reinstall ===\n");
          return callback(null, true);
        }
      });
    });
  });
};

/**
 * (Re)install the dependencies
 * @param {WritableStream} out Stream were everything that happens needs to be logged
 * @param {Function} callback Required callback
 */
Job.prototype.reinstallDependencies = function (out, callback) {
  var self = this, installer;

  if (! this.name) { return callback("This job doesn't seem to have a name ..."); }

  async.waterfall([
    function (cb) {   // Reinstall
    childProcess.exec('rm -rf node_modules', { cwd: Job.getRepoPath(self.name) }, function (err, stdout, stderr) {
      if (err) { return cb(err); }

      installer = childProcess.spawn('npm', ['install'], { cwd: Job.getRepoPath(self.name) });
      out && installer.stdout.pipe(out, { end: false });
      out && installer.stderr.pipe(out, { end: false });

      installer.on('exit', function (code) {
        var error = code === 0 ? null : "Couldn't reinstall dependencies";
        cb(error);
      });
    });
  }
  , function (cb) {   // Copy dependency files to remember them next time we check if need to reinstall
    customUtils.ensureFolderExists(Job.getDependenciesInfoDir(self.name), function (err) {
      if (err) { return cb(err); }
      customUtils.copySafe(Job.getRepoPath(self.name) + '/npm-shrinkwrap.json', Job.getDependenciesInfoDir(self.name) + '/npm-shrinkwrap.json', function (err) {
        if (err) { return cb(err); }
        customUtils.copySafe(Job.getRepoPath(self.name) + '/package.json', Job.getDependenciesInfoDir(self.name) + '/package.json', function (err) {
          return cb(err);
        });
      });
    });
  }
  ], callback);
};

/**
 * Run the tests
 * @param {WritableStream} out Stream were everything that happens needs to be logged
 * @param {Function} callback Required callback
 */
Job.prototype.runTests = function (out, callback) {
  var self = this, tester;

  if (! this.name) { return callback("This job doesn't seem to have a name ..."); }

  tester = childProcess.spawn('npm', ['test'], { cwd: Job.getRepoPath(self.name) });
  out && tester.stdout.pipe(out, { end: false });

  tester.on('exit', function (code) {
    var error = code === 0 ? null : 'Tests failed';
    callback(error);
  });
};

/**
 * Advertise the result of a build on hipchat
 */
Job.prototype.advertiseOnHipchat = function (buildSuccessful) {
  var messageToSend = { room_id: 'Deployment'
                      , from: 'Braindead CI'
                      , message_format: 'html'
                      , notify: buildSuccessful ? 0 : 1
                      , color: buildSuccessful ? 'green' : 'red'
                      };

  if (buildSuccessful) {
    messageToSend.message = this.name + " - Build and deploy successful";
  } else {
    messageToSend.message = this.name + " - Build and deploy failed";
  }

  customUtils.sendMessageToHipchat(messageToSend);
};

/**
 * Launch a build
 */
Job.prototype.build = function (out, callback) {
  var self = this
    , buildReport
    , channel = new customUtils.PassthroughStream()   // Used to centralize build outputs
    , needToReinstallDeps = false
    ;

  customUtils.ensureFolderExists(Job.getBuildsDir(this.name), function (err) {
    if (err) { return callback(err); }
    buildReport = fs.createWriteStream(Job.getBuildFilename(self.name, self.nextBuildNumber));
    out && channel.pipe(out);
    channel.pipe(process.stdout);
    channel.pipe(buildReport);

    async.waterfall([
      function (cb) {
        channel.write("=== Pulling new code ===\n");
        self.pullRepo(channel, function (err) {
          if (err) { buildReport.write("Couldn't pull the repository, error given: " + err + "\n"); }
          cb(err);
        });
      }
    , function (cb) {
      channel.write("=== Checking if we need to reinstall dependencies ===\n");
      self.checkIfDependenciesNeedToBeReinstalled(channel, function (err, need) {
        if (err) { channel.write("=== Problem while checking if we need to reinstall dependencies, error given: " + err + "\n"); return cb(err); }
        needToReinstallDeps = need;
        return cb();
      });
    }
    , function (cb) {
      if (!needToReinstallDeps) { return cb(); }
      channel.write("=== Reinstalling dependencies ===\n");
      self.reinstallDependencies(channel, function (err) {
        if (err) { channel.write("=== Problem reinstalling dependencies, error given: " + err + "\n"); }
        cb(err);
      });
    }
    , function (cb) {
      channel.write("=== Running tests ===\n");
      var script = "cd " + Job.getRepoPath(self.name).replace(/ /g, '\\ ') + ";" + self.testScript;

      customUtils.executeBashScript(script, null, channel, function (err) {
        if (err) { channel.write("=== Tests didn't pass, error given: " + err + "\n"); }
        cb(err);
      });
    }
    , function (cb) {
      channel.write("=== Deploying ===\n");
      customUtils.executeBashScript(self.deployScript, { REINSTALL_DEPS: needToReinstallDeps }, channel, function (err) {
        return cb(err);
      });
    }
    ], function (err) {
      var buildSuccessful = err ? false : true;

      if (buildSuccessful) {
        channel.write("=== YES! Build and deploy successful! ===\n");
      } else {
        channel.write("=== OH NOES! Something went wrong :( ===\n");
      }

      buildReport.end();
      self.advertiseOnHipchat(buildSuccessful);   // Asynchronously advertise build
      self.previousBuilds[self.nextBuildNumber] = { number: self.nextBuildNumber
                                                  , success: buildSuccessful
                                                  , date: new Date()
                                                  };
      self.nextBuildNumber += 1;
      self.save(function () { callback(err); });
    });
  });
};

/**
 * Get a build's info
 */
Job.prototype.getBuild = function (buildNumber, callback) {
  var self = this;
  if (! self.previousBuilds[buildNumber]) { return callback('Build number ' + buildNumber + ' doesn\'t exist'); }

  fs.readFile(Job.getBuildFilename(self.name, parseInt(buildNumber, 10)), 'utf8', function (err, data) {
    var buildData = {};
    if (err) { return callback('Build number ' + buildNumber + ' doesn\'t exist or data is corrupted'); }

    buildData = self.previousBuilds[buildNumber];
    buildData.log = data;

    return callback(null, buildData);
  });
};

/**
 * Recreate a job from a filesystem conf file
 */
Job.getJob = function (name, callback) {
  if (!name) { return callback("This job doesn't have a name"); }

  Job.loadConfig(name, function (err, conf) {
    var j;
    if (err) { return callback (err); }
    if (!conf) { return callback('Job ' + name + ' not found!'); }

    return callback(null, new Job(conf));
  });
};

/**
 * Get a job from its repo URL
 */

/**
 * Create a new job
 */
Job.createJob = function (options, callback) {
  options.nextBuildNumber = 1;
  options.previousBuilds = {};
  j = new Job(options);
  j.save(function (err) {
    return callback(err, j);
  });
}

/**
 * Edit an existing job
 */
Job.prototype.edit = function (newOptions, callback) {
  var keys = Object.keys(newOptions)
    , i;

  for (i = 0; i < keys.length; i += 1) {
    this[keys[i]] = newOptions[keys[i]];
  }

  this.save(callback);
};

/**
 * Save job state in its config file
 */
Job.prototype.save = function (callback) {
  var self = this
    , toBeSerialized = {}
    , propertiesToSave = [ 'name'
                         , 'githubRepoUrl'
                         , 'repoSSHUrl'
                         , 'branch'
                         , 'nextBuildNumber'
                         , 'previousBuilds'
                         , 'testScript'
                         , 'deployScript'
                         ]
    ;

  if (! this.name) { return callback("This job doesn't seem to have a name ..."); }

  propertiesToSave.forEach(function (prop) { toBeSerialized[prop] = self[prop]; });
  customUtils.ensureFolderExists(Job.getRootDir(self.name), function (err) {
    if (err) { return callback(err); }
    fs.writeFile(Job.getConfigPath(self.name), JSON.stringify(toBeSerialized), 'utf8', callback);
  });
};

/**
 * Try to load a config from the filesystem
 */
Job.loadConfig = function (name, callback) {
  fs.exists(Job.getConfigPath(name), function (exists) {
    if (!exists) { return callback(); }

    fs.readFile(Job.getConfigPath(name), 'utf8', function (err, data) {
      var f, config;

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

      try {
        f = new Function('return ' + data);
        config = f();
      } catch (e) {
        return callback(e);
      }

      return callback(null, config);
    });
  });
};

/**
 * Load all jobs
 * For now we assume a one-to-one mapping between the list of directories and the list of jobs
 * TODO: check if real projects
 */
Job.loadAllJobsNames = function (callback) {
  fs.readdir(config.workspace, function (err, files) {
    var names = [];
    files.forEach(function (file) {
      if (validation.validateJobName(file)) { names.push(file); }
    });
    if (err) {
      return callback(err);
    } else {
      return callback(null, names.sort());
    }
  });
};

/**
 * Functions defining where the data is stored
 */
Job.getRootDir = function (name) { return config.workspace + '/' + name };
Job.getConfigPath = function (name) { return Job.getRootDir(name) + '/settings.conf'; };
Job.getRepoPath = function (name) { return Job.getRootDir(name) + '/repo'; };
Job.getBuildsDir = function (name) { return Job.getRootDir(name) + '/builds'; };
Job.getBuildFilename = function (name, buildNumber) { return Job.getBuildsDir(name) + '/build' + buildNumber + '.log'; };
Job.getDependenciesInfoDir = function (name) { return Job.getRootDir(name) + '/dependencies'; };

module.exports = Job;
<commit_msg>Link to build<commit_after>/**
 * A job takes care of a branch on a repo.
 * Its config is stored in workspace/name_of_job.js
 * The corresponding Git repo used for tests is workspace/name_of_job
 *
 */
var config = require('./config')
  , fs = require('fs')
  , app = require('../app')
  , customUtils = require('./customUtils')
  , validation = require('./validation')
  , childProcess = require('child_process')
  , spawn = require('child_process').spawn
  , async = require('async')
  ;

/**
 * Constructor. Not to be called directly, use Job.getJob instead.
 */
function Job (options) {
  var keys = Object.keys(options)
    , i;

  for (i = 0; i < keys.length; i += 1) {
    this[keys[i]] = options[keys[i]];
  }
}

/**
 * Pull the repo, and create it if it doesn't exist
 * @param {WritableStream} out Stream were everything that happens needs to be logged
 * @param {Function} callback Required callback
 */
Job.prototype.pullRepo = function (out, callback) {
  var self = this;

  if (! this.name) { return callback("This job doesn't seem to have a name ..."); }

  async.waterfall([
    function (cb) {   // Ensure the repo exists, create it if needed
      customUtils.ensureFolderExists(Job.getRepoPath(self.name), function (err) {
        if (err) { return cb(err); }

        fs.readdir(Job.getRepoPath(self.name), function (err, files) {
          if (err) { return cb(err); }
          if (files.length > 0) { return cb(); }   // Files were found, we assume it's a Git repo. TODO: check for .git folder

          childProcess.exec('git clone ' + self.repoSSHUrl + ' .', {  cwd: Job.getRepoPath(self.name)  }, function (err, stdout, stderr) {
            out && out.write("=== First build, cloning the repository ===\n");
            out && out.write(stdout);
            out && out.write(stderr);
            return cb(err);
          })
        });
      });
    }
  , function (cb) {   // Pull it (will fail if branch doesn't exist)
      childProcess.exec('set -e; git checkout ' + self.branch + '; git pull', {  cwd: Job.getRepoPath(self.name)  }, function (err, stdout, stderr) {
        out && out.write(stdout);
        out && out.write(stderr);
        return cb(err);
      })
    }
  ], callback);
};

/**
 * Check if we need to reinstall the dependencies, that's what takes most of the build time
 * @param {WritableStream} out Stream were everything that happens needs to be logged
 * @param {Function} callback Required callback. Signature: err, true/false
 */
Job.prototype.checkIfDependenciesNeedToBeReinstalled = function(out, callback) {
  var self = this;
  fs.readFile(Job.getRepoPath(self.name) + '/.gitignore', 'utf8', function (err, data) {
    var lines = data.split('\n')
      , nodeModulesIsGitignored = false;

    lines.forEach(function (line) {
      if (line.match(/^\/?node_modules\/?$/)) {
        nodeModulesIsGitignored = true;
      }
    });

    if (!nodeModulesIsGitignored) {
      out.write("=== node_modules is checked in Git so no need to reinstall them ===\n");
      return callback(null, false);
    }

    customUtils.checkIfFilesAreIdentical(Job.getDependenciesInfoDir(self.name) + '/npm-shrinkwrap.json', Job.getRepoPath(self.name) + '/npm-shrinkwrap.json', function (err, sameN) {
      if (err) { return callback(err); }
      customUtils.checkIfFilesAreIdentical(Job.getDependenciesInfoDir(self.name) + '/package.json', Job.getRepoPath(self.name) + '/package.json', function (err, sameP) {
        if (err) { return callback(err); }
        if (sameN && sameP) {
          out.write("=== node_modules is gitignored but package.json and npm-shrinkwrap.json didn't change, no need to reinstall ===\n");
          return callback(null, false);
        } else {
          out.write("=== node_modules is gitignored and package.json or npm-shrinkwrap.json changed, we need to reinstall ===\n");
          return callback(null, true);
        }
      });
    });
  });
};

/**
 * (Re)install the dependencies
 * @param {WritableStream} out Stream were everything that happens needs to be logged
 * @param {Function} callback Required callback
 */
Job.prototype.reinstallDependencies = function (out, callback) {
  var self = this, installer;

  if (! this.name) { return callback("This job doesn't seem to have a name ..."); }

  async.waterfall([
    function (cb) {   // Reinstall
    childProcess.exec('rm -rf node_modules', { cwd: Job.getRepoPath(self.name) }, function (err, stdout, stderr) {
      if (err) { return cb(err); }

      installer = childProcess.spawn('npm', ['install'], { cwd: Job.getRepoPath(self.name) });
      out && installer.stdout.pipe(out, { end: false });
      out && installer.stderr.pipe(out, { end: false });

      installer.on('exit', function (code) {
        var error = code === 0 ? null : "Couldn't reinstall dependencies";
        cb(error);
      });
    });
  }
  , function (cb) {   // Copy dependency files to remember them next time we check if need to reinstall
    customUtils.ensureFolderExists(Job.getDependenciesInfoDir(self.name), function (err) {
      if (err) { return cb(err); }
      customUtils.copySafe(Job.getRepoPath(self.name) + '/npm-shrinkwrap.json', Job.getDependenciesInfoDir(self.name) + '/npm-shrinkwrap.json', function (err) {
        if (err) { return cb(err); }
        customUtils.copySafe(Job.getRepoPath(self.name) + '/package.json', Job.getDependenciesInfoDir(self.name) + '/package.json', function (err) {
          return cb(err);
        });
      });
    });
  }
  ], callback);
};

/**
 * Run the tests
 * @param {WritableStream} out Stream were everything that happens needs to be logged
 * @param {Function} callback Required callback
 */
Job.prototype.runTests = function (out, callback) {
  var self = this, tester;

  if (! this.name) { return callback("This job doesn't seem to have a name ..."); }

  tester = childProcess.spawn('npm', ['test'], { cwd: Job.getRepoPath(self.name) });
  out && tester.stdout.pipe(out, { end: false });

  tester.on('exit', function (code) {
    var error = code === 0 ? null : 'Tests failed';
    callback(error);
  });
};

/**
 * Advertise the result of a build on hipchat
 */
Job.prototype.advertiseOnHipchat = function (buildSuccessful) {
  var messageToSend = { room_id: 'Deployment'
                      , from: 'Braindead CI'
                      , message_format: 'html'
                      , notify: buildSuccessful ? 0 : 1
                      , color: buildSuccessful ? 'green' : 'red'
                      }
    , buildUrl = config.braindeadRootUrl + '/jobs/' + this.name + '/builds/' + this.nextBuildNumber;
    ;

  if (buildSuccessful) {
    messageToSend.message = this.name + ' - Build and deploy successful (<a href="' + buildUrl + '">see build</a>)';
  } else {
    messageToSend.message = this.name + ' - Build and deploy failed (<a href="' + buildUrl + '">see build</a>)';
  }

  customUtils.sendMessageToHipchat(messageToSend);
};

/**
 * Launch a build
 */
Job.prototype.build = function (out, callback) {
  var self = this
    , buildReport
    , channel = new customUtils.PassthroughStream()   // Used to centralize build outputs
    , needToReinstallDeps = false
    ;

  customUtils.ensureFolderExists(Job.getBuildsDir(this.name), function (err) {
    if (err) { return callback(err); }
    buildReport = fs.createWriteStream(Job.getBuildFilename(self.name, self.nextBuildNumber));
    out && channel.pipe(out);
    channel.pipe(process.stdout);
    channel.pipe(buildReport);

    async.waterfall([
      function (cb) {
        channel.write("=== Pulling new code ===\n");
        self.pullRepo(channel, function (err) {
          if (err) { buildReport.write("Couldn't pull the repository, error given: " + err + "\n"); }
          cb(err);
        });
      }
    , function (cb) {
      channel.write("=== Checking if we need to reinstall dependencies ===\n");
      self.checkIfDependenciesNeedToBeReinstalled(channel, function (err, need) {
        if (err) { channel.write("=== Problem while checking if we need to reinstall dependencies, error given: " + err + "\n"); return cb(err); }
        needToReinstallDeps = need;
        return cb();
      });
    }
    , function (cb) {
      if (!needToReinstallDeps) { return cb(); }
      channel.write("=== Reinstalling dependencies ===\n");
      self.reinstallDependencies(channel, function (err) {
        if (err) { channel.write("=== Problem reinstalling dependencies, error given: " + err + "\n"); }
        cb(err);
      });
    }
    , function (cb) {
      channel.write("=== Running tests ===\n");
      var script = "cd " + Job.getRepoPath(self.name).replace(/ /g, '\\ ') + ";" + self.testScript;

      customUtils.executeBashScript(script, null, channel, function (err) {
        if (err) { channel.write("=== Tests didn't pass, error given: " + err + "\n"); }
        cb(err);
      });
    }
    , function (cb) {
      channel.write("=== Deploying ===\n");
      customUtils.executeBashScript(self.deployScript, { REINSTALL_DEPS: needToReinstallDeps }, channel, function (err) {
        return cb(err);
      });
    }
    ], function (err) {
      var buildSuccessful = err ? false : true;

      if (buildSuccessful) {
        channel.write("=== YES! Build and deploy successful! ===\n");
      } else {
        channel.write("=== OH NOES! Something went wrong :( ===\n");
      }

      buildReport.end();
      self.advertiseOnHipchat(buildSuccessful);   // Asynchronously advertise build
      self.previousBuilds[self.nextBuildNumber] = { number: self.nextBuildNumber
                                                  , success: buildSuccessful
                                                  , date: new Date()
                                                  };
      self.nextBuildNumber += 1;
      self.save(function () { callback(err); });
    });
  });
};

/**
 * Get a build's info
 */
Job.prototype.getBuild = function (buildNumber, callback) {
  var self = this;
  if (! self.previousBuilds[buildNumber]) { return callback('Build number ' + buildNumber + ' doesn\'t exist'); }

  fs.readFile(Job.getBuildFilename(self.name, parseInt(buildNumber, 10)), 'utf8', function (err, data) {
    var buildData = {};
    if (err) { return callback('Build number ' + buildNumber + ' doesn\'t exist or data is corrupted'); }

    buildData = self.previousBuilds[buildNumber];
    buildData.log = data;

    return callback(null, buildData);
  });
};

/**
 * Recreate a job from a filesystem conf file
 */
Job.getJob = function (name, callback) {
  if (!name) { return callback("This job doesn't have a name"); }

  Job.loadConfig(name, function (err, conf) {
    var j;
    if (err) { return callback (err); }
    if (!conf) { return callback('Job ' + name + ' not found!'); }

    return callback(null, new Job(conf));
  });
};

/**
 * Get a job from its repo URL
 */

/**
 * Create a new job
 */
Job.createJob = function (options, callback) {
  options.nextBuildNumber = 1;
  options.previousBuilds = {};
  j = new Job(options);
  j.save(function (err) {
    return callback(err, j);
  });
}

/**
 * Edit an existing job
 */
Job.prototype.edit = function (newOptions, callback) {
  var keys = Object.keys(newOptions)
    , i;

  for (i = 0; i < keys.length; i += 1) {
    this[keys[i]] = newOptions[keys[i]];
  }

  this.save(callback);
};

/**
 * Save job state in its config file
 */
Job.prototype.save = function (callback) {
  var self = this
    , toBeSerialized = {}
    , propertiesToSave = [ 'name'
                         , 'githubRepoUrl'
                         , 'repoSSHUrl'
                         , 'branch'
                         , 'nextBuildNumber'
                         , 'previousBuilds'
                         , 'testScript'
                         , 'deployScript'
                         ]
    ;

  if (! this.name) { return callback("This job doesn't seem to have a name ..."); }

  propertiesToSave.forEach(function (prop) { toBeSerialized[prop] = self[prop]; });
  customUtils.ensureFolderExists(Job.getRootDir(self.name), function (err) {
    if (err) { return callback(err); }
    fs.writeFile(Job.getConfigPath(self.name), JSON.stringify(toBeSerialized), 'utf8', callback);
  });
};

/**
 * Try to load a config from the filesystem
 */
Job.loadConfig = function (name, callback) {
  fs.exists(Job.getConfigPath(name), function (exists) {
    if (!exists) { return callback(); }

    fs.readFile(Job.getConfigPath(name), 'utf8', function (err, data) {
      var f, config;

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

      try {
        f = new Function('return ' + data);
        config = f();
      } catch (e) {
        return callback(e);
      }

      return callback(null, config);
    });
  });
};

/**
 * Load all jobs
 * For now we assume a one-to-one mapping between the list of directories and the list of jobs
 * TODO: check if real projects
 */
Job.loadAllJobsNames = function (callback) {
  fs.readdir(config.workspace, function (err, files) {
    var names = [];
    files.forEach(function (file) {
      if (validation.validateJobName(file)) { names.push(file); }
    });
    if (err) {
      return callback(err);
    } else {
      return callback(null, names.sort());
    }
  });
};

/**
 * Functions defining where the data is stored
 */
Job.getRootDir = function (name) { return config.workspace + '/' + name };
Job.getConfigPath = function (name) { return Job.getRootDir(name) + '/settings.conf'; };
Job.getRepoPath = function (name) { return Job.getRootDir(name) + '/repo'; };
Job.getBuildsDir = function (name) { return Job.getRootDir(name) + '/builds'; };
Job.getBuildFilename = function (name, buildNumber) { return Job.getBuildsDir(name) + '/build' + buildNumber + '.log'; };
Job.getDependenciesInfoDir = function (name) { return Job.getRootDir(name) + '/dependencies'; };

module.exports = Job;
<|endoftext|>


<commit_before>var main = function() {
 $('.bar2').click(function() {
    $("tablez").animate({
      left: "0px"
    }, 200);

    $("tablez").animate({
      left: "285px"
    }, 200);
  });

};
$(document).ready(main);
<commit_msg>Delete main.js<commit_after><|endoftext|>


<commit_before>
var _ = require('lodash')
  , EventEmitter = require('events').EventEmitter
  , spawn = require('child_process').spawn
  , path = require('path')
  , util = require('util')
  , fs = require('fs')

  , async = require('async')
  , mkdirp = require('mkdirp')
  , shellParse = require('shell-quote').parse
  , shellQuote = require('shell-quote').quote

  , colorize = require('./colorize')

  , text = require('../locales/en.json')

var PHASES = ['environment', 'prepare', 'test', 'deploy', 'cleanup']

function ExitError(command, code) {
  var e = new Error('Command "' + command + ' failed with code: ' + code)
  e.type = 'exitCode'
  e.code = code
  return e
}

function sum(list) {
  return list.reduce(function (a, b) { return a + b }, 0)
}

function runPlugin(phase, plugin, context, done) {
  if (!plugin[phase]) return done()
  if ('function' === typeof plugin[phase]) {
    return plugin[phase](context, done)
  }
  context.cmd(plugin[phase], function (code) {
    var data = {}
    data[phase + 'Cmd'] = code ? 'failed' : 'passed'
    context.data(data, 'extend')
    done(code && ExitError(code), true)
  })
}

module.exports = Job

// config:
// - baseDir - the directory to work in. The code gets cloned to baseDir/code
// - io - eventemitter for sending job status events
// - logger
// - log
// - error
// - pty
function Job(job, provider, plugins, config) {
  this.config = _.extend({
    pty: false,
    logger: console
  }, config)
  if (this.config.pty) {
    this.config.logger.warn('PTY has been disabled due to a bug in node core');
    this.config.pty = false;
  }
  this.io = config.io
  this.job = job
  this.id = job._id
  this.provider = provider
  this.plugins = plugins
  this.logPrefix = colorize.job(this.id)
  this.phase = null
  this.testcode = null
  this.cancelled = false
  this.listen()
}

Job.prototype = {
  // public api
  run: function (done) {
    var self = this
      , dom = require('domain').create()

    // no plugins in use, nothing will happen. fail.
    if (!this.plugins.length) {
      return this.done({message: 'No plugins configured. Aborting'}, done)
    }

    dom.on('error', function (err) {
      self.log('domain error caught', err.message, err.stack)
      self.error(err)
      dom.dispose()
      self.done(err, done)
    })
    dom.run(function () {
      self.phase = PHASES[0]
      self.runPhase(done)
    })
  },
  cancel: function () {
    this.cancelled = true
    this.io.emit('job.cancelled', this.id)
  },
  // private api
  listen: function () {
    var self = this
    this.io.on('job.cancel', function (id) {
      if (self.id === id) {
        self.cancel()
      }
    })
  },
  done: function (err, done) {
    if (this.cancelled) {
      this.log('"done" called but the job is cancelled. Not calling continuation')
      return
    }
    done(err)
  },

  // command execution stuff

  comment: function (text, plugin) {
    this.status('command.comment', { comment: text, plugin: plugin, time: new Date() })
  },

  /* usage:   (cmd, [plugin,] next) -> next(exitCode)
   * or:      (options, [plugin,] next) -> next(exitCode)
   *
   * plugin:  the name of the plugin that intiated the command
   *
   * options:
   *     cmd: string or {command: str, args: [], screen: str}
   *     silent: don't status the output as it goes
   *     env: {}
   *     cwd: str
   *
   * cmd('echo "hey"', next)
   * cmd({command: 'echo secretpassword', screen: 'echo [pwd]'}, next)
   * cmd({command: 'echo', args: ['hello']}, next)
   * cmd({cmd: 'hey', env: {ONE: 2}}, next)
   */
  cmd: function (cmd, plugin, next) {
    if (arguments.length === 2) {
      next = plugin
      plugin = null
    }
    var parts
      , self = this
      , start = new Date()
    var options = {
      cwd: this.phase === 'environment' ? this.config.baseDir : this.config.dataDir,
      silent: false,
      detached: true
    }

    if (cmd.cmd) {
      _.extend(options, cmd)
      cmd = options.cmd;
      delete options.cmd
    }
    if (typeof(cmd) === 'string') {
      cmd = {
        command: cmd
      }
    }
    if (typeof(cmd.args) === 'undefined') {
      cmd.args = shellParse(cmd.command)
      cmd.command = cmd.args.shift()
    }

    var proc = spawn(cmd.command, cmd.args, options)
    proc.stdout.setEncoding('utf8')
    proc.stderr.setEncoding('utf8')

    if (!options.silent) {
      proc.stdout.on('data', function(buf) {
        self.status('stdout', buf)
      })

      proc.stderr.on('data', function(buf) {
        self.status('stderr', buf)
      })
    }

    proc.on('close', function(exitCode) {
      var end = new Date()
        , elapsed = end.getTime() - start.getTime()
      // Uncomment to monitor job commands
      // self.log(util.format('command done %s %s; exit code %s; duration %s', cmd.command, cmd.args, exitCode, elapsed))
      self.status('command.done', { exitCode: exitCode, time: end, elapsed: elapsed })
      next(exitCode)
    })

    var strCmd = shellQuote([cmd.command].concat(cmd.args))
      , display = cmd.screen || strCmd
    this.status('command.start', { command: display, started: start, plugin: plugin, time: start })
    if (options.silent) {
      this.status('stdout', '[output suppressed]')
    }
    return proc;
  },

  // job running stuff
  pluginContext: function (pluginName, env, path) {
    var self = this
    var context = {
      status: this.status.bind(this),
      out: this.out.bind(this),

      log: function () {
        var args = [].slice.call(arguments)
        // TODO colorize the plugin name?
        return self.log.apply(self, [pluginName].concat(args))
      },

      comment: function (text) {
        self.comment(text, pluginName)
      },
      cmd: function (cmd, next) {
        if (typeof(cmd) === 'string' || cmd.command) {
          cmd = {cmd: cmd}
        }
        cmd.env = _.extend({}, env, {
          PATH: path.join(':') + ':' + process.env.PATH,
          HOME: process.env.HOME,
          PAAS_NAME: 'strider'
        }, cmd.env || {})
        return self.cmd(cmd, pluginName, next)
      },

      // update pluginData
      data: function (data, method, path) {
        self.status('plugin-data', {plugin: pluginName, data: data, method: method, path: path})
      },

      logger: this.config.logger,
      dataDir: this.config.dataDir,
      baseDir: this.config.baseDir,
      cacheDir: this.config.cacheDir,
      cachier: this.config.cachier(pluginName),
      io: this.io,

      plugin: pluginName,
      phase: this.phase,
      job: this.job,
      branch: this.job.ref.branch,
      project: this.project
    }
    return context
  },

  // collect `env` from all plugins for the current phase, and execute
  // plugin.runPhase(context) for each plugin in series
  runPhase: function (next) {
    if (this.cancelled) return
    var tasks = []
      , started = new Date()
      , self = this
      , path = []
      , env = {}
      , provider
      , name
    // need to clone the repo first
    if (this.phase === 'prepare') {
      tasks.push(this.provider.fetch.bind(this.provider, self.pluginContext(this.provider.id, env, path)))
    }
    this.plugins.forEach(function (plugin) {
      // all plugins will get the final, fully populated env object
      _.extend(env, ('function' === typeof plugin.env) ? plugin.env(self.phase) : plugin.env)
      if ('string' === typeof plugin.path) path.push(plugin.path)
      else path = path.concat('function' === typeof plugin.path ? plugin.path(self.phase) : plugin.path)
      tasks.push(function (next) {
        runPlugin(self.phase, plugin, self.pluginContext(plugin.id, env, path), next)
      })
    })
    async.series(tasks, this.phaseDone.bind(this, next, started))
  },

  // called on the completion (or erroring) of a phase
  phaseDone: function (next, started, err, actuallyRan) {
    var code = 0
      , now = new Date()
    if (err) {
      if (err.type === 'exitCode') code = err.code
      if ('number' === typeof err) code = err
      if (code === 0) {
        this.error(err)
        return this.done(err, next)
      }
    }
    // Complain if no plugins actually did anything during testing?
    // XXX: should this be a fail hard?
    if (['deploy', 'test'].indexOf(this.phase) !== -1 && sum(actuallyRan) === 0) {
      this.out('Phase ' + this.phase + " didn't actually run anything. Check your plugin configuration", 'warn')
    }
    if (this.phase === 'test') {
      this.status('tested', { time: now, exitCode: code })
    }
    if (this.phase === 'deploy') {
      this.status('deployed', { time: now, exitCode: code })
    }
    var nextPhase = PHASES[PHASES.indexOf(this.phase) + 1]
    if (code !== 0) {
      nextPhase = 'cleanup'
    } else if (nextPhase === 'deploy' && this.job.type !== 'TEST_AND_DEPLOY') {
      nextPhase = 'cleanup'
    }
    this.status('phase.done', {
      phase: this.phase,
      time: now,
      exitCode: code,
      next: nextPhase,
      elapsed: now.getTime() - started.getTime()
    })
    if (this.phase === 'cleanup') {
      return this.done(null, next)
    }
    this.phase = nextPhase
    this.runPhase(next)
  },

  // io stuff
  log: function () {
    var args = [].slice.call(arguments)
    this.config.log.apply(null, [this.logPrefix].concat(args))
  },
  error: function (error, serverOnly) {
    this.config.error(this.logPrefix, error.message || error, error.stack);
    if (!serverOnly) {
      this.status('stderr', text.error_please_report + '\n\n' + error.message + '\n\n' + error.stack)
    }
  },
  status: function (type) {
    if (this.cancelled) return false
    var args = [].slice.call(arguments, 1)
    this.io.emit.apply(this.io, ['job.status.' + type, this.id].concat(args))
  },
  out: function (text, type) {
    var dest = ['error', 'stderr', 'warn'].indexOf(type) !== -1 ? 'stderr' : 'stdout'
    text = (type && colorize[type]) ? colorize[type](text) : text
    this.status(dest, text)
  }
}
<commit_msg>awareness of passed-in env vbls<commit_after>
var _ = require('lodash')
  , EventEmitter = require('events').EventEmitter
  , spawn = require('child_process').spawn
  , path = require('path')
  , util = require('util')
  , fs = require('fs')

  , async = require('async')
  , mkdirp = require('mkdirp')
  , shellParse = require('shell-quote').parse
  , shellQuote = require('shell-quote').quote

  , colorize = require('./colorize')

  , text = require('../locales/en.json')

var PHASES = ['environment', 'prepare', 'test', 'deploy', 'cleanup']

function ExitError(command, code) {
  var e = new Error('Command "' + command + ' failed with code: ' + code)
  e.type = 'exitCode'
  e.code = code
  return e
}

function sum(list) {
  return list.reduce(function (a, b) { return a + b }, 0)
}

function runPlugin(phase, plugin, context, done) {
  if (!plugin[phase]) return done()
  if ('function' === typeof plugin[phase]) {
    return plugin[phase](context, done)
  }
  context.cmd(plugin[phase], function (code) {
    var data = {}
    data[phase + 'Cmd'] = code ? 'failed' : 'passed'
    context.data(data, 'extend')
    done(code && ExitError(code), true)
  })
}

module.exports = Job

// config:
// - baseDir - the directory to work in. The code gets cloned to baseDir/code
// - io - eventemitter for sending job status events
// - logger
// - log
// - error
// - pty
function Job(job, provider, plugins, config) {
  this.config = _.extend({
    pty: false,
    env: {},
    logger: console
  }, config)
  if (this.config.pty) {
    this.config.logger.warn('PTY has been disabled due to a bug in node core');
    this.config.pty = false;
  }
  this.io = config.io
  this.job = job
  this.id = job._id
  this.provider = provider
  this.plugins = plugins
  this.logPrefix = colorize.job(this.id)
  this.phase = null
  this.testcode = null
  this.cancelled = false
  this.listen()
}

Job.prototype = {
  // public api
  run: function (done) {
    var self = this
      , dom = require('domain').create()

    // no plugins in use, nothing will happen. fail.
    if (!this.plugins.length) {
      return this.done({message: 'No plugins configured. Aborting'}, done)
    }

    dom.on('error', function (err) {
      self.log('domain error caught', err.message, err.stack)
      self.error(err)
      dom.dispose()
      self.done(err, done)
    })
    dom.run(function () {
      self.phase = PHASES[0]
      self.runPhase(done)
    })
  },
  cancel: function () {
    this.cancelled = true
    this.io.emit('job.cancelled', this.id)
  },
  // private api
  listen: function () {
    var self = this
    this.io.on('job.cancel', function (id) {
      if (self.id === id) {
        self.cancel()
      }
    })
  },
  done: function (err, done) {
    if (this.cancelled) {
      this.log('"done" called but the job is cancelled. Not calling continuation')
      return
    }
    done(err)
  },

  // command execution stuff

  comment: function (text, plugin) {
    this.status('command.comment', { comment: text, plugin: plugin, time: new Date() })
  },

  /* usage:   (cmd, [plugin,] next) -> next(exitCode)
   * or:      (options, [plugin,] next) -> next(exitCode)
   *
   * plugin:  the name of the plugin that intiated the command
   *
   * options:
   *     cmd: string or {command: str, args: [], screen: str}
   *     silent: don't status the output as it goes
   *     env: {}
   *     cwd: str
   *
   * cmd('echo "hey"', next)
   * cmd({command: 'echo secretpassword', screen: 'echo [pwd]'}, next)
   * cmd({command: 'echo', args: ['hello']}, next)
   * cmd({cmd: 'hey', env: {ONE: 2}}, next)
   */
  cmd: function (cmd, plugin, next) {
    if (arguments.length === 2) {
      next = plugin
      plugin = null
    }
    var parts
      , self = this
      , start = new Date()
    var options = {
      cwd: this.phase === 'environment' ? this.config.baseDir : this.config.dataDir,
      silent: false,
      detached: true
    }

    if (cmd.cmd) {
      _.extend(options, cmd)
      cmd = options.cmd;
      delete options.cmd
    }
    if (typeof(cmd) === 'string') {
      cmd = {
        command: cmd
      }
    }
    if (typeof(cmd.args) === 'undefined') {
      cmd.args = shellParse(cmd.command)
      cmd.command = cmd.args.shift()
    }

    var proc = spawn(cmd.command, cmd.args, options)
    proc.stdout.setEncoding('utf8')
    proc.stderr.setEncoding('utf8')

    if (!options.silent) {
      proc.stdout.on('data', function(buf) {
        self.status('stdout', buf)
      })

      proc.stderr.on('data', function(buf) {
        self.status('stderr', buf)
      })
    }

    proc.on('close', function(exitCode) {
      var end = new Date()
        , elapsed = end.getTime() - start.getTime()
      // Uncomment to monitor job commands
      // self.log(util.format('command done %s %s; exit code %s; duration %s', cmd.command, cmd.args, exitCode, elapsed))
      self.status('command.done', { exitCode: exitCode, time: end, elapsed: elapsed })
      next(exitCode)
    })

    var strCmd = shellQuote([cmd.command].concat(cmd.args))
      , display = cmd.screen || strCmd
    this.status('command.start', { command: display, started: start, plugin: plugin, time: start })
    if (options.silent) {
      this.status('stdout', '[output suppressed]')
    }
    return proc;
  },

  // job running stuff
  pluginContext: function (pluginName, env, path) {
    var self = this
    var context = {
      status: this.status.bind(this),
      out: this.out.bind(this),

      log: function () {
        var args = [].slice.call(arguments)
        // TODO colorize the plugin name?
        return self.log.apply(self, [pluginName].concat(args))
      },

      comment: function (text) {
        self.comment(text, pluginName)
      },
      cmd: function (cmd, next) {
        if (typeof(cmd) === 'string' || cmd.command) {
          cmd = {cmd: cmd}
        }
        cmd.env = _.extend({}, env, {
          PATH: path.join(':') + ':' + process.env.PATH,
          HOME: process.env.HOME,
          PAAS_NAME: 'strider'
        }, cmd.env || {})
        return self.cmd(cmd, pluginName, next)
      },

      // update pluginData
      data: function (data, method, path) {
        self.status('plugin-data', {plugin: pluginName, data: data, method: method, path: path})
      },

      logger: this.config.logger,
      dataDir: this.config.dataDir,
      baseDir: this.config.baseDir,
      cacheDir: this.config.cacheDir,
      cachier: this.config.cachier(pluginName),
      io: this.io,

      plugin: pluginName,
      phase: this.phase,
      job: this.job,
      branch: this.job.ref.branch,
      project: this.project
    }
    return context
  },

  // collect `env` from all plugins for the current phase, and execute
  // plugin.runPhase(context) for each plugin in series
  runPhase: function (next) {
    if (this.cancelled) return
    var tasks = []
      , started = new Date()
      , self = this
      , path = []
      , env = _.cloneDeep(this.config.env)
      , provider
      , name
    // need to clone the repo first
    if (this.phase === 'prepare') {
      tasks.push(this.provider.fetch.bind(this.provider, self.pluginContext(this.provider.id, env, path)))
    }
    this.plugins.forEach(function (plugin) {
      // all plugins will get the final, fully populated env object
      _.extend(env, ('function' === typeof plugin.env) ? plugin.env(self.phase) : plugin.env)
      if ('string' === typeof plugin.path) path.push(plugin.path)
      else path = path.concat('function' === typeof plugin.path ? plugin.path(self.phase) : plugin.path)
      tasks.push(function (next) {
        runPlugin(self.phase, plugin, self.pluginContext(plugin.id, env, path), next)
      })
    })
    async.series(tasks, this.phaseDone.bind(this, next, started))
  },

  // called on the completion (or erroring) of a phase
  phaseDone: function (next, started, err, actuallyRan) {
    var code = 0
      , now = new Date()
    if (err) {
      if (err.type === 'exitCode') code = err.code
      if ('number' === typeof err) code = err
      if (code === 0) {
        this.error(err)
        return this.done(err, next)
      }
    }
    // Complain if no plugins actually did anything during testing?
    // XXX: should this be a fail hard?
    if (['deploy', 'test'].indexOf(this.phase) !== -1 && sum(actuallyRan) === 0) {
      this.out('Phase ' + this.phase + " didn't actually run anything. Check your plugin configuration", 'warn')
    }
    if (this.phase === 'test') {
      this.status('tested', { time: now, exitCode: code })
    }
    if (this.phase === 'deploy') {
      this.status('deployed', { time: now, exitCode: code })
    }
    var nextPhase = PHASES[PHASES.indexOf(this.phase) + 1]
    if (code !== 0) {
      nextPhase = 'cleanup'
    } else if (nextPhase === 'deploy' && this.job.type !== 'TEST_AND_DEPLOY') {
      nextPhase = 'cleanup'
    }
    this.status('phase.done', {
      phase: this.phase,
      time: now,
      exitCode: code,
      next: nextPhase,
      elapsed: now.getTime() - started.getTime()
    })
    if (this.phase === 'cleanup') {
      return this.done(null, next)
    }
    this.phase = nextPhase
    this.runPhase(next)
  },

  // io stuff
  log: function () {
    var args = [].slice.call(arguments)
    this.config.log.apply(null, [this.logPrefix].concat(args))
  },
  error: function (error, serverOnly) {
    this.config.error(this.logPrefix, error.message || error, error.stack);
    if (!serverOnly) {
      this.status('stderr', text.error_please_report + '\n\n' + error.message + '\n\n' + error.stack)
    }
  },
  status: function (type) {
    if (this.cancelled) return false
    var args = [].slice.call(arguments, 1)
    this.io.emit.apply(this.io, ['job.status.' + type, this.id].concat(args))
  },
  out: function (text, type) {
    var dest = ['error', 'stderr', 'warn'].indexOf(type) !== -1 ? 'stderr' : 'stdout'
    text = (type && colorize[type]) ? colorize[type](text) : text
    this.status(dest, text)
  }
}
<|endoftext|>


<commit_before>tinymce.init({
    selector: "textarea",
    plugins: [
        "advlist autolink lists link image charmap print preview anchor",
        "searchreplace visualblocks code fullscreen save",
        "insertdatetime media table contextmenu paste"
    ],
    toolbar: "save | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
    setup: function(editor) {
		editor.on('init', function() {
			setTimeout(function() {
				tinyMCE.editors[0].execCommand('mceFullScreen');
			},0);
		});
	},
	save_enablewhendirty: true,
    save_onsavecallback: function(editor) {
		console.log("Save");
		console.log(editor.save());
	}
});
<commit_msg>Now saving to local storage<commit_after>$(function() {
	var filename = "fix_me.html";

	function save(editor) {
		if (Modernizr.localstorage) {
			localStorage.setItem("file_" + filename, editor.save());
		} else {
			alert("Local storage is required to save.");
		}
	}

	tinymce.init({
		selector: "textarea",
		plugins: [
			"advlist autolink lists link image charmap print preview anchor",
			"searchreplace visualblocks code fullscreen save",
			"insertdatetime media table contextmenu paste"
		],
		toolbar: "save | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
		setup: function(editor) {
			editor.on('init', function() {
				setTimeout(function() {
					tinyMCE.editors[0].execCommand('mceFullScreen');
				},0);
			});
		},
		save_enablewhendirty: true,
		save_onsavecallback: save
	});
});
<|endoftext|>


<commit_before>var Promise = require('bluebird');

function Job(zugzug, queue) {
  if (!(this instanceof Job)) return new Job(zugzug, queue);
  this.zugzug = zugzug;
  this._client = zugzug._client;
  this.data = {};
  this.queue = queue;
  this.progress = 0;
  this.failures = 0;
  this.maxFailures = 1;
}

Job.states = ['pending', 'progress', 'error', 'done'];

Job.prototype.save = function save(callback) {
  var job = this;
  var db = this._client;
  return (
    job.id
    ? job._save()
    : (
      db.incr('zugzug:id')
      .then(function(id) {
        job.id = String(id);
        job.state = 'pending';
        return job._save();
      })
      .then(function() {
        var m = db.multi()
        .lpush('zugzug:job:' + job.id + ':log', JSON.stringify({
          date: Number(job.created),
          message: 'Job created.',
          state: job.state
        }))
        .sadd('zugzug:queue', job.queue)
        .sadd('zugzug:queue:' + job.queue, job.id)
        .lpush('zugzug:queue:' + job.queue + ':' + job.state, job.id);
        return Promise.promisify(m.exec, m)();
      })
    )
  )
  .thenReturn(job)
  .nodeify(callback);
};

Job.prototype._save = function _save() {
  this.updated = new Date();
  if (this.created === undefined) this.created = this.updated;
  return this._client
  .hmset('zugzug:job:' + this.id, {
    data: JSON.stringify(this.data),
    queue: this.queue,
    state: this.state,
    progress: this.progress,
    failures: this.failures,
    maxFailures: this.maxFailures,
    created: Number(this.created),
    updated: Number(this.updated)
  });
};

Job.prototype.update = function update(progress, message, callback) {
  if (typeof message === 'function') {
    callback = message;
    message = undefined;
  }
  this.updated = new Date();
  if (progress !== undefined) this.progress = progress;
  var m = this._client.multi()
  .hmset('zugzug:job:' + this.id, {
    updated: Number(this.updated),
    progress: this.progress
  });
  if (message) {
    m = m.lpush('zugzug:job:' + this.id + ':log', JSON.stringify({
      date: Number(this.updated),
      progress: this.progress,
      message: message
    }));
  }
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.log = function log(message, details, callback) {
  if (typeof details === 'function') {
    callback = details;
    details = undefined;
  }
  this.updated = new Date();
  var obj = {
    date: Number(this.updated),
    message: message
  };
  if (details) obj.details = details;
  var m = this._client.multi()
  .lpush('zugzug:job:' + this.id + ':log', JSON.stringify(obj))
  .hset('zugzug:job:' + this.id, 'updated', Number(this.updated));
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.start = function start(callback) {
  var oldState = this.state;
  this.updated = new Date();
  this.state = 'progress';
  var m = this._client.multi()
  .hmset('zugzug:job:' + this.id, {
    updated: Number(this.updated),
    state: this.state
  })
  .lpush('zugzug:job:' + this.id + ':log', JSON.stringify({
    date: Number(this.updated),
    message: 'Job started.',
    state: this.state
  }))
  .lrem('zugzug:queue:' + this.queue + ':' + oldState, 0, this.id)
  .lpush('zugzug:queue:' + this.queue + ':' + this.state, this.id);
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.complete = function complete(callback) {
  this.updated = new Date();
  this.state = 'done';
  var m = this._client.multi()
  .hmset('zugzug:job:' + this.id, {
    updated: Number(this.updated),
    state: 'done'
  })
  .lpush('zugzug:job:' + this.id + ':log', JSON.stringify({
    date: Number(this.updated),
    message: 'Job complete.',
    state: 'done',
    success: true
  }))
  .lrem('zugzug:queue:' + this.queue + ':progress', 0, this.id)
  .lpush('zugzug:queue:' + this.queue + ':done', this.id);
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.fail = function fail(details, callback) {
  if (typeof details === 'function') {
    callback = details;
    details = undefined;
  }
  this.updated = new Date();
  this.failures += 1;
  if (!this.maxFailures || this.failures < this.maxFailures) {
    this.state = 'pending';
  } else {
    this.state = 'error';
  }
  var obj = {
    date: Number(this.updated),
    message: 'Job failed.',
    state: this.state,
    success: false
  };
  if (details) obj.details = details;
  var m = this._client.multi()
  .hmset('zugzug:job:' + this.id, {
    updated: Number(this.updated),
    failures: this.failures,
    state: this.state
  })
  .lpush('zugzug:job:' + this.id + ':log', JSON.stringify(obj))
  .lrem('zugzug:queue:' + this.queue + ':progress', 0, this.id)
  .lpush('zugzug:queue:' + this.queue + ':' + this.state, this.id);
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.delete = function del(callback) {
  if (!this.id) return Promise.resolve(this);
  var job = this;
  var db = this._client;
  var m = db.multi()
  .srem('zugzug:queue:' + job.queue, job.id)
  .lrem('zugzug:queue:' + job.queue + ':' + job.state, 0, job.id)
  .del('zugzug:job:' + job.id + ':log')
  .del('zugzug:job:' + job.id);
  delete job.id;
  delete job.state;
  delete job.created;
  delete job.updated;
  return Promise.promisify(m.exec, m)()
  .thenReturn(job)
  .nodeify(callback);
};

module.exports = Job;<commit_msg>Made complete/fail cope with other from-states.<commit_after>var Promise = require('bluebird');

function Job(zugzug, queue) {
  if (!(this instanceof Job)) return new Job(zugzug, queue);
  this.zugzug = zugzug;
  this._client = zugzug._client;
  this.data = {};
  this.queue = queue;
  this.progress = 0;
  this.failures = 0;
  this.maxFailures = 1;
}

Job.states = ['pending', 'progress', 'error', 'done'];

Job.prototype.save = function save(callback) {
  var job = this;
  var db = this._client;
  return (
    job.id
    ? job._save()
    : (
      db.incr('zugzug:id')
      .then(function(id) {
        job.id = String(id);
        job.state = 'pending';
        return job._save();
      })
      .then(function() {
        var m = db.multi()
        .lpush('zugzug:job:' + job.id + ':log', JSON.stringify({
          date: Number(job.created),
          message: 'Job created.',
          state: job.state
        }))
        .sadd('zugzug:queue', job.queue)
        .sadd('zugzug:queue:' + job.queue, job.id)
        .lpush('zugzug:queue:' + job.queue + ':' + job.state, job.id);
        return Promise.promisify(m.exec, m)();
      })
    )
  )
  .thenReturn(job)
  .nodeify(callback);
};

Job.prototype._save = function _save() {
  this.updated = new Date();
  if (this.created === undefined) this.created = this.updated;
  return this._client
  .hmset('zugzug:job:' + this.id, {
    data: JSON.stringify(this.data),
    queue: this.queue,
    state: this.state,
    progress: this.progress,
    failures: this.failures,
    maxFailures: this.maxFailures,
    created: Number(this.created),
    updated: Number(this.updated)
  });
};

Job.prototype.update = function update(progress, message, callback) {
  if (typeof message === 'function') {
    callback = message;
    message = undefined;
  }
  this.updated = new Date();
  if (progress !== undefined) this.progress = progress;
  var m = this._client.multi()
  .hmset('zugzug:job:' + this.id, {
    updated: Number(this.updated),
    progress: this.progress
  });
  if (message) {
    m = m.lpush('zugzug:job:' + this.id + ':log', JSON.stringify({
      date: Number(this.updated),
      progress: this.progress,
      message: message
    }));
  }
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.log = function log(message, details, callback) {
  if (typeof details === 'function') {
    callback = details;
    details = undefined;
  }
  this.updated = new Date();
  var obj = {
    date: Number(this.updated),
    message: message
  };
  if (details) obj.details = details;
  var m = this._client.multi()
  .lpush('zugzug:job:' + this.id + ':log', JSON.stringify(obj))
  .hset('zugzug:job:' + this.id, 'updated', Number(this.updated));
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.start = function start(callback) {
  var oldState = this.state;
  this.updated = new Date();
  this.state = 'progress';
  var m = this._client.multi()
  .hmset('zugzug:job:' + this.id, {
    updated: Number(this.updated),
    state: this.state
  })
  .lpush('zugzug:job:' + this.id + ':log', JSON.stringify({
    date: Number(this.updated),
    message: 'Job started.',
    state: this.state
  }))
  .lrem('zugzug:queue:' + this.queue + ':' + oldState, 0, this.id)
  .lpush('zugzug:queue:' + this.queue + ':' + this.state, this.id);
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.complete = function complete(callback) {
  var oldState = this.state;
  this.updated = new Date();
  this.state = 'done';
  var m = this._client.multi()
  .hmset('zugzug:job:' + this.id, {
    updated: Number(this.updated),
    state: this.state
  })
  .lpush('zugzug:job:' + this.id + ':log', JSON.stringify({
    date: Number(this.updated),
    message: 'Job complete.',
    state: this.state,
    success: true
  }))
  .lrem('zugzug:queue:' + this.queue + ':' + oldState, 0, this.id)
  .lpush('zugzug:queue:' + this.queue + ':' + this.state, this.id);
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.fail = function fail(details, callback) {
  if (typeof details === 'function') {
    callback = details;
    details = undefined;
  }
  var oldState = this.state;
  this.updated = new Date();
  this.failures += 1;
  if (!this.maxFailures || this.failures < this.maxFailures) {
    this.state = 'pending';
  } else {
    this.state = 'error';
  }
  var obj = {
    date: Number(this.updated),
    message: 'Job failed.',
    state: this.state,
    success: false
  };
  if (details) obj.details = details;
  var m = this._client.multi()
  .hmset('zugzug:job:' + this.id, {
    updated: Number(this.updated),
    failures: this.failures,
    state: this.state
  })
  .lpush('zugzug:job:' + this.id + ':log', JSON.stringify(obj))
  .lrem('zugzug:queue:' + this.queue + ':' + oldState, 0, this.id)
  .lpush('zugzug:queue:' + this.queue + ':' + this.state, this.id);
  return Promise.promisify(m.exec, m)()
  .thenReturn(this)
  .nodeify(callback);
};

Job.prototype.delete = function del(callback) {
  if (!this.id) return Promise.resolve(this);
  var job = this;
  var db = this._client;
  var m = db.multi()
  .srem('zugzug:queue:' + job.queue, job.id)
  .lrem('zugzug:queue:' + job.queue + ':' + job.state, 0, job.id)
  .del('zugzug:job:' + job.id + ':log')
  .del('zugzug:job:' + job.id);
  delete job.id;
  delete job.state;
  delete job.created;
  delete job.updated;
  return Promise.promisify(m.exec, m)()
  .thenReturn(job)
  .nodeify(callback);
};

module.exports = Job;<|endoftext|>


<commit_before>(function(win, $) {
    "use strict";
    var app = win.app = (typeof (win.app) !== 'undefined') ? win.app : {};

    (app.main = function () {
        // private properties/methods here
        var showMenuPanel = function (panelType, parentOffsets) {
            var $panel = $('#' + panelType);

            $panel
                .css({ left: parentOffsets.right - $panel.get(0).clientWidth, top: parentOffsets.bottom, opacity: 1 }); //.animate({ opacity: 1.0 }, 500);
        };

        var hideMenuPanel = function (panelType) {
            $('#' + panelType).animate({ opacity: 0 }, 200, function() {
                $(this).css({ left: -99999 });
            });
        }

        var navDropdownHoverIn = function(e) {
            //console.log('hovering over ' + $(e.currentTarget).data('dropdown-type') + ' left:' + e.currentTarget.offsetLeft + ' width:' + e.currentTarget.offsetWidth + ' top:' + e.currentTarget.offsetTop + ' height:' + e.currentTarget.offsetHeight + ' right:' + (e.currentTarget.offsetLeft + e.currentTarget.offsetWidth) + ' bottom:' + (e.currentTarget.offsetTop + e.currentTarget.offsetHeight));
            showMenuPanel($(e.currentTarget).data('dropdown-type'), { right: e.currentTarget.offsetLeft + e.currentTarget.offsetWidth, bottom: e.currentTarget.offsetTop + e.currentTarget.offsetHeight});
        };

        var navDropdownHoverOut = function(e) {
            //console.log('hover outside of element ' + $(e.currentTarget).data('dropdown-type'));
            hideMenuPanel($(e.currentTarget).data('dropdown-type'));
        };

        var showCountryContent = function (continent) {
            $('.country-list').each(function () {
                var $this = $(this);
                $this.css({ opacity: 0 }).hide();
                if ($this.data('continent') === continent) {
                    $this.css({ opacity: 0 }).show().css( { opacity: 1 });
                }
            });
        };

        var getNewsletterClickHandler = function(e) {
            //console.log('subscribe to the newsletter');
        };

        var documentReadyComplete = function(){
                $('.featured-tours article').equalizeHeight();
        };

        var registerModuleEvents = function() {
            $('.nav-item').hover(navDropdownHoverIn, navDropdownHoverOut);
            $('.cta').on('click', getNewsletterClickHandler);
            $('.continent-link').hover(function(e) {
                //on mouse in (over)
                var $this = $(this);
                $this.closest('ul')
                    .find('.link-primary-text-color')
                    .removeClass('link-primary-text-color')
                    .end();
                $this.addClass('link-primary-text-color');
                showCountryContent($this.data('continent'));
            });

            //just want to close the panel when these are clicked
            $('.country-link, .panel-cta ').on('click', function(e) {
                hideMenuPanel($(e.currentTarget).closest('.nav-item').data('dropdown-type'));
            });

            $('.observable').on('document.ready.complete', documentReadyComplete);

            // var $navbarClone = $('nav').clone();
            // $('.sticky-header').append($navbarClone);

            $(window).on('scroll', function(e) {
                var $header = $('.sticky-header');
                $(e.currentTarget).scrollTop();
                if ($(e.currentTarget).scrollTop() > 60) {
                    $header.addClass('active');
                    //console.log('scrolling down: ' + $(e.currentTarget).scrollTop());
                } else {
                    $header.removeClass('active')
                    //console.log('scrolling up: ' + $(e.currentTarget).scrollTop());;
                }
            });
        };

        //implements a horizontal slide show.
        var slideShow = function () {

            var viewableWidth = $('.slide-viewer').get(0).clientWidth;

            var executeTransition = function(transitionToAssetIndex, direction, transitionDuration) {
                var defaultTransition = '1.5s';

                if (typeof(transitionDuration) === 'undefined') {
                    transitionDuration = defaultTransition;
                }

                $('.asset-list')
                    .css({ transition: transitionDuration, transform: 'translate3d(-' + (transitionToAssetIndex * viewableWidth) + 'px, 0, 0)'})
                    .one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) {
                        //if we've landed on a cloned asset, at the beginning or the end, we need to move to the correct asset (the one this is a clone of)
                        //By sending in a zero second transition, the active image will be switched to the real image, but the user doesn't experience the
                        //change and the correct item will be queued up for the next transition.
                        if ($('.asset-item:eq('+ transitionToAssetIndex +')').hasClass('clone')) {
                            var $assetToShow = $('.asset-item:last').index() === transitionToAssetIndex ? $('.asset-item:not(.clone):first') : $('.asset-item:not(.clone):last');
                            executeTransition($assetToShow.index(), direction, '0s');
                            $('.asset-item:eq(' + transitionToAssetIndex + ')').removeClass('is-active');
                            $assetToShow.addClass('is-active');
                        }
                        // set the asset indicators
                        $('.asset-indicators .circle.is-active').removeClass('is-active');
                        $('.asset-indicators li:eq(' + ($('.asset-item.is-active').index() - 1) + ') .circle').addClass('is-active');
                    });
                };

            var startTransition = function (direction, index) {
                //console.log('direction: ' + direction + ' index:' + index);
                var visibleAssetIndex = $('.asset-item.is-active').index(),
                    transitionToAssetIndex = typeof (index) !== 'undefined'
                                             ? index + 1
                                             : direction === 'next' ? visibleAssetIndex + 1 : visibleAssetIndex - 1, // navigating by prev/next
                    //if the user is navigating by index, transitionDirection allows us to transition in the proper direction.
                    transitionDirection = typeof (index) !== 'undefined'
                                          ? (transitionToAssetIndex > visibleAssetIndex ? 'next' : 'prev')
                                          : direction;

                $('.asset-item:eq(' + visibleAssetIndex + ')').removeClass('is-active');
                $('.asset-item:eq(' + transitionToAssetIndex + ')').addClass('is-active');

                if (visibleAssetIndex !== transitionToAssetIndex) {
                    executeTransition(transitionToAssetIndex, transitionDirection); //uses default transition duration
                }
            };

            var addCircleAssetIndicators = function () {
                //create asset indicator for each image
                var $assetIndicators = $('.asset-indicators'),
                    $circleTemplate = $assetIndicators.find('.template');

                $('.asset-item:not(.clone)').each(function () {
                    var $templateClone = $circleTemplate.clone();
                    $templateClone
                        .removeClass('template')
                        .appendTo($assetIndicators);
                });
                $circleTemplate.remove();
            };

            //The reason for cloning assets is to create the
            //illusion of an infinite scrolling and have the transition
            //slide in the user's selected direction.
            var createClonedAssets = function () {
                if ($('.asset-item').first().hasClass('clone')) {
                    return;  //already been done
                }
                var $first = $('.asset-item:first'),
                    $last = $('.asset-item:last');

                $last.clone().prependTo($('.asset-list'));
                $first.clone().appendTo($('.asset-list'));

                $('.asset-item').first().addClass('clone');
                $('.asset-item').last().addClass('clone');
            };

            var init = function () {

                createClonedAssets();

                var $assetItems = $('.asset-item');

                addCircleAssetIndicators();

                viewableWidth = $('.slide-viewer').get(0).clientWidth;

                $('.asset-list').css({width: ($assetItems.length * viewableWidth)});
                //set all assets sizes in pixels
                $assetItems.each(function() {
                    $(this).css( {width: viewableWidth});
                });

                executeTransition($('.asset-item:not(.clone):first').index(), 'next', '0s');

                $('.asset-item:not(.clone):first').addClass('is-active');
                $('.asset-indicators .circle').removeClass('is-active');
                $('.asset-indicators .circle:first').addClass('is-active');
            };

            //build the asset indicators, and set up the first rotation element
            init();

            var slideShowInterval = setInterval(function () {
               startTransition('next');
            }, 8000);

            var adjustSlideShowViewableArea = function() {
                init();
            }

            $('.slide-nav, .asset-indicators').on('click', function (e) {
                e.preventDefault();
                var direction = $(e.currentTarget).data('nav-direction'),
                    visibleAssetIndex = $('.asset-item.is-active').index();

                if (direction ) {
                    startTransition(direction);
                } else {
                    var transitionToIndex = $(e.currentTarget).find('li').index($(e.target).parents('li'));
                    if (visibleAssetIndex !== transitionToIndex + 1) { // + 1 because we've prepended a cloned element to list
                        startTransition(direction, transitionToIndex);
                    }
                }
                //clear the slide show scrolling
                clearInterval(slideShowInterval);
            });

            $('.slide-nav').hover(function (e) {
                $(e.currentTarget).find('a').addClass('hovered');
            }, function(e) {
                $(e.currentTarget).find('a').removeClass('hovered');
            });

            $(window).resize( function() {
                adjustSlideShowViewableArea();
            });
        }; // end slideshow

        // public properties/methods
        return {
            init: function () {
                registerModuleEvents();
                slideShow();
            }
        };
    }());
}(window, jQuery));<commit_msg>modified the transition<commit_after>(function(win, $) {
    "use strict";
    var app = win.app = (typeof (win.app) !== 'undefined') ? win.app : {};

    (app.main = function () {
        // private properties/methods here
        var showMenuPanel = function (panelType, parentOffsets) {
            var $panel = $('#' + panelType);

            $panel
                .css({ left: parentOffsets.right - $panel.get(0).clientWidth, top: parentOffsets.bottom, opacity: 1 }); //.animate({ opacity: 1.0 }, 500);
        };

        var hideMenuPanel = function (panelType) {
            $('#' + panelType).animate({ opacity: 0 }, 200, function() {
                $(this).css({ left: -99999 });
            });
        }

        var navDropdownHoverIn = function(e) {
            //console.log('hovering over ' + $(e.currentTarget).data('dropdown-type') + ' left:' + e.currentTarget.offsetLeft + ' width:' + e.currentTarget.offsetWidth + ' top:' + e.currentTarget.offsetTop + ' height:' + e.currentTarget.offsetHeight + ' right:' + (e.currentTarget.offsetLeft + e.currentTarget.offsetWidth) + ' bottom:' + (e.currentTarget.offsetTop + e.currentTarget.offsetHeight));
            showMenuPanel($(e.currentTarget).data('dropdown-type'), { right: e.currentTarget.offsetLeft + e.currentTarget.offsetWidth, bottom: e.currentTarget.offsetTop + e.currentTarget.offsetHeight});
        };

        var navDropdownHoverOut = function(e) {
            //console.log('hover outside of element ' + $(e.currentTarget).data('dropdown-type'));
            hideMenuPanel($(e.currentTarget).data('dropdown-type'));
        };

        var showCountryContent = function (continent) {
            $('.country-list').each(function () {
                var $this = $(this);
                $this.css({ opacity: 0 }).hide();
                if ($this.data('continent') === continent) {
                    $this.css({ opacity: 0 }).show().css( { opacity: 1 });
                }
            });
        };

        var getNewsletterClickHandler = function(e) {
            //console.log('subscribe to the newsletter');
        };

        var documentReadyComplete = function(){
                $('.featured-tours article').equalizeHeight();
        };

        var registerModuleEvents = function() {
            $('.nav-item').hover(navDropdownHoverIn, navDropdownHoverOut);
            $('.cta').on('click', getNewsletterClickHandler);
            $('.continent-link').hover(function(e) {
                //on mouse in (over)
                var $this = $(this);
                $this.closest('ul')
                    .find('.link-primary-text-color')
                    .removeClass('link-primary-text-color')
                    .end();
                $this.addClass('link-primary-text-color');
                showCountryContent($this.data('continent'));
            });

            //just want to close the panel when these are clicked
            $('.country-link, .panel-cta ').on('click', function(e) {
                hideMenuPanel($(e.currentTarget).closest('.nav-item').data('dropdown-type'));
            });

            $('.observable').on('document.ready.complete', documentReadyComplete);

            // var $navbarClone = $('nav').clone();
            // $('.sticky-header').append($navbarClone);

            $(window).on('scroll', function(e) {
                var $header = $('.sticky-header');
                $(e.currentTarget).scrollTop();
                if ($(e.currentTarget).scrollTop() > 60) {
                    $header.addClass('active');
                    //console.log('scrolling down: ' + $(e.currentTarget).scrollTop());
                } else {
                    $header.removeClass('active')
                    //console.log('scrolling up: ' + $(e.currentTarget).scrollTop());;
                }
            });
        };

        //implements a horizontal slide show.
        var slideShow = function () {

            var viewableWidth = $('.slide-viewer').get(0).clientWidth;

            var executeTransition = function(transitionToAssetIndex, direction, transitionDuration) {
                var defaultTransition = '1s';

                if (typeof(transitionDuration) === 'undefined') {
                    transitionDuration = defaultTransition;
                }

                $('.asset-list')
                    .css({ transition: transitionDuration, transform: 'translate3d(-' + (transitionToAssetIndex * viewableWidth) + 'px, 0, 0)'})
                    .one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) {
                        //if we've landed on a cloned asset, at the beginning or the end, we need to move to the correct asset (the one this is a clone of)
                        //By sending in a zero second transition, the active image will be switched to the real image, but the user doesn't experience the
                        //change and the correct item will be queued up for the next transition.
                        if ($('.asset-item:eq('+ transitionToAssetIndex +')').hasClass('clone')) {
                            var $assetToShow = $('.asset-item:last').index() === transitionToAssetIndex ? $('.asset-item:not(.clone):first') : $('.asset-item:not(.clone):last');
                            executeTransition($assetToShow.index(), direction, '0s');
                            $('.asset-item:eq(' + transitionToAssetIndex + ')').removeClass('is-active');
                            $assetToShow.addClass('is-active');
                        }
                        // set the asset indicators
                        $('.asset-indicators .circle.is-active').removeClass('is-active');
                        $('.asset-indicators li:eq(' + ($('.asset-item.is-active').index() - 1) + ') .circle').addClass('is-active');
                    });
                };

            var startTransition = function (direction, index) {
                //console.log('direction: ' + direction + ' index:' + index);
                var visibleAssetIndex = $('.asset-item.is-active').index(),
                    transitionToAssetIndex = typeof (index) !== 'undefined'
                                             ? index + 1
                                             : direction === 'next' ? visibleAssetIndex + 1 : visibleAssetIndex - 1, // navigating by prev/next
                    //if the user is navigating by index, transitionDirection allows us to transition in the proper direction.
                    transitionDirection = typeof (index) !== 'undefined'
                                          ? (transitionToAssetIndex > visibleAssetIndex ? 'next' : 'prev')
                                          : direction;

                $('.asset-item:eq(' + visibleAssetIndex + ')').removeClass('is-active');
                $('.asset-item:eq(' + transitionToAssetIndex + ')').addClass('is-active');

                if (visibleAssetIndex !== transitionToAssetIndex) {
                    executeTransition(transitionToAssetIndex, transitionDirection); //uses default transition duration
                }
            };

            var addCircleAssetIndicators = function () {
                //create asset indicator for each image
                var $assetIndicators = $('.asset-indicators'),
                    $circleTemplate = $assetIndicators.find('.template');

                $('.asset-item:not(.clone)').each(function () {
                    var $templateClone = $circleTemplate.clone();
                    $templateClone
                        .removeClass('template')
                        .appendTo($assetIndicators);
                });
                $circleTemplate.remove();
            };

            //The reason for cloning assets is to create the
            //illusion of an infinite scrolling and have the transition
            //slide in the user's selected direction.
            var createClonedAssets = function () {
                if ($('.asset-item').first().hasClass('clone')) {
                    return;  //already been done
                }
                var $first = $('.asset-item:first'),
                    $last = $('.asset-item:last');

                $last.clone().prependTo($('.asset-list'));
                $first.clone().appendTo($('.asset-list'));

                $('.asset-item').first().addClass('clone');
                $('.asset-item').last().addClass('clone');
            };

            var init = function () {

                createClonedAssets();

                var $assetItems = $('.asset-item');

                addCircleAssetIndicators();

                viewableWidth = $('.slide-viewer').get(0).clientWidth;

                $('.asset-list').css({width: ($assetItems.length * viewableWidth)});
                //set all assets sizes in pixels
                $assetItems.each(function() {
                    $(this).css( {width: viewableWidth});
                });

                executeTransition($('.asset-item:not(.clone):first').index(), 'next', '0s');

                $('.asset-item:not(.clone):first').addClass('is-active');
                $('.asset-indicators .circle').removeClass('is-active');
                $('.asset-indicators .circle:first').addClass('is-active');
            };

            //build the asset indicators, and set up the first rotation element
            init();

            var slideShowInterval = setInterval(function () {
               startTransition('next');
            }, 6000);

            var adjustSlideShowViewableArea = function() {
                init();
            }

            $('.slide-nav, .asset-indicators').on('click', function (e) {
                e.preventDefault();
                var direction = $(e.currentTarget).data('nav-direction'),
                    visibleAssetIndex = $('.asset-item.is-active').index();

                if (direction ) {
                    startTransition(direction);
                } else {
                    var transitionToIndex = $(e.currentTarget).find('li').index($(e.target).parents('li'));
                    if (visibleAssetIndex !== transitionToIndex + 1) { // + 1 because we've prepended a cloned element to list
                        startTransition(direction, transitionToIndex);
                    }
                }
                //clear the slide show scrolling
                clearInterval(slideShowInterval);
            });

            $('.slide-nav').hover(function (e) {
                $(e.currentTarget).find('a').addClass('hovered');
            }, function(e) {
                $(e.currentTarget).find('a').removeClass('hovered');
            });

            $(window).resize( function() {
                adjustSlideShowViewableArea();
            });
        }; // end slideshow

        // public properties/methods
        return {
            init: function () {
                registerModuleEvents();
                slideShow();
            }
        };
    }());
}(window, jQuery));<|endoftext|>


<commit_before>var LCD = require('lcd'),
    device = new LCD({
        rs: 12,
        e: 21,
        data: [5, 6, 17, 18],
        cols: 16,
        rows: 2
    }),
    message = '',
    print = function(str, callback) {
        message = str;
        device.clear(function() {
            if (str.length > 16) {
                device.setCursor(0, 0);
                device.print(str.substring(0, 16), function() {
                    device.setCursor(0, 1);
                    device.print(str.substring(16, 32), function() {
                        if (callback) {
                            callback();
                        }
                    });
                });
            } else {
                device.setCursor(0, 0);
                device.print(str, function() {
                    if (callback) {
                        callback();
                    }
                });
            }
        });
    }
module.exports = {
    device: device,
    print: print,
    noNew: function() {
        print('No new messages!');
        setTimeout(function() {
            print(message);
        }, 1000);
    }
}<commit_msg>adjusting time<commit_after>var LCD = require('lcd'),
    device = new LCD({
        rs: 12,
        e: 21,
        data: [5, 6, 17, 18],
        cols: 16,
        rows: 2
    }),
    message = '',
    print = function(str, callback) {
        message = str;
        device.clear(function() {
            if (str.length > 16) {
                device.setCursor(0, 0);
                device.print(str.substring(0, 16), function() {
                    device.setCursor(0, 1);
                    device.print(str.substring(16, 32), function() {
                        if (callback) {
                            callback();
                        }
                    });
                });
            } else {
                device.setCursor(0, 0);
                device.print(str, function() {
                    if (callback) {
                        callback();
                    }
                });
            }
        });
    }
module.exports = {
    device: device,
    print: print,
    noNew: function() {
        print('No new messages!');
        setTimeout(function() {
            print(message);
        }, 2);
    }
}<|endoftext|>


<commit_before>// Read a page's GET URL variables and return them as an associative array.
// Source: http://jquery-howto.blogspot.com/2009/09/get-url-parameters-values-with-jquery.html
var params = function () {
  var vars = [], hash;
  var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
  for(var i = 0; i < hashes.length; i++) {
    hash = hashes[i].split('=');
    vars.push(hash[0]);
    vars[hash[0]] = hash[1];
  }
  return vars;
}()
var user = params.user,
    repo = params.repo,
    head = document.getElementsByTagName('head')[0],
    gravatar = document.getElementById('gravatar');
    username = document.getElementById('gh-username');
    reponums = document.getElementById('gh-repo-nums');

// Add commas to numbers
function addCommas(n) {
  return String(n).replace(/(\d)(?=(\d{3})+$)/g, '$1,')
}

function jsonp(path, callback_var) {
  var el = document.createElement('script');
  el.src = path + '?callback=' + callback_var;
  head.insertBefore(el, head.firstChild);
}

function callback_user(obj) {
    gravatar.innerHTML = '';
    username.innerHTML = obj.data.name;
    reponums.innerHTML = addCommas(obj.data.public_repos) + ' public repositories';
}

function callback_events(obj) {
	var activities = obj.data;
		$.each(activities, function() {
			console.log(this);
			switch(this.type)
			{
				case 'WatchEvent':
					verb = 'starred';
					break;
				case 'PushEvent':
					verb = 'pushed a commit to';
					break;
				default:
					verb = 'did something';
					break;
			}
			var individualstory = '
';
			individualstory += '
';
			individualstory += '
' + this.actor.login + ' ' + verb + ' ' + this.repo.name + '
';
			individualstory += '
';
			individualstory += '
';
			$('#activities-container').append(individualstory);
			$('#individual-story-' + this.id).fadeIn(3000);
		});
}

jsonp('https://api.github.com/users/' + user, 'callback_user');
jsonp('https://api.github.com/users/' + user + '/events', 'callback_events');
<commit_msg>Revert "remove console log"<commit_after>// Read a page's GET URL variables and return them as an associative array.
// Source: http://jquery-howto.blogspot.com/2009/09/get-url-parameters-values-with-jquery.html
var params = function () {
  var vars = [], hash;
  var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
  for(var i = 0; i < hashes.length; i++) {
    hash = hashes[i].split('=');
    vars.push(hash[0]);
    vars[hash[0]] = hash[1];
  }
  return vars;
}()
var user = params.user,
    repo = params.repo,
    head = document.getElementsByTagName('head')[0],
    gravatar = document.getElementById('gravatar');
    username = document.getElementById('gh-username');
    reponums = document.getElementById('gh-repo-nums');

// Add commas to numbers
function addCommas(n) {
  return String(n).replace(/(\d)(?=(\d{3})+$)/g, '$1,')
}

function jsonp(path, callback_var) {
  var el = document.createElement('script');
  el.src = path + '?callback=' + callback_var;
  head.insertBefore(el, head.firstChild);
}

function callback_user(obj) {
    gravatar.innerHTML = '';
    username.innerHTML = obj.data.name;
    reponums.innerHTML = addCommas(obj.data.public_repos) + ' public repositories';
}

function callback_events(obj) {
	var activities = obj.data;
		$.each(activities, function() {

			switch(this.type)
			{
				case 'WatchEvent':
					verb = 'starred';
					break;
				case 'PushEvent':
					verb = 'pushed a commit to';
					break;
				default:
					verb = 'did something';
					break;
			}
			console.log(this);
			var individualstory = '
';
			individualstory += '
';
			individualstory += '
' + this.actor.login + ' ' + verb + ' ' + this.repo.name + '
';
			individualstory += '
';
			individualstory += '
';
			$('#activities-container').append(individualstory);
			$('#individual-story-' + this.id).fadeIn(3000);
		});
}

jsonp('https://api.github.com/users/' + user, 'callback_user');
jsonp('https://api.github.com/users/' + user + '/events', 'callback_events');
<|endoftext|>


<commit_before>$(function() {

	$.getJSON('get-tweets.php',function(feeds) {
	    var date = parseTwitterDate(feeds[0].created_at);
	    var entry = "@nickjvm: " + feeds[0].text + " — " + moment(date).fromNow();

	    $("#twitter .tweet").html(entry.parseURL().parseUsername().parseHashtag());
	    $("#twitter").fadeIn();
	});

	$('.email').magnificPopup({
		type: 'inline',
		preloader: false,
		focus: '#name',
		mainClass:"mfp-zoom-in",

		// When elemened is focused, some mobile browsers in some cases zoom in
		// It looks not nice, so we disable it:
		callbacks: {
			beforeOpen: function() {
			  if($(window).width() < 700) {
			    this.st.focus = false;
			  } else {
			    this.st.focus = '#name';
			  }
			},
			beforeClose:function() {
				clearForm("#contact");
				$("#contact").removeClass("thinking");
				validator.resetForm();
			}
		}
	});

	var validator = $("#contact").validate({
		errorPlacement:function() {}
	});

	$("#contact .cancel").on("click",function(e) {
		e.preventDefault();
		validator.resetForm();
		$.magnificPopup.close()
	});

	$("#contact").on("submit",function(e) {
		if(!validator.valid()) {
			return false;
		}
		var $self = $(this);
		//do some cleanup
		e.preventDefault();
		validator.resetForm();
		$self.addClass("thinking");
		var data = $(this).serialize();

		var request = $.ajax({
			type: "POST",
			url: "contact.php",
			data: data
		})

		request.done(function(response,success) {
			$self.removeClass("thinking");
			response = $.parseJSON(response);
			if(response.status) {
				//passed validation - got a good response!
				clearForm("#contact");
				$.magnificPopup.close();
				$("#success").css({
					top:0
				})
				setTimeout(function() {
					$("#success").css({
						top:-40
					})
				},2500);

			} else {
				if(response.code == 1) {
					//something went wrong!
					$.each(response.errors,function() {
						$("#contact [name='"+this+"']").addClass("error");
					});
			 	} else if (response.code == 2) {
			 		$("#contact .alert-error").text("Something went wrong. Please try again later");
			 	}
			}
		});

	});
});
var clearForm = function(selector) {
	$(selector).find(":input").val("").prop("checked",false);
}

var contentTop = $(window).width() < 480 ? 150 : 360;
window.onscroll = function() {
	if(!navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
		var n = Math.ceil($(window).scrollTop() / 2.5);
		$("#hero").css({
			"-webkit-transform":"translateY(-" + n + "px)",
			"-moz-transform":"translateY(-" + n + "px)",
			"-ms-transform":"translateY(-" + n + "px)",
			"transform":"translateY(-" + n + "px)"
		});
	}
	if($(window).scrollTop() > contentTop) {
		$("header").addClass("shrink");
	} else {
		$("header").removeClass("shrink");
	}
}

function parseTwitterDate(text) {
	//IE doesn't like twitter's created_at date format, need to move a few things around
	var date = text.split(" ");
	return new Date(date[0] + " " + date[1] + " " + date[2] + " " + date[5] + " " + date[3] + " " + date[4]);
}

String.prototype.parseURL = function() {
	return this.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&~\?\/.=]+/g, function(url) {
		return url.link(url);
	});
};
    String.prototype.parseUsername = function() {
	return this.replace(/[@]+[A-Za-z0-9-_]+/g, function(u) {
		var username = u.replace("@","")
		return u.link("http://twitter.com/"+username);
	});
};
    String.prototype.parseHashtag = function() {
	return this.replace(/[#]+[A-Za-z0-9-_]+/g, function(t) {
		var tag = t.replace("#","%23")
		return t.link("http://search.twitter.com/search?q="+tag);
	});
};
<commit_msg>Add removalDelay for popup<commit_after>$(function() {

	$.getJSON('get-tweets.php',function(feeds) {
	    var date = parseTwitterDate(feeds[0].created_at);
	    var entry = "@nickjvm: " + feeds[0].text + " — " + moment(date).fromNow();

	    $("#twitter .tweet").html(entry.parseURL().parseUsername().parseHashtag());
	    $("#twitter").fadeIn();
	});

	$('.email').magnificPopup({
		type: 'inline',
		preloader: false,
		focus: '#name',
		mainClass:"mfp-zoom-in",
		removalDelay: 300,
		// When elemened is focused, some mobile browsers in some cases zoom in
		// It looks not nice, so we disable it:
		callbacks: {
			beforeOpen: function() {
			  if($(window).width() < 700) {
			    this.st.focus = false;
			  } else {
			    this.st.focus = '#name';
			  }
			},
			beforeClose:function() {
				clearForm("#contact");
				$("#contact").removeClass("thinking");
				validator.resetForm();
			}
		}
	});

	var validator = $("#contact").validate({
		errorPlacement:function() {}
	});

	$("#contact .cancel").on("click",function(e) {
		e.preventDefault();
		validator.resetForm();
		$.magnificPopup.close()
	});

	$("#contact").on("submit",function(e) {
		if(!validator.valid()) {
			return false;
		}
		var $self = $(this);
		//do some cleanup
		e.preventDefault();
		validator.resetForm();
		$self.addClass("thinking");
		var data = $(this).serialize();

		var request = $.ajax({
			type: "POST",
			url: "contact.php",
			data: data
		})

		request.done(function(response,success) {
			$self.removeClass("thinking");
			response = $.parseJSON(response);
			if(response.status) {
				//passed validation - got a good response!
				clearForm("#contact");
				$.magnificPopup.close();
				$("#success").css({
					top:0
				})
				setTimeout(function() {
					$("#success").css({
						top:-40
					})
				},2500);

			} else {
				if(response.code == 1) {
					//something went wrong!
					$.each(response.errors,function() {
						$("#contact [name='"+this+"']").addClass("error");
					});
			 	} else if (response.code == 2) {
			 		$("#contact .alert-error").text("Something went wrong. Please try again later");
			 	}
			}
		});

	});
});
var clearForm = function(selector) {
	$(selector).find(":input").val("").prop("checked",false);
}

var contentTop = $(window).width() < 480 ? 150 : 360;
window.onscroll = function() {
	if(!navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
		var n = Math.ceil($(window).scrollTop() / 2.5);
		$("#hero").css({
			"-webkit-transform":"translateY(-" + n + "px)",
			"-moz-transform":"translateY(-" + n + "px)",
			"-ms-transform":"translateY(-" + n + "px)",
			"transform":"translateY(-" + n + "px)"
		});
	}
	if($(window).scrollTop() > contentTop) {
		$("header").addClass("shrink");
	} else {
		$("header").removeClass("shrink");
	}
}

function parseTwitterDate(text) {
	//IE doesn't like twitter's created_at date format, need to move a few things around
	var date = text.split(" ");
	return new Date(date[0] + " " + date[1] + " " + date[2] + " " + date[5] + " " + date[3] + " " + date[4]);
}

String.prototype.parseURL = function() {
	return this.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&~\?\/.=]+/g, function(url) {
		return url.link(url);
	});
};
    String.prototype.parseUsername = function() {
	return this.replace(/[@]+[A-Za-z0-9-_]+/g, function(u) {
		var username = u.replace("@","")
		return u.link("http://twitter.com/"+username);
	});
};
    String.prototype.parseHashtag = function() {
	return this.replace(/[#]+[A-Za-z0-9-_]+/g, function(t) {
		var tag = t.replace("#","%23")
		return t.link("http://search.twitter.com/search?q="+tag);
	});
};
<|endoftext|>