<commit_before>var _ = require('underscore');
var Fiber = require('fibers');
var Future = require('fibers/future');

var files = require('./files.js');
var release = require('./release.js');
var buildmessage = require('./buildmessage.js');
var fiberHelpers = require('./fiber-helpers.js');
var runLog = require('./run-log.js');

var Console = require('./console.js').Console;

var Proxy = require('./run-proxy.js').Proxy;
var Selenium = require('./run-selenium.js').Selenium;
var HttpProxy = require('./run-httpproxy.js').HttpProxy;
var AppRunner = require('./run-app.js').AppRunner;
var MongoRunner = require('./run-mongo.js').MongoRunner;
var Updater = require('./run-updater.js').Updater;

// options: projectContext, proxyPort, proxyHost, appPort, appHost,
// buildOptions, settingsFile, banner, onRunEnd, onFailure,
// watchForChanges, quiet, rootUrl, mongoUrl, oplogUrl, mobileServerUrl,
// disableOplog
var Runner = function (options) {
  var self = this;
  self.projectContext = options.projectContext;

  if (! _.has(options, 'proxyPort'))
    throw new Error("no proxyPort?");

  var listenPort = options.proxyPort;
  var mongoPort = parseInt(listenPort) + 1;
  self.specifiedAppPort = options.appPort;
  self.regenerateAppPort();

  self.stopped = false;
  self.quiet = options.quiet;
  self.banner = options.banner ||
    files.convertToOSPath(files.prettyPath(self.projectContext.projectDir));
  if (options.rootUrl) {
    self.rootUrl = options.rootUrl;
  } else if (options.proxyHost) {
    self.rootUrl = 'http://' + options.proxyHost + ':' + listenPort + '/';
  } else {
    self.rootUrl = 'http://localhost:' + listenPort + '/';
  }

  self.extraRunners = options.extraRunners;

  self.proxy = new Proxy({
    listenPort: listenPort,
    listenHost: options.proxyHost,
    proxyToPort: self.appPort,
    proxyToHost: options.appHost,
    onFailure: options.onFailure
  });

  self.httpProxy = null;
  if (options.httpProxyPort) {
    self.httpProxy = new HttpProxy({
      listenPort: options.httpProxyPort
    });
  }

  self.mongoRunner = null;
  var mongoUrl, oplogUrl;
  if (options.mongoUrl) {
    mongoUrl = options.mongoUrl;
    oplogUrl = options.disableOplog ? null : options.oplogUrl;
  } else {
    self.mongoRunner = new MongoRunner({
      appDir: self.projectContext.projectDir,
      port: mongoPort,
      onFailure: options.onFailure,
      // For testing mongod failover, run with 3 mongod if the env var is
      // set. Note that data is not preserved from one run to the next.
      multiple: !!process.env.METEOR_TEST_MULTIPLE_MONGOD_REPLSET
    });

    mongoUrl = self.mongoRunner.mongoUrl();
    oplogUrl = options.disableOplog ? null : self.mongoRunner.oplogUrl();
  }

  self.updater = new Updater;

  require("./safe-pathwatcher.js").testDirectory(
    files.pathJoin(self.projectContext.projectDir, ".meteor", "local")
  );

  self.appRunner = new AppRunner({
    projectContext: self.projectContext,
    port: self.appPort,
    listenHost: options.appHost,
    mongoUrl: mongoUrl,
    oplogUrl: oplogUrl,
    mobileServerUrl: options.mobileServerUrl,
    buildOptions: options.buildOptions,
    rootUrl: self.rootUrl,
    settingsFile: options.settingsFile,
    debugPort: options.debugPort,
    proxy: self.proxy,
    onRunEnd: options.onRunEnd,
    watchForChanges: options.watchForChanges,
    noRestartBanner: self.quiet,
    recordPackageUsage: options.recordPackageUsage,
    omitPackageMapDeltaDisplayOnFirstRun: (
      options.omitPackageMapDeltaDisplayOnFirstRun)
  });

  self.selenium = null;
  if (options.selenium) {
    self.selenium = new Selenium({
      runner: self,
      browser: options.seleniumBrowser
    });
  }
};

_.extend(Runner.prototype, {
  // XXX leave a pidfile and check if we are already running
  start: function () {
    var self = this;

    // XXX: Include all runners, and merge parallel-launch patch
    var allRunners = [ ] ;
    allRunners = allRunners.concat(self.extraRunners);
    _.each(allRunners, function (runner) {
      if (!runner) return;
      runner.prestart && runner.prestart();
    });

    self.proxy.start();

    // print the banner only once we've successfully bound the port
    if (! self.quiet && ! self.stopped) {
      runLog.log("[[[[[ " + self.banner + " ]]]]]\n");
      runLog.log("Started proxy.",  { arrow: true });
    }

    self._startMongoAsync();

    if (! self.stopped) {
      self.updater.start();
    }

    // print the banner only once we've successfully bound the port
    if (! self.stopped && self.httpProxy) {
      self.httpProxy.start();
      if (! self.quiet) {
        runLog.log("Started http proxy.", { arrow: true });
      }
    }

    _.forEach(self.extraRunners, function (extraRunner) {
      if (! self.stopped) {
        var title = extraRunner.title;
        buildmessage.enterJob({ title: "starting " + title }, function () {
          extraRunner.start();
        });
        if (! self.quiet && ! self.stopped)
          runLog.log("Started " + title + ".",  { arrow: true });
      }
    });

    if (! self.stopped) {
      buildmessage.enterJob({ title: "starting your app" }, function () {
        self.appRunner.start();
      });
      if (! self.quiet && ! self.stopped)
        runLog.log("Started your app.",  { arrow: true });
    }

    if (! self.stopped && ! self.quiet) {
      runLog.log("");
      runLog.log("App running at: " + self.rootUrl,  { arrow: true });
    }

    if (self.selenium && ! self.stopped) {
      buildmessage.enterJob({ title: "starting Selenium" }, function () {
        self.selenium.start();
      });
      if (! self.quiet && ! self.stopped)
        runLog.log("Started Selenium.", { arrow: true });
    }

    // XXX It'd be nice to (cosmetically) handle failure better. Right
    // now we overwrite the "starting foo..." message with the
    // error. It'd be better to overwrite it with "failed to start
    // foo" and then print the error.
  },

  _startMongoAsync: function () {
    var self = this;
    if (! self.stopped && self.mongoRunner) {
      var future = new Future;
      self.appRunner.awaitFutureBeforeStart(future);
      Fiber(function () {
        self.mongoRunner.start();
        if (! self.stopped && ! self.quiet) {
          runLog.log("Started MongoDB.",  { arrow: true });
        }
        // This future might also get resolved by appRunner.stop, so we need
        // this check here (which is why we can't use f.future(), which does not
        // have this check).
        future.isResolved() || future.return();
      }).run();
    }
  },

  // Idempotent
  stop: function () {
    var self = this;
    if (self.stopped)
      return;

    self.stopped = true;
    self.proxy.stop();
    self.httpProxy && self.httpProxy.stop();
    self.updater.stop();
    self.mongoRunner && self.mongoRunner.stop();
    _.forEach(self.extraRunners, function (extraRunner) {
      extraRunner.stop();
    });
    self.appRunner.stop();
    self.selenium && self.selenium.stop();
    // XXX does calling this 'finish' still make sense now that runLog is a
    // singleton?
    runLog.finish();
  },

  // Call this whenever you want to regenerate the app's port (if it is not
  // explicitly specified by the user).
  //
  // Rationale: if we randomly chose a port that's in use and the app failed to
  // listen on it, we should try a different port when we restart the app!
  regenerateAppPort: function () {
    var self = this;
    if (self.specifiedAppPort) {
      self.appPort = self.specifiedAppPort;
    } else {
      self.appPort = require('./utils.js').randomPort();
    }
    if (self.proxy)
      self.proxy.proxyToPort = self.appPort;
    if (self.appRunner)
      self.appRunner.port = self.appPort;
  }
});

// Run the app and all of its associated processes. Runs (and does not
// return) until an unrecoverable failure happens. Logs to
// stdout. Returns a suggested exit code.
//
// If 'once' is set, run the app process exactly once and pass through
// its exit code. Return an exit code of 255 if the app process was
// killed by a signal and 254 if the app process could not start
// (build failure, invalid program name, database couldn't start, and
// so on).
//
// If the 'once' option is not set, the default, restart the app
// process if it crashes or if source files change. (Non-app
// processes, such as the database, are always restarted as
// necessary.) The function will only return if there is an
// unrecoverable error, which generally means an error that could not
// be fixed by source code changes (such as the database refusing to
// run), but also currently includes Meteor version mismatches. So the
// exit code will always be 254 because in all other cases we'll
// persevere.
//
// Options:
//
// - proxyPort: the port to connect to to access the application (we will
//   run a proxy here that proxies to the actual app process). required
// - buildOptions: 'buildOptions' argument to bundler.bundle()
// - settingsFile: path to file containing deploy-time settings
// - once: see above
// - banner: replace the application path that is normally printed on
//   startup with an arbitrary string (eg, 'Tests')
// - rootUrl: tell the app that traffic at this URL will be routed to
//   it at '/' (used by the app to construct absolute URLs)
// - disableOplog: don't use oplog tailing
// - mongoUrl: don't start a mongo process; instead use the mongo at
//   this mongo URL
// - oplogUrl: URL of the mongo oplog to use. if mongoUrl isn't
//   set (we're starting a mongo) a default will be provided, but can
//   be overridden. if mongoUrl is set, you must set this or you don't
//   get oplog tailing.
// - recordPackageUsage: (default true) if set to false, don't send
//   information about packages used by this app to the package stats
//   server.
exports.run = function (options) {
  var runOptions = _.clone(options);
  var once = runOptions.once;
  delete runOptions.once;

  var fut = new Future;

  _.extend(runOptions, {
    onFailure: function () {
      // Ensure that runner stops now. You might think this is unnecessary
      // because the runner is stopped immediately after `fut.wait()`, but if
      // the failure happens while runner.start() is still running, we want the
      // rest of start to stop, and it's not like fut['return'] magically makes
      // us jump to a fut.wait() that hasn't happened yet!.
      runner.stop();
      fut.isResolved() || fut['return']({ outcome: 'failure' });
    },
    onRunEnd: function (result) {
      if (once ||
          result.outcome === "conflicting-versions" ||
          result.outcome === "wrong-release" ||
          result.outcome === "outdated-cordova-platforms" ||
          result.outcome === "outdated-cordova-plugins" ||
          (result.outcome === "terminated" &&
           result.signal === undefined && result.code === undefined)) {
        // Allow run() to continue (and call runner.stop()) only once the
        // AppRunner has processed our "return false"; otherwise we deadlock.
        process.nextTick(function () {
          fut.isResolved() || fut['return'](result);
        });
        return false;  // stop restarting
      }
      runner.regenerateAppPort();
      return true;  // restart it
    },
    watchForChanges: ! once,
    quiet: once
  });

  var runner = new Runner(runOptions);
  runner.start();
  var result = fut.wait();
  runner.stop();

  if (result.outcome === "conflicting-versions") {
    Console.error(
      "The constraint solver could not find a set of package versions to",
      "use that would satisfy the constraints of .meteor/versions and",
      ".meteor/packages. This could be caused by conflicts in",
      ".meteor/versions, conflicts in .meteor/packages, and/or",
      "inconsistent changes to the dependencies in local packages.");
    return 254;
  }

  if (result.outcome === "outdated-cordova-plugins") {
    Console.error("Your app's Cordova plugins have changed.");
    Console.error("Restart meteor to use the new set of plugins.");
    return 254;
  }

  if (result.outcome === "outdated-cordova-platforms") {
    Console.error("Your app's platforms have changed.");
    Console.error("Restart meteor to use the new set of platforms.");
    return 254;
  }

  if (result.outcome === "wrong-release") {
    if (once)
      // We lost a race where the user ran 'meteor update' and 'meteor
      // run --once' simultaneously.
      throw new Error("wrong release?");

    // If the user did not specify a --release on the command line,
    // and simultaneously runs `meteor update` during this run, just
    // exit and let them restart the run. (We can do something fancy
    // like allowing this to work if the tools version didn't change,
    // or even springboarding if the tools version does change, but
    // this (which prevents weird errors) is a start.)
    var from = release.current.getDisplayName();
    var to = result.displayReleaseNeeded;
    Console.error(
      "Your app has been updated to " + to + " from " + from + ".",
      "Restart meteor to use the new release.");
    return 254;
  }

  if (result.outcome === "failure" ||
      (result.outcome === "terminated" &&
       result.signal === undefined && result.code === undefined)) {
    // Fatal problem with something other than the app process. An
    // explanation should already have been logged.
    return 254;
  }

  if (once && result.outcome === "bundle-fail") {
    Console.arrowError("Build failed:\n\n" +
                       result.errors.formatMessages());
    return 254;
  }

  if (once && result.outcome === "terminated") {
    if (result.signal) {
      Console.error("Killed (" + result.signal + ")");
      return 255;
    } else if (typeof result.code === "number") {
      // We used to print 'Your application is exiting' here, but that
      // seems unnecessarily chatty? once mode is otherwise silent
      return result.code;
    } else {
      // If there is neither a code nor a signal, it means that we
      // failed to start the process. We logged the reason. Probably a
      // bad program name.
      return 254;
    }
  }

  throw new Error("unexpected outcome " + result.outcome);
};
<commit_msg>Extra line added from rebase<commit_after>var _ = require('underscore');
var Fiber = require('fibers');
var Future = require('fibers/future');

var files = require('./files.js');
var release = require('./release.js');
var buildmessage = require('./buildmessage.js');
var fiberHelpers = require('./fiber-helpers.js');
var runLog = require('./run-log.js');

var Console = require('./console.js').Console;

var Proxy = require('./run-proxy.js').Proxy;
var Selenium = require('./run-selenium.js').Selenium;
var HttpProxy = require('./run-httpproxy.js').HttpProxy;
var AppRunner = require('./run-app.js').AppRunner;
var MongoRunner = require('./run-mongo.js').MongoRunner;
var Updater = require('./run-updater.js').Updater;

// options: projectContext, proxyPort, proxyHost, appPort, appHost,
// buildOptions, settingsFile, banner, onRunEnd, onFailure,
// watchForChanges, quiet, rootUrl, mongoUrl, oplogUrl, mobileServerUrl,
// disableOplog
var Runner = function (options) {
  var self = this;
  self.projectContext = options.projectContext;

  if (! _.has(options, 'proxyPort'))
    throw new Error("no proxyPort?");

  var listenPort = options.proxyPort;
  var mongoPort = parseInt(listenPort) + 1;
  self.specifiedAppPort = options.appPort;
  self.regenerateAppPort();

  self.stopped = false;
  self.quiet = options.quiet;
  self.banner = options.banner ||
    files.convertToOSPath(files.prettyPath(self.projectContext.projectDir));
  if (options.rootUrl) {
    self.rootUrl = options.rootUrl;
  } else if (options.proxyHost) {
    self.rootUrl = 'http://' + options.proxyHost + ':' + listenPort + '/';
  } else {
    self.rootUrl = 'http://localhost:' + listenPort + '/';
  }

  self.extraRunners = options.extraRunners;

  self.proxy = new Proxy({
    listenPort: listenPort,
    listenHost: options.proxyHost,
    proxyToPort: self.appPort,
    proxyToHost: options.appHost,
    onFailure: options.onFailure
  });

  self.httpProxy = null;
  if (options.httpProxyPort) {
    self.httpProxy = new HttpProxy({
      listenPort: options.httpProxyPort
    });
  }

  self.mongoRunner = null;
  var mongoUrl, oplogUrl;
  if (options.mongoUrl) {
    mongoUrl = options.mongoUrl;
    oplogUrl = options.disableOplog ? null : options.oplogUrl;
  } else {
    self.mongoRunner = new MongoRunner({
      appDir: self.projectContext.projectDir,
      port: mongoPort,
      onFailure: options.onFailure,
      // For testing mongod failover, run with 3 mongod if the env var is
      // set. Note that data is not preserved from one run to the next.
      multiple: !!process.env.METEOR_TEST_MULTIPLE_MONGOD_REPLSET
    });

    mongoUrl = self.mongoRunner.mongoUrl();
    oplogUrl = options.disableOplog ? null : self.mongoRunner.oplogUrl();
  }

  self.updater = new Updater;

  self.appRunner = new AppRunner({
    projectContext: self.projectContext,
    port: self.appPort,
    listenHost: options.appHost,
    mongoUrl: mongoUrl,
    oplogUrl: oplogUrl,
    mobileServerUrl: options.mobileServerUrl,
    buildOptions: options.buildOptions,
    rootUrl: self.rootUrl,
    settingsFile: options.settingsFile,
    debugPort: options.debugPort,
    proxy: self.proxy,
    onRunEnd: options.onRunEnd,
    watchForChanges: options.watchForChanges,
    noRestartBanner: self.quiet,
    recordPackageUsage: options.recordPackageUsage,
    omitPackageMapDeltaDisplayOnFirstRun: (
      options.omitPackageMapDeltaDisplayOnFirstRun)
  });

  self.selenium = null;
  if (options.selenium) {
    self.selenium = new Selenium({
      runner: self,
      browser: options.seleniumBrowser
    });
  }
};

_.extend(Runner.prototype, {
  // XXX leave a pidfile and check if we are already running
  start: function () {
    var self = this;

    // XXX: Include all runners, and merge parallel-launch patch
    var allRunners = [ ] ;
    allRunners = allRunners.concat(self.extraRunners);
    _.each(allRunners, function (runner) {
      if (!runner) return;
      runner.prestart && runner.prestart();
    });

    self.proxy.start();

    // print the banner only once we've successfully bound the port
    if (! self.quiet && ! self.stopped) {
      runLog.log("[[[[[ " + self.banner + " ]]]]]\n");
      runLog.log("Started proxy.",  { arrow: true });
    }

    self._startMongoAsync();

    if (! self.stopped) {
      self.updater.start();
    }

    // print the banner only once we've successfully bound the port
    if (! self.stopped && self.httpProxy) {
      self.httpProxy.start();
      if (! self.quiet) {
        runLog.log("Started http proxy.", { arrow: true });
      }
    }

    _.forEach(self.extraRunners, function (extraRunner) {
      if (! self.stopped) {
        var title = extraRunner.title;
        buildmessage.enterJob({ title: "starting " + title }, function () {
          extraRunner.start();
        });
        if (! self.quiet && ! self.stopped)
          runLog.log("Started " + title + ".",  { arrow: true });
      }
    });

    if (! self.stopped) {
      buildmessage.enterJob({ title: "starting your app" }, function () {
        self.appRunner.start();
      });
      if (! self.quiet && ! self.stopped)
        runLog.log("Started your app.",  { arrow: true });
    }

    if (! self.stopped && ! self.quiet) {
      runLog.log("");
      runLog.log("App running at: " + self.rootUrl,  { arrow: true });
    }

    if (self.selenium && ! self.stopped) {
      buildmessage.enterJob({ title: "starting Selenium" }, function () {
        self.selenium.start();
      });
      if (! self.quiet && ! self.stopped)
        runLog.log("Started Selenium.", { arrow: true });
    }

    // XXX It'd be nice to (cosmetically) handle failure better. Right
    // now we overwrite the "starting foo..." message with the
    // error. It'd be better to overwrite it with "failed to start
    // foo" and then print the error.
  },

  _startMongoAsync: function () {
    var self = this;
    if (! self.stopped && self.mongoRunner) {
      var future = new Future;
      self.appRunner.awaitFutureBeforeStart(future);
      Fiber(function () {
        self.mongoRunner.start();
        if (! self.stopped && ! self.quiet) {
          runLog.log("Started MongoDB.",  { arrow: true });
        }
        // This future might also get resolved by appRunner.stop, so we need
        // this check here (which is why we can't use f.future(), which does not
        // have this check).
        future.isResolved() || future.return();
      }).run();
    }
  },

  // Idempotent
  stop: function () {
    var self = this;
    if (self.stopped)
      return;

    self.stopped = true;
    self.proxy.stop();
    self.httpProxy && self.httpProxy.stop();
    self.updater.stop();
    self.mongoRunner && self.mongoRunner.stop();
    _.forEach(self.extraRunners, function (extraRunner) {
      extraRunner.stop();
    });
    self.appRunner.stop();
    self.selenium && self.selenium.stop();
    // XXX does calling this 'finish' still make sense now that runLog is a
    // singleton?
    runLog.finish();
  },

  // Call this whenever you want to regenerate the app's port (if it is not
  // explicitly specified by the user).
  //
  // Rationale: if we randomly chose a port that's in use and the app failed to
  // listen on it, we should try a different port when we restart the app!
  regenerateAppPort: function () {
    var self = this;
    if (self.specifiedAppPort) {
      self.appPort = self.specifiedAppPort;
    } else {
      self.appPort = require('./utils.js').randomPort();
    }
    if (self.proxy)
      self.proxy.proxyToPort = self.appPort;
    if (self.appRunner)
      self.appRunner.port = self.appPort;
  }
});

// Run the app and all of its associated processes. Runs (and does not
// return) until an unrecoverable failure happens. Logs to
// stdout. Returns a suggested exit code.
//
// If 'once' is set, run the app process exactly once and pass through
// its exit code. Return an exit code of 255 if the app process was
// killed by a signal and 254 if the app process could not start
// (build failure, invalid program name, database couldn't start, and
// so on).
//
// If the 'once' option is not set, the default, restart the app
// process if it crashes or if source files change. (Non-app
// processes, such as the database, are always restarted as
// necessary.) The function will only return if there is an
// unrecoverable error, which generally means an error that could not
// be fixed by source code changes (such as the database refusing to
// run), but also currently includes Meteor version mismatches. So the
// exit code will always be 254 because in all other cases we'll
// persevere.
//
// Options:
//
// - proxyPort: the port to connect to to access the application (we will
//   run a proxy here that proxies to the actual app process). required
// - buildOptions: 'buildOptions' argument to bundler.bundle()
// - settingsFile: path to file containing deploy-time settings
// - once: see above
// - banner: replace the application path that is normally printed on
//   startup with an arbitrary string (eg, 'Tests')
// - rootUrl: tell the app that traffic at this URL will be routed to
//   it at '/' (used by the app to construct absolute URLs)
// - disableOplog: don't use oplog tailing
// - mongoUrl: don't start a mongo process; instead use the mongo at
//   this mongo URL
// - oplogUrl: URL of the mongo oplog to use. if mongoUrl isn't
//   set (we're starting a mongo) a default will be provided, but can
//   be overridden. if mongoUrl is set, you must set this or you don't
//   get oplog tailing.
// - recordPackageUsage: (default true) if set to false, don't send
//   information about packages used by this app to the package stats
//   server.
exports.run = function (options) {
  var runOptions = _.clone(options);
  var once = runOptions.once;
  delete runOptions.once;

  var fut = new Future;

  _.extend(runOptions, {
    onFailure: function () {
      // Ensure that runner stops now. You might think this is unnecessary
      // because the runner is stopped immediately after `fut.wait()`, but if
      // the failure happens while runner.start() is still running, we want the
      // rest of start to stop, and it's not like fut['return'] magically makes
      // us jump to a fut.wait() that hasn't happened yet!.
      runner.stop();
      fut.isResolved() || fut['return']({ outcome: 'failure' });
    },
    onRunEnd: function (result) {
      if (once ||
          result.outcome === "conflicting-versions" ||
          result.outcome === "wrong-release" ||
          result.outcome === "outdated-cordova-platforms" ||
          result.outcome === "outdated-cordova-plugins" ||
          (result.outcome === "terminated" &&
           result.signal === undefined && result.code === undefined)) {
        // Allow run() to continue (and call runner.stop()) only once the
        // AppRunner has processed our "return false"; otherwise we deadlock.
        process.nextTick(function () {
          fut.isResolved() || fut['return'](result);
        });
        return false;  // stop restarting
      }
      runner.regenerateAppPort();
      return true;  // restart it
    },
    watchForChanges: ! once,
    quiet: once
  });

  var runner = new Runner(runOptions);
  runner.start();
  var result = fut.wait();
  runner.stop();

  if (result.outcome === "conflicting-versions") {
    Console.error(
      "The constraint solver could not find a set of package versions to",
      "use that would satisfy the constraints of .meteor/versions and",
      ".meteor/packages. This could be caused by conflicts in",
      ".meteor/versions, conflicts in .meteor/packages, and/or",
      "inconsistent changes to the dependencies in local packages.");
    return 254;
  }

  if (result.outcome === "outdated-cordova-plugins") {
    Console.error("Your app's Cordova plugins have changed.");
    Console.error("Restart meteor to use the new set of plugins.");
    return 254;
  }

  if (result.outcome === "outdated-cordova-platforms") {
    Console.error("Your app's platforms have changed.");
    Console.error("Restart meteor to use the new set of platforms.");
    return 254;
  }

  if (result.outcome === "wrong-release") {
    if (once)
      // We lost a race where the user ran 'meteor update' and 'meteor
      // run --once' simultaneously.
      throw new Error("wrong release?");

    // If the user did not specify a --release on the command line,
    // and simultaneously runs `meteor update` during this run, just
    // exit and let them restart the run. (We can do something fancy
    // like allowing this to work if the tools version didn't change,
    // or even springboarding if the tools version does change, but
    // this (which prevents weird errors) is a start.)
    var from = release.current.getDisplayName();
    var to = result.displayReleaseNeeded;
    Console.error(
      "Your app has been updated to " + to + " from " + from + ".",
      "Restart meteor to use the new release.");
    return 254;
  }

  if (result.outcome === "failure" ||
      (result.outcome === "terminated" &&
       result.signal === undefined && result.code === undefined)) {
    // Fatal problem with something other than the app process. An
    // explanation should already have been logged.
    return 254;
  }

  if (once && result.outcome === "bundle-fail") {
    Console.arrowError("Build failed:\n\n" +
                       result.errors.formatMessages());
    return 254;
  }

  if (once && result.outcome === "terminated") {
    if (result.signal) {
      Console.error("Killed (" + result.signal + ")");
      return 255;
    } else if (typeof result.code === "number") {
      // We used to print 'Your application is exiting' here, but that
      // seems unnecessarily chatty? once mode is otherwise silent
      return result.code;
    } else {
      // If there is neither a code nor a signal, it means that we
      // failed to start the process. We logged the reason. Probably a
      // bad program name.
      return 254;
    }
  }

  throw new Error("unexpected outcome " + result.outcome);
};
<|endoftext|>


<commit_before>import $ from 'jquery';

class Core {

  /**
   * Initialize Papi core class.
   */
  static init() {
    const core = new Core();

    core.binds();
    core.setEqualBoxHeights();
    core.setSelectedMenuItem();
    core.addCurrentClassToMenuItem();
  }

  /**
   * Autosave fields on heartbeat.
   *
   * @param {object} e
   * @param {object} xhr
   * @param {object} options
   */
  autosave(e, xhr, options) {
    const fields = jQuery('[name*="papi_"]');
    const data = {};
    const reg = /action\=(.+?)\&/;
    const val = reg.exec(options.data);
    const id  = /post_id\=\d+/.exec(options.data);

    if (val !== null && val.length && val[1] === 'heartbeat' && id != null && id.length) {
        fields.each(function () {
          const $this = $(this);

          data[$this.attr('name')] = $this.val();
      });

      options.data += '&' + jQuery.param(data);
    }
  }

  /**
   * Bind elements with functions.
   */
  binds() {
    $('.papi-box-list > li > p').on('click', this.redirect);
    $('input[name="add-new-page-search"]').on('keyup', this.search);
    $('[data-papi-href]').on('click touchstart', this.redirect);
    $(window).resize(this.setEqualBoxHeights);

    if ('select2' in $.fn) {
      $('.inside .papi-table tr .papi-component-select2').select2();
    }

    $('.papi-meta-type-term button.handlediv').on('click', this.handlediv);

    // Autosave fields on heartbeat.
    $(document).ajaxSend(this.autosave);
  }

  /**
   * Add current class to menu item.
   */
  addCurrentClassToMenuItem() {
    let $submenu = $('.wp-has-current-submenu .wp-submenu');
    let $menuitem = $submenu.find('a[href*="papi-add-new-page"]').parent();

    if (!$menuitem.hasClass('current') && !$submenu.find('li.current').length) {
      $menuitem.addClass('current');
    }
  }

  /**
   * Handle expanded postbox div.
   *
   * @param  {object} e
   */
  handlediv(e) {
    e.preventDefault();

    const $this   = $(this);
    const $parent = $this.parent();
    const $inside = $parent.find('.inside');

    $parent.parent().toggleClass('closed');
    $inside.toggleClass('papi-hide');
    $this.attr('aria-expanded', !$inside.hasClass('papi-hide'));
  }

  /**
   * Redirect to location from `papi-href` data attribute
   * or closest tag with href attribute.
   *
   * @param {object} e
   */
  redirect(e) {
    e.preventDefault();
    let $this    = $(this);
    let papiHref = $this.data().papiHref;

    if (papiHref !== undefined) {
      window.location = papiHref;
    } else {
      window.location = $(this).closest('[href]').attr('href');
    }
  }

  /**
   * Search in page types box list.
   *
   * @param {object} e
   */
  search(e) {
    e.preventDefault();

    let $this = $(this);
    let $list = $('.papi-box-list');
    let val   = $this.val();

    $list.find('.papi-box-item').each(function() {
      let $item = $(this);
      $item[$item.text().toLowerCase().indexOf(val) === -1 ? 'addClass' : 'removeClass']('papi-hide');
    });
  }

  /**
   * Set equal height on page type boxes.
   */
  setEqualBoxHeights() {
    let $boxItems  = $('.papi-box-item');
    let thumbnails = $boxItems.find('.papi-post-type-screenshot').length > 0;
    let boxMaxHeight = 0;

    if (!thumbnails) {
      $boxItems = $('.papi-page-type-info');
    }

    $boxItems.each(function () {
      let $this  = $(this);

      if (thumbnails) {
        $this.find('.papi-post-type-info').removeAttr('style');
      } else {
        $this.removeAttr('style');
      }

      let height = $this.height();
      boxMaxHeight = height > boxMaxHeight ? height : boxMaxHeight;
    });

    $boxItems.each(function () {
      let $this  = $(this);
      let height = boxMaxHeight;

      if (thumbnails) {
        let $thumb = $this.find('.papi-post-type-screenshot');

        if ($thumb.length) {
          height = height - $thumb.height();
        } else {
          height += 5;
        }

        $this.find('.papi-post-type-info').height(height);
      } else {
        $this.height(height);
      }
    });
  }

  /**
   * Set selected menu item if it isn't selected.
   */
  setSelectedMenuItem() {
    let href = typeof window.location === 'string' ? window.location : window.location.href;
    let $adminmenu = $('#adminmenu');

    if (!$adminmenu.find('li.current > a.current').length) {
      href = href.substr(href.lastIndexOf('/') + 1);
      href = href.replace(/\%2F/g, '/');
      $('a[href="' + href + '"]', $adminmenu).addClass('current').parent().addClass('current');
    }
  }
}

export default Core;
<commit_msg>Fix equal box hight class names<commit_after>import $ from 'jquery';

class Core {

  /**
   * Initialize Papi core class.
   */
  static init() {
    const core = new Core();

    core.binds();
    core.setEqualBoxHeights();
    core.setSelectedMenuItem();
    core.addCurrentClassToMenuItem();
  }

  /**
   * Autosave fields on heartbeat.
   *
   * @param {object} e
   * @param {object} xhr
   * @param {object} options
   */
  autosave(e, xhr, options) {
    const fields = jQuery('[name*="papi_"]');
    const data = {};
    const reg = /action\=(.+?)\&/;
    const val = reg.exec(options.data);
    const id  = /post_id\=\d+/.exec(options.data);

    if (val !== null && val.length && val[1] === 'heartbeat' && id != null && id.length) {
        fields.each(function () {
          const $this = $(this);

          data[$this.attr('name')] = $this.val();
      });

      options.data += '&' + jQuery.param(data);
    }
  }

  /**
   * Bind elements with functions.
   */
  binds() {
    $('.papi-box-list > li > p').on('click', this.redirect);
    $('input[name="add-new-page-search"]').on('keyup', this.search);
    $('[data-papi-href]').on('click touchstart', this.redirect);
    $(window).resize(this.setEqualBoxHeights);

    if ('select2' in $.fn) {
      $('.inside .papi-table tr .papi-component-select2').select2();
    }

    $('.papi-meta-type-term button.handlediv').on('click', this.handlediv);

    // Autosave fields on heartbeat.
    $(document).ajaxSend(this.autosave);
  }

  /**
   * Add current class to menu item.
   */
  addCurrentClassToMenuItem() {
    let $submenu = $('.wp-has-current-submenu .wp-submenu');
    let $menuitem = $submenu.find('a[href*="papi-add-new-page"]').parent();

    if (!$menuitem.hasClass('current') && !$submenu.find('li.current').length) {
      $menuitem.addClass('current');
    }
  }

  /**
   * Handle expanded postbox div.
   *
   * @param  {object} e
   */
  handlediv(e) {
    e.preventDefault();

    const $this   = $(this);
    const $parent = $this.parent();
    const $inside = $parent.find('.inside');

    $parent.parent().toggleClass('closed');
    $inside.toggleClass('papi-hide');
    $this.attr('aria-expanded', !$inside.hasClass('papi-hide'));
  }

  /**
   * Redirect to location from `papi-href` data attribute
   * or closest tag with href attribute.
   *
   * @param {object} e
   */
  redirect(e) {
    e.preventDefault();
    let $this    = $(this);
    let papiHref = $this.data().papiHref;

    if (papiHref !== undefined) {
      window.location = papiHref;
    } else {
      window.location = $(this).closest('[href]').attr('href');
    }
  }

  /**
   * Search in page types box list.
   *
   * @param {object} e
   */
  search(e) {
    e.preventDefault();

    let $this = $(this);
    let $list = $('.papi-box-list');
    let val   = $this.val();

    $list.find('.papi-box-item').each(function() {
      let $item = $(this);
      $item[$item.text().toLowerCase().indexOf(val) === -1 ? 'addClass' : 'removeClass']('papi-hide');
    });
  }

  /**
   * Set equal height on page type boxes.
   */
  setEqualBoxHeights() {
    let $boxItems = $('.papi-box-item');
    let thumbnails = $boxItems.find('.papi-page-type-screenshot').length;
    let boxMaxHeight = 0;

    if (!thumbnails) {
      $boxItems = $('.papi-page-type-info');
    }

    $boxItems.each(function () {
      let $this  = $(this);

      if (thumbnails) {
        $this.find('.papi-post-type-info').removeAttr('style');
      } else {
        $this.removeAttr('style');
      }

      let height = $this.height();
      boxMaxHeight = height > boxMaxHeight ? height : boxMaxHeight;
    });

    $boxItems.each(function () {
      let $this  = $(this);
      let height = boxMaxHeight;

      if (thumbnails) {
        let $thumb = $this.find('.papi-page-type-screenshot');

        if ($thumb.length) {
          height = height - $thumb.height();
        } else {
          height += 5;
        }

        $this.find('.papi-page-type-info').height(height);
      } else {
        $this.height(height);
      }
    });
  }

  /**
   * Set selected menu item if it isn't selected.
   */
  setSelectedMenuItem() {
    let href = typeof window.location === 'string' ? window.location : window.location.href;
    let $adminmenu = $('#adminmenu');

    if (!$adminmenu.find('li.current > a.current').length) {
      href = href.substr(href.lastIndexOf('/') + 1);
      href = href.replace(/\%2F/g, '/');
      $('a[href="' + href + '"]', $adminmenu).addClass('current').parent().addClass('current');
    }
  }
}

export default Core;
<|endoftext|>


<commit_before>import React from 'react';
// components
import { Grid, CircularProgress } from '@material-ui/core'
// icons
import ShoppingCart from '@material-ui/icons/ShoppingCart';
import Person from '@material-ui/icons/Person';
import AccountBox from '@material-ui/icons/AccountBox';
import Web from '@material-ui/icons/Web';
import StarHalf from '@material-ui/icons/StarHalf';
import Business from '@material-ui/icons/Business';
import DoneIcon from '@material-ui/icons/Done'
import ReputationIcon from '@material-ui/icons/Star'
// custom components
import Button from 'components/MaterialDashboardPro/Button'
import RegularCard from 'components/MaterialDashboardPro/RegularCard'
import NavPills from 'components/MaterialDashboardPro/NavPills'
import InfoArea from 'components/MaterialDashboardPro/InfoArea'
// styles
import regularFormsStyle from 'styles/material-dashboard-pro-react/views/regularFormsStyle';
import { withStyles } from '@material-ui/core'
// helpers
import { isEmpty } from 'lodash'

import IndividualsCrawlerForm from './individualsCrawlerForm'
import BusinessCrawlerForm from './businessCrawlerForm'
import ProductOwnersCrawlerForm from './productOwnersCrawlerForm'

const style = {
  profileText: {
    fontWeight: '300',
    margin: '10px 0 0 0',
    textAlign: 'center'
  },
  itemGrid: {
    backgroundColor: 'rgba(200, 200, 200, .2)'
  },
  card: {
    display: 'inline-block',
    position: 'relative',
    width: '100%',
    margin: '1px 0',
    boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.14)',
    borderRadius: '6px',
    color: 'rgba(0, 0, 0, 0.87)',
    background: '#fff'
  },
  ...regularFormsStyle
};

class Step3 extends React.Component {

  render() {
    const { classes, reputation, reputationLoading: loading, push } = this.props;

    if (loading) {
      return <Grid container justify='center'>
        <Grid item xs={4}>
          <InfoArea
            icon={CircularProgress}
            iconColor='warning'
            title='Checking your Reputation'
            description='Please wait while we check your Decentralised Reputation'
          />
        </Grid>
      </Grid>
    } else if (!isEmpty(reputation)) {
      return <Grid container justify='center'>
        <Grid item xs={4}>
          <InfoArea
            icon={DoneIcon}
            iconColor='success'
            title='All done'
            description='You have imported your reputation'
          >
            <Button color='success' onClick={() => push('/reputation')}>
              <ReputationIcon/> View My Reputation
            </Button>
          </InfoArea>
        </Grid>
      </Grid>
    } else {
      return (

        <Grid container justify='center'>
          <Grid item xs={12} sm={12} md={9} className={classes.itemGrid}>

Do You Manage An Online Profile That Receives Ratings & Reviews?

          </Grid>
        </Grid>
        <Grid container justify='center'>
          <Grid item xs={12} sm={12} md={3} className={classes.itemGrid}>
            <InfoArea
              title='Select Profile Type'
              description='Select Your Profile Type That Currently Receives Reviews - Individual, Business or Specific Product'
              icon={AccountBox}
              iconColor='rose'
            />
          </Grid>
          <Grid item xs={12} sm={12} md={3} className={classes.itemGrid}>
            <InfoArea
              title='Profile Websites'
              description='Enter Your Email & Password On the Sites Where That Profile Exists'
              icon={Web}
              iconColor='primary'
            />
          </Grid>
          <Grid item xs={12} sm={12} md={3} className={classes.itemGrid}>
            <InfoArea
              title='Get Portable Reputation'
              description='We Merge, Normalize & Decentrally Store Your Ratings & Reviews So You Can Take them to Any Website'
              icon={StarHalf}
              iconColor='info'
            />
          </Grid>
        </Grid>
        <Grid container>

          <NavPills
            color='info'
            alignCenter
            tabs={[
              {
                tabButton: 'Individuals',
                tabIcon: Person,
                tabContent: (
                  <RegularCard
                    cardTitle={[

To begin, simply enter your email & password for any of the sites below on which you have an active profile.
,

We extract, merge and decentrally store your reputation in a portable format so you own and control it.

                    ]}
                    content={<IndividualsCrawlerForm />}
                  />
                )
              },
              {
                tabButton: 'Businesses',
                tabIcon: Business,
                tabContent: (
                  <RegularCard
                    cardTitle="To begin, simply enter your email & password for any of the sites below on which you have an active profile.
                    We extract, merge and decentrally store your reputation in a portable format so you own and control it."
                    content={<BusinessCrawlerForm />}
                  />
                )
              },
              {
                tabButton: 'Product Owners',
                tabIcon: ShoppingCart,
                tabContent: (
                <RegularCard
                  cardTitle="To begin, simply enter your email & password for any of the sites below on which you have an active profile.
                  We extract, merge and decentrally store your reputation in a portable format so you own and control it."
                  content={<ProductOwnersCrawlerForm />}
                />
                )
              }
            ]}
          />
        </Grid>
        <Grid container justify='flex-end'>
          <Grid item xs={12} sm={12} md={12} className={classes.infoText}>

Chlu guarantees that no information submitted from this form is ever stored on our system

By submitting this form you acknowledge you are entitled to invoke your data access rights and
             data portability rights under European GDPR legislation.

          </Grid>
        </Grid>

    );
    }

  }
}

export default withStyles(style)(Step3);
<commit_msg>wallet needs to be downloaded by the business too<commit_after>import React from 'react';
// components
import { Grid, CircularProgress } from '@material-ui/core'
// icons
import ShoppingCart from '@material-ui/icons/ShoppingCart';
import Person from '@material-ui/icons/Person';
import AccountBox from '@material-ui/icons/AccountBox';
import Web from '@material-ui/icons/Web';
import StarHalf from '@material-ui/icons/StarHalf';
import Business from '@material-ui/icons/Business';
import DoneIcon from '@material-ui/icons/Done'
import ReputationIcon from '@material-ui/icons/Star'
import DownloadIcon from '@material-ui/icons/FileDownload'
// custom components
import Button from 'components/MaterialDashboardPro/Button'
import RegularCard from 'components/MaterialDashboardPro/RegularCard'
import NavPills from 'components/MaterialDashboardPro/NavPills'
import InfoArea from 'components/MaterialDashboardPro/InfoArea'

// styles
import regularFormsStyle from 'styles/material-dashboard-pro-react/views/regularFormsStyle';
import { withStyles } from '@material-ui/core'
// helpers
import { isEmpty } from 'lodash'

import IndividualsCrawlerForm from './individualsCrawlerForm'
import BusinessCrawlerForm from './businessCrawlerForm'
import ProductOwnersCrawlerForm from './productOwnersCrawlerForm'

const style = {
  profileText: {
    fontWeight: '300',
    margin: '10px 0 0 0',
    textAlign: 'center'
  },
  itemGrid: {
    backgroundColor: 'rgba(200, 200, 200, .2)'
  },
  card: {
    display: 'inline-block',
    position: 'relative',
    width: '100%',
    margin: '1px 0',
    boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.14)',
    borderRadius: '6px',
    color: 'rgba(0, 0, 0, 0.87)',
    background: '#fff'
  },
  ...regularFormsStyle
};

class Step3 extends React.Component {

  render() {
    const { classes, reputation, downloadWallet, reputationLoading: loading, push } = this.props;

    if (loading) {
      return <Grid container justify='center'>
        <Grid item xs={4}>
          <InfoArea
            icon={CircularProgress}
            iconColor='warning'
            title='Checking your Reputation'
            description='Please wait while we check your Decentralised Reputation'
          />
        </Grid>
      </Grid>
    } else if (!isEmpty(reputation)) {
      return <Grid container justify='center'>
        <Grid item xs={4}>
          <InfoArea
            icon={DoneIcon}
            iconColor='success'
            title='All done'
            description='You have imported your reputation'
          >
            <Button color='success' onClick={() => push('/reputation')}>
              <ReputationIcon/> View My Reputation
            </Button>
          </InfoArea>
        </Grid>
      </Grid>
    } else {
      return (

        <Grid container justify='center'>
          <Grid item xs={12} sm={12} md={9} justify='center'>

Your Chlu Wallet is now created. You must download and save your public and private keys to continue.

          </Grid>
          <Grid item xs={12} sm={12} md={3} justify='center'>
            <Button color='success' onClick={downloadWallet}>
              <DownloadIcon/> Save My Keys
            </Button>
          </Grid>
          <Grid item xs={12} sm={12} md={12} justify='center'>

          </Grid>
          <Grid item xs={12} sm={12} md={9} className={classes.itemGrid}>

Do You Manage An Online Profile That Receives Ratings & Reviews?

          </Grid>
        </Grid>
        <Grid container justify='center'>
          <Grid item xs={12} sm={12} md={3} className={classes.itemGrid}>
            <InfoArea
              title='Select Profile Type'
              description='Select Your Profile Type That Currently Receives Reviews - Individual, Business or Specific Product'
              icon={AccountBox}
              iconColor='rose'
            />
          </Grid>
          <Grid item xs={12} sm={12} md={3} className={classes.itemGrid}>
            <InfoArea
              title='Profile Websites'
              description='Enter Your Email & Password On the Sites Where That Profile Exists'
              icon={Web}
              iconColor='primary'
            />
          </Grid>
          <Grid item xs={12} sm={12} md={3} className={classes.itemGrid}>
            <InfoArea
              title='Get Portable Reputation'
              description='We Merge, Normalize & Decentrally Store Your Ratings & Reviews So You Can Take them to Any Website'
              icon={StarHalf}
              iconColor='info'
            />
          </Grid>
        </Grid>
        <Grid container>

          <NavPills
            color='info'
            alignCenter
            tabs={[
              {
                tabButton: 'Individuals',
                tabIcon: Person,
                tabContent: (
                  <RegularCard
                    cardTitle={[

To begin, simply enter your email & password for any of the sites below on which you have an active profile.
,

We extract, merge and decentrally store your reputation in a portable format so you own and control it.

                    ]}
                    content={<IndividualsCrawlerForm />}
                  />
                )
              },
              {
                tabButton: 'Businesses',
                tabIcon: Business,
                tabContent: (
                  <RegularCard
                    cardTitle="To begin, simply enter your email & password for any of the sites below on which you have an active profile.
                    We extract, merge and decentrally store your reputation in a portable format so you own and control it."
                    content={<BusinessCrawlerForm />}
                  />
                )
              },
              {
                tabButton: 'Product Owners',
                tabIcon: ShoppingCart,
                tabContent: (
                <RegularCard
                  cardTitle="To begin, simply enter your email & password for any of the sites below on which you have an active profile.
                  We extract, merge and decentrally store your reputation in a portable format so you own and control it."
                  content={<ProductOwnersCrawlerForm />}
                />
                )
              }
            ]}
          />
        </Grid>
        <Grid container justify='flex-end'>
          <Grid item xs={12} sm={12} md={12} className={classes.infoText}>

Chlu guarantees that no information submitted from this form is ever stored on our system

By submitting this form you acknowledge you are entitled to invoke your data access rights and
             data portability rights under European GDPR legislation.

          </Grid>
        </Grid>

    );
    }

  }
}

export default withStyles(style)(Step3);
<|endoftext|>


<commit_before>angular.module('arachne.visualizations.directives')
    .directive('con10tD3AuthorsChord', function () { // con10t-d3
        return {
            restrict: 'E',
            scope: {
                spec: '@',
                name: '@'
            },
            link: function () {
                var names = [
                    "Braun, Emil",
                    "Brunn, Heinrich von",
                    "Bunsen, Christian Karl Josias von",
                    "Gerhard, Eduard",
                    "Henzen, Wilhelm",
                    "Jahn, Otto",
                    "Lepsius, Carl Richard",
                    "Michaelis, Adolf",
                    "Mommsen, Theodor",
                    "Unbekannt"
                ];
                var matrix = [
                    // Autoren:
                    // Braun, Brunn, Bunsen, Gerhard, Henzen, Jahn, Lepsius, Michaelis, Mommsen, Unbekannt
                    [      0,     0,      0,     538,     31,   10,      31,         0,       0,        0], // Braun
                    [      8,     0,      0,      82,    254,    5,       0,         0,       1,        1], // Brunn
                    [    100,     2,      0,      67,     10,    0,      23,         0,       0,       21], // Bunsen
                    [    461,     2,     59,       0,    578,   14,      33,         0,       0,        6], // Gerhard
                    [     30,    52,      0,     661,      2,    5,      41,         0,       0,        6], // Henzen
                    [      0,     0,      0,       0,      0,    0,       0,         0,       0,        0], // Jahn
                    [     92,     0,      2,      48,    245,    0,       0,         0,       0,        5], // Lepsius
                    [      0,     0,      0,      62,      0,  252,       0,         0,       0,        0], // Michaelis
                    [      1,     0,      0,       0,    337,    1,       0,         0,       0,       12], // Mommsen
                    [      0,     0,      0,      21,      0,   12,       0,         0,       0,        0]  // Unbekannt
                ];

                var svg = d3.select("svg"),
                    width = +svg.attr("width"),
                    height = +svg.attr("height"),
                    outerRadius = Math.min(width, height) * 0.5 - 40,
                    innerRadius = outerRadius - 30;

                var formatValue = d3.formatPrefix(",.0", 1e3);

                var chord = d3.chord()
                    .padAngle(0.05)
                    .sortSubgroups(d3.descending);

                var arc = d3.arc()
                    .innerRadius(innerRadius)
                    .outerRadius(outerRadius);

                var ribbon = d3.ribbon()
                    .radius(innerRadius);

                var color = d3.scaleOrdinal()
                    .domain(d3.range(4))
                    .range([
                        "#cd3d08", "#ec8f00", "#6dae29", "#683f92", "#b60275",
                        "#2058a5", "#00a592", "#009d3c", "#378974", "#ffca00"
                    ]);

                var g = svg.append("g")
                    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
                    .datum(chord(matrix));

                var group = g.append("g")
                    .attr("class", "groups")
                    .selectAll("g")
                    .data(function (chords) {
                        return chords.groups;
                    })
                    .enter().append("g");

                group.append("path")
                    .style("fill", function (d) {
                        return color(d.index);
                    })
                    .style("stroke", function (d) {
                        return d3.rgb(color(d.index)).darker();
                    })
                    .attr("d", arc);

                var groupTick = group.selectAll(".group-tick")
                    .data(function (d) {
                        return groupTicks(d, 1e3);
                    })
                    .enter().append("g")
                    .attr("class", "group-tick")
                    .attr("transform", function (d) {
                        return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)";
                    });

                groupTick.append("line")
                    .attr("x2", 6);

                groupTick
                    .filter(function (d) {
                        return d.value % 5e3 === 0;
                    })
                    .append("text")
                    .attr("x", 8)
                    .attr("dy", ".35em")
                    .attr("transform", function (d) {
                        return d.angle > Math.PI ? "rotate(180) translate(-16)" : null;
                    })
                    .style("text-anchor", function (d) {
                        return d.angle > Math.PI ? "end" : null;
                    })
                    .text(function (d) {
                        return formatValue(d.value);
                    });

                g.append("g")
                    .attr("class", "ribbons")
                    .selectAll("path")
                    .data(function (chords) {
                        return chords;
                    })
                    .enter().append("path")
                    .attr("d", ribbon)
                    .style("fill", function (d) {
                        return color(d.target.index);
                    })
                    .style("stroke", function (d) {
                        return d3.rgb(color(d.target.index)).darker();
                    });

                // Returns an array of tick angles and values for a given group and step.
                function groupTicks(d, step) {
                    var k = (d.endAngle - d.startAngle) / d.value;
                    return d3.range(0, d.value, step).map(function (value) {
                        return {value: value, angle: value * k + d.startAngle};
                    });
                }
            }
        }
    });
<commit_msg>#9313 - adjust d3-chord details<commit_after>angular.module('arachne.visualizations.directives')
    .directive('con10tD3AuthorsChord', function () { // con10t-d3
        return {
            restrict: 'E',
            scope: {
                spec: '@',
                name: '@'
            },
            link: function () {
                var names = [
                    "Braun, Emil",
                    "Brunn, Heinrich von",
                    "Bunsen, Christian Karl Josias von",
                    "Gerhard, Eduard",
                    "Henzen, Wilhelm",
                    "Jahn, Otto",
                    "Lepsius, Carl Richard",
                    "Michaelis, Adolf",
                    "Mommsen, Theodor",
                    "Unbekannt"
                ];
                var matrix = [
                    // Autoren:
                    // Braun, Brunn, Bunsen, Gerhard, Henzen,  Jahn, Lepsius, Michaelis, Mommsen, Unbekannt
                    // (692),  (56),   (61),  (1479), (1457), (299),   (128),       (0),     (1),      (51)
                    //                                                                                Rezipienten:
                    [      0,     0,      0,     538,     31,    10,      31,         0,       0,        0], // Braun (610)
                    [      8,     0,      0,      82,    254,     5,       0,         0,       1,        1], // Brunn (351)
                    [    100,     2,      0,      67,     10,     0,      23,         0,       0,       21], // Bunsen (223)
                    [    461,     2,     59,       0,    578,    14,      33,         0,       0,        6], // Gerhard (1153)
                    [     30,    52,      0,     661,      2,     5,      41,         0,       0,        6], // Henzen (797)
                    [      0,     0,      0,       0,      0,     0,       0,         0,       0,        0], // Jahn (0)
                    [     92,     0,      2,      48,    245,     0,       0,         0,       0,        5], // Lepsius (392)
                    [      0,     0,      0,      62,      0,   252,       0,         0,       0,        0], // Michaelis (314)
                    [      1,     0,      0,       0,    337,     1,       0,         0,       0,       12], // Mommsen (351)
                    [      0,     0,      0,      21,      0,    12,       0,         0,       0,        0]  // Unbekannt (33)
                ];

                var div = document.querySelector('#svg-container');

                var svg = d3.select("svg"),
                    width = div.scrollWidth,
                    height = div.scrollHeight,
                    outerRadius = Math.min(width, height) * 0.5 - 120,
                    innerRadius = outerRadius - 30;

                var formatValue = d3.formatPrefix(",.0", 1e2);

                var chord = d3.chord()
                    .padAngle(0.05)
                    .sortSubgroups(d3.descending);

                var arc = d3.arc()
                    .innerRadius(innerRadius)
                    .outerRadius(outerRadius);

                var ribbon = d3.ribbon()
                    .radius(innerRadius);

                var color = d3.scaleOrdinal()
                    .domain(d3.range(4))
                    .range([
                        "#cd3d08", "#ec8f00", "#6dae29", "#683f92", "#b60275",
                        "#2058a5", "#00a592", "#009d3c", "#378974", "#ffca00"
                    ]);

                var g = svg.append("g")
                    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
                    .datum(chord(matrix));

                var group = g.append("g")
                    .attr("class", "groups")
                    .selectAll("g")
                    .data(function (chords) {
                        return chords.groups;
                    })
                    .enter().append("g");

                group.append("path")
                    .style("fill", function (d) {
                        return color(d.index);
                    })
                    .style("stroke", function (d) {
                        return d3.rgb(color(d.index)).darker();
                    })
                    .attr("d", arc);

                group.append("text")
                    .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
                    .attr("dy", ".35em")
                    .attr("transform", function(d) {
                        return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                            + "translate(" + (innerRadius + 64) + ")" + (d.angle > Math.PI ? "rotate(180)" : "");
                    })
                    .style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
                    .text(function(d) { return names[d.index] })
                    .style("cursor", "pointer");

                var groupTick = group.selectAll(".group-tick")
                    .data(function (d) {
                        return groupTicks(d, 1e2);
                    })
                    .enter().append("g")
                    .attr("class", "group-tick")
                    .attr("transform", function (d) {
                        return "rotate(" + (d.angle * 180 / Math.PI - 90) + ") translate(" + outerRadius + ",0)";
                    });

                groupTick.append("line")
                    .attr("x2", 6);

                groupTick
                    .filter(function (d) {
                        return d.value % 1e2 === 0;
                    })
                    .append("text")
                    .attr("x", 8)
                    .attr("dy", ".35em")
                    .attr("transform", function (d) {
                        return d.angle > Math.PI ? "rotate(180) translate(-16)" : null;
                    })
                    .style("text-anchor", function (d) {
                        return d.angle > Math.PI ? "end" : null;
                    })
                    .text(function (d) {
                        return formatValue(d.value);
                    });

                g.append("g")
                    .attr("class", "ribbons")
                    .selectAll("path")
                    .data(function (chords) {
                        return chords;
                    })
                    .enter().append("path")
                    .attr("d", ribbon)
                    .style("fill", function (d) {
                        return color(d.target.index);
                    })
                    .style("stroke", function (d) {
                        return d3.rgb(color(d.target.index)).darker();
                    });

                // Returns an array of tick angles and values for a given group and step.
                function groupTicks(d, step) {
                    var k = (d.endAngle - d.startAngle) / d.value;
                    return d3.range(0, d.value, step).map(function (value) {
                        return {value: value, angle: value * k + d.startAngle};
                    });
                }
            }
        }
    });
<|endoftext|>


<commit_before>var symphony = symphony || {};
symphony.interop = {createChannel: function(){
    var mb = meow.messagebroker();
    var sysstatus = "Ready";

    mb.subscribe("com.symphony.api", function (envelope) {
        console.log("com.symphony.api/incoming envelope: ", envelope);

        window.postMessage(envelope.message, "*");

    });

    mb.subscribe("com.symphony.system.interop", function (envelope) {
        console.log("system.symphony.com/message: ", envelope);
        console.log("system.symphony.com/envelope.replyAddress/ ", envelope.replyAddress);
        console.log("system.symphony.com/envelope.message.action/ ", envelope.message.action);

        if (envelope.replyAddress != null) {
            switch(envelope.message.action) {
                case 'Query_System_Status':
                    mb.send(envelope.replyAddress, { type: 'System_Response', action: 'Query_System_Status', status: sysstatus });
                    break;
                case 'Bring_Symphony_To_Front':
                    paragon.app.window.getCurrent(function (win) {
                        win.restore(function () {
                            win.setAlwaysOnTop(true, function () {
                                win.setAlwaysOnTop(false);
                                mb.send(envelope.replyAddress, { type: 'System_Response', action: 'Bring_Symphony_To_Front', result: true });
                            });
                        });
                    });
                    break;
            }
        }

    });

    mb.publish("com.symphony.system.interop.events", { type: "System_Status", status: sysstatus });

    symphony.interop.outboundChannel = {
        post: function (message) {
            if (message != null && message.responseAddress != null)
            {
                mb.send(message.responseAddress, message);
            }
        }
    }
}};
<commit_msg>Updated code block symphony.interop.outboundChannel.post function<commit_after>var symphony = symphony || {};
symphony.interop = {createChannel: function(){
    var mb = meow.messagebroker();
    var sysstatus = "Ready";

    mb.subscribe("com.symphony.api", function (envelope) {
        console.log("com.symphony.api/incoming envelope: ", envelope);

        window.postMessage(envelope.message, "*");

    });

    mb.subscribe("com.symphony.system.interop", function (envelope) {
        console.log("system.symphony.com/message: ", envelope);
        console.log("system.symphony.com/envelope.replyAddress/ ", envelope.replyAddress);
        console.log("system.symphony.com/envelope.message.action/ ", envelope.message.action);

        if (envelope.replyAddress != null) {
            switch(envelope.message.action) {
                case 'Query_System_Status':
                    mb.send(envelope.replyAddress, { type: 'System_Response', action: 'Query_System_Status', status: sysstatus });
                    break;
                case 'Bring_Symphony_To_Front':
                    paragon.app.window.getCurrent(function (win) {
                        win.restore(function () {
                            win.setAlwaysOnTop(true, function () {
                                win.setAlwaysOnTop(false);
                                mb.send(envelope.replyAddress, { type: 'System_Response', action: 'Bring_Symphony_To_Front', result: true });
                            });
                        });
                    });
                    break;
            }
        }

    });

    mb.publish("com.symphony.system.interop.events", { type: "System_Status", status: sysstatus });

    symphony.interop.outboundChannel = {
        post: function (message) {
            if (message != null && message.data !=null && message.data.responseAddress != null)
            {
                mb.send(message.data.responseAddress, message);
            }
        }
    }
}};
<|endoftext|>


<commit_before>// This file is part of Fedora Community.
// Copyright (C) 2008-2009  Red Hat, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

function get_nvr(build) {
    var nvr = build.split('-');
    var version = nvr.slice(nvr.length - 2, nvr.length - 1).join('-');
    var release = nvr.slice(nvr.length - 1, nvr.length).join('-');
    var name = nvr.slice(0, nvr.length - 2).join('-');
    return {
        'name': name,
        'version': version,
        'release': release,
        'nvr': build,
        }
}

function update_id(update) {
    return update.replace(/\./g, '').replace(/,/g, '');
}

function update_action(update, action) {
    args = {}
    args['update'] = update;
    args['action'] = action;

    if (typeof(moksha_csrf_token)!='undefined' && moksha_csrf_token) {
        args['_csrf_token'] = moksha_csrf_token;
    }

    $.getJSON('/moksha_connector/bodhi/request_data/request', args, function(json) {
        json = json[1];
        $('#flash').text('');
        $('div.left-content-column')
            .prepend($('')
            .attr('id', 'flash')
            .text(json.tg_flash))
            .show('slow');

        if (typeof(json.update) != 'undefined' && json.update.request == action) {
            $('#' + update_id(update) + '_' + action).hide('slow');
        }
    });

}

function get_menu_links(nvr) {
    return $('
').append(
       $('
').append(
         $('').attr('href', 'https://admin.fedoraproject.org/updates/' + nvr.nvr).append(
           $('').attr('src', moksha.url('/images/16_bodhi.png'))
         ).append(
           $('')
             .append('Go to this ')
             .append($('').text('update'))
             .append(' in Bodhi')
         ).append(
           $('').attr('src', moksha.url('/images/16_offsite-link.png'))
         )
       )
    ).append(
       $('
').append(
         $('').attr('href', 'http://koji.fedoraproject.org/koji/search?type=build&match=glob&terms=' + nvr.nvr).append(
           $('').attr('src', moksha.url('/images/16_koji.png'))
         ).append(
           $('')
             .append('Go to the ')
             .append($('').text('build'))
             .append(' in Koji')
         ).append(
           $('').attr('src', moksha.url('/images/16_offsite-link.png'))
         )
       )
    ).append(
       $('
').append(
         $('').attr('href', 'https://admin.fedoraproject.org/pkgdb/packages/name/' + nvr.name).append(
           $('').attr('src', moksha.url('/images/16_pkgdb.png'))
         ).append(
           $('')
             .append('Go to ' + nvr.name + ' ')
             .append($('').text('package info'))
             .append(' in the PackageDB')
         ).append(
           $('').attr('src', moksha.url('/images/16_offsite-link.png'))
         )
       )
    ).append(
       $('
').append(
         $('').attr('href', 'https://bugzilla.redhat.com/buglist.cgi?query_format=advanced&classification=Fedora&product=Fedora&bug_status=NEW&bug_status=ASSIGNED&bug_status=MODIFIED&component=' + nvr.name).append(
           $('').attr('src', moksha.url('/images/16_bugzilla.png'))
         ).append(
           $('')
             .append('Go to ' + nvr.name + ' ')
             .append($('').text('bugs'))
             .append(' in Bugzilla')
         ).append(
           $('').attr('src', moksha.url('/images/16_offsite-link.png'))
         )
       )
    );
}

function generate_package_column(build) {
    var nvr = get_nvr(build);
    var build_id = update_id(build);
    var entry = $('
').addClass('menu')
                  .attr('id', 'menu_' + build_id)
                  .attr('panel', 'menu_panel_' + build_id);

    $('').addClass('package-name')
      .append(
        $('')
            .attr('href', '/package_maintenance/tools/updates?package=' + nvr.name)
            .attr('moksha_url', 'dynamic')
            .text(nvr.name))
      .append('
')
      .appendTo(entry);

    $('').addClass('version')
      .append(nvr.version + '-' + nvr.release)
      .append('

')
      .appendTo(entry);

    $('').addClass('menu_panel')
      .attr('id', 'menu_panel_' + build_id)
      .append(
        $('
')
          .append('Quick links for ')
          .append($('').text(nvr.name)))
      .append(get_menu_links(nvr))
      .appendTo(entry);

    $("<moksha_extpoint/>")
      .append("{'type': 'make_menu', 'placeholder_id': 'menu_" + build_id + "', 'id': '" + build_id + "_id'}")
      .appendTo(entry);

    return entry;
}

function render_update_builds(update) {
    var updateid = update_id(update) + '_packages';
    var result = $('
').attr('id', updateid);
    var builds = update.split(',');
    var partial = false;
    var orig_result = null;

    if (builds.length > 2) {
        partial = true;
        result.append(
            $('')
              .append($('').text('Multiple-Build Update '))
              .append('(' + builds.length + ' packages)')
        );
        result.append('

');
        result.append(generate_package_column(builds[0]));
        orig_result = result;
        result = $('
').attr('id', updateid + '_hidden');
        builds = builds.slice(1, builds.length);
    }

    $.each(builds, function(i, build) {
        result.append(generate_package_column(build));
    });

    $('span.version br:last').remove();

    if (partial) {
        orig_result.append(result);
        result = orig_result;
    }

    result = $('
').append(result);
    html = result.html();

    if (partial) {
        html += '';
    }

    return html;
}
<commit_msg>Wrap our update action commands around moksha.url()<commit_after>// This file is part of Fedora Community.
// Copyright (C) 2008-2009  Red Hat, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

function get_nvr(build) {
    var nvr = build.split('-');
    var version = nvr.slice(nvr.length - 2, nvr.length - 1).join('-');
    var release = nvr.slice(nvr.length - 1, nvr.length).join('-');
    var name = nvr.slice(0, nvr.length - 2).join('-');
    return {
        'name': name,
        'version': version,
        'release': release,
        'nvr': build,
        }
}

function update_id(update) {
    return update.replace(/\./g, '').replace(/,/g, '');
}

function update_action(update, action) {
    args = {}
    args['update'] = update;
    args['action'] = action;

    if (typeof(moksha_csrf_token)!='undefined' && moksha_csrf_token) {
        args['_csrf_token'] = moksha_csrf_token;
    }

    $.getJSON(moksha.url('/moksha_connector/bodhi/request_data/request'), args, function(json) {
        json = json[1];
        $('#flash').text('');
        $('div.left-content-column')
            .prepend($('')
            .attr('id', 'flash')
            .text(json.tg_flash))
            .show('slow');

        if (typeof(json.update) != 'undefined' && json.update.request == action) {
            $('#' + update_id(update) + '_' + action).hide('slow');
        }
    });

}

function get_menu_links(nvr) {
    return $('
').append(
       $('
').append(
         $('').attr('href', 'https://admin.fedoraproject.org/updates/' + nvr.nvr).append(
           $('').attr('src', moksha.url('/images/16_bodhi.png'))
         ).append(
           $('')
             .append('Go to this ')
             .append($('').text('update'))
             .append(' in Bodhi')
         ).append(
           $('').attr('src', moksha.url('/images/16_offsite-link.png'))
         )
       )
    ).append(
       $('
').append(
         $('').attr('href', 'http://koji.fedoraproject.org/koji/search?type=build&match=glob&terms=' + nvr.nvr).append(
           $('').attr('src', moksha.url('/images/16_koji.png'))
         ).append(
           $('')
             .append('Go to the ')
             .append($('').text('build'))
             .append(' in Koji')
         ).append(
           $('').attr('src', moksha.url('/images/16_offsite-link.png'))
         )
       )
    ).append(
       $('
').append(
         $('').attr('href', 'https://admin.fedoraproject.org/pkgdb/packages/name/' + nvr.name).append(
           $('').attr('src', moksha.url('/images/16_pkgdb.png'))
         ).append(
           $('')
             .append('Go to ' + nvr.name + ' ')
             .append($('').text('package info'))
             .append(' in the PackageDB')
         ).append(
           $('').attr('src', moksha.url('/images/16_offsite-link.png'))
         )
       )
    ).append(
       $('
').append(
         $('').attr('href', 'https://bugzilla.redhat.com/buglist.cgi?query_format=advanced&classification=Fedora&product=Fedora&bug_status=NEW&bug_status=ASSIGNED&bug_status=MODIFIED&component=' + nvr.name).append(
           $('').attr('src', moksha.url('/images/16_bugzilla.png'))
         ).append(
           $('')
             .append('Go to ' + nvr.name + ' ')
             .append($('').text('bugs'))
             .append(' in Bugzilla')
         ).append(
           $('').attr('src', moksha.url('/images/16_offsite-link.png'))
         )
       )
    );
}

function generate_package_column(build) {
    var nvr = get_nvr(build);
    var build_id = update_id(build);
    var entry = $('
').addClass('menu')
                  .attr('id', 'menu_' + build_id)
                  .attr('panel', 'menu_panel_' + build_id);

    $('').addClass('package-name')
      .append(
        $('')
            .attr('href', '/package_maintenance/tools/updates?package=' + nvr.name)
            .attr('moksha_url', 'dynamic')
            .text(nvr.name))
      .append('
')
      .appendTo(entry);

    $('').addClass('version')
      .append(nvr.version + '-' + nvr.release)
      .append('

')
      .appendTo(entry);

    $('').addClass('menu_panel')
      .attr('id', 'menu_panel_' + build_id)
      .append(
        $('
')
          .append('Quick links for ')
          .append($('').text(nvr.name)))
      .append(get_menu_links(nvr))
      .appendTo(entry);

    $("<moksha_extpoint/>")
      .append("{'type': 'make_menu', 'placeholder_id': 'menu_" + build_id + "', 'id': '" + build_id + "_id'}")
      .appendTo(entry);

    return entry;
}

function render_update_builds(update) {
    var updateid = update_id(update) + '_packages';
    var result = $('
').attr('id', updateid);
    var builds = update.split(',');
    var partial = false;
    var orig_result = null;

    if (builds.length > 2) {
        partial = true;
        result.append(
            $('')
              .append($('').text('Multiple-Build Update '))
              .append('(' + builds.length + ' packages)')
        );
        result.append('

');
        result.append(generate_package_column(builds[0]));
        orig_result = result;
        result = $('
').attr('id', updateid + '_hidden');
        builds = builds.slice(1, builds.length);
    }

    $.each(builds, function(i, build) {
        result.append(generate_package_column(build));
    });

    $('span.version br:last').remove();

    if (partial) {
        orig_result.append(result);
        result = orig_result;
    }

    result = $('
').append(result);
    html = result.html();

    if (partial) {
        html += '';
    }

    return html;
}
<|endoftext|>


<commit_before>
Ember.Handlebars.registerBoundHelper('markdown', function(input){
    return new Ember.Handlebars.SafeString(showdown.makeHtml(input));
});
<commit_msg>Small fix for showdown.<commit_after>var showdown = new Showdown.converter();

Ember.Handlebars.registerBoundHelper('markdown', function(input){
    return new Ember.Handlebars.SafeString(showdown.makeHtml(input));
});
<|endoftext|>


<commit_before>const https = require('https')
const parseString = require('xml2js').parseString
const mongoose = require('mongoose')
const Publication = require('./server/models/publication')
const config = require('config')
const hash = require('object-hash')
const cron = require('node-cron')

mongoose.Promise = global.Promise
let lastCheck // holds previous RSS feed, used to only run if different

// removes html tags from text
const cleanUpContent = text => {
  let plaintext = text.replace(/<\/?[^>]+(>|$)/g, " ")
  plaintext = plaintext.replace(/\s\s+/g, ' ')
  return plaintext
}

// pulls the unique ID for the publication from the medium url
const getUrlId = item => {
  let id = item.guid[0]["_"]
  id = id.substring(id.lastIndexOf('/') + 1)
  return id
}

// handles all the data setting of the publication
const createUpdatePublication = (publication, rssItem) => {
  return new Promise((resolve, reject) => {
    publication.title = rssItem.title[0]
    publication.author = rssItem["dc:creator"][0]
    publication.link = rssItem["link"][0]
    publication.tags = rssItem.category.join(', ')
    publication.checksum = hash(rssItem)
    publication.published = new Date(rssItem.pubDate[0])
    publication.published_num = publication.published.getTime()
    publication.content = rssItem["content:encoded"][0]
    publication.mediumId = getUrlId(rssItem)
    publication.plaintext = cleanUpContent(publication.content)
    publication.short_description = publication.plaintext.length > 200 ? `${publication.plaintext.substring(0, 197)}...` : publication.plaintext
    publication.image = getFirstImage(publication.content) || "/attachments/default/publication.png"
    publication.approved = true
    publication.save()
      .then(result => {
        resolve()
      })
  })
}

// retrieves the first image in the content, or null if none exists
const getFirstImage = content => {
  let index = content.indexOf("<img")
  if (index >= 0) {
    index = content.indexOf('src="', index)
    if (index >= 0) {
      index += 5
      let lastIndex = content.indexOf('"', index)
      let image = content.substring(index, lastIndex)
      if (image.indexOf("stat?event=post.clientViewed") >= 0) {
        // this is an image medium puts into rss feeds, don't want to use this
        return null
      }
      return image
    } else {
      return null
    }
  } else {
    return null
  }
}

cron.schedule('*/3 * * * *', () => {
  console.log('Checking for new publications')

  https.get(`https://medium.com/feed/${config.mediumId}`, res => {
    const statusCode = res.statusCode
    const contentType = res.headers['content-type']

    let error
    if (statusCode !== 200) {
      error = new Error(`Request Failed.\n` +
        `Status Code: ${statusCode}`)
    } else if (!/^text\/xml/.test(contentType)) {
      error = new Error(`Invalid content-type.\n` +
        `Expected application/json but received ${contentType}`)
    }
    if (error) {
      console.error(error.message)
      // consume response data to free up memory
      res.resume()
      return
    }

    res.setEncoding('utf8')
    let rawData = ''
    res.on('data', chunk => rawData += chunk)
    res.on('end', () => {
      try {
        let rdHash = hash(rawData)
        if (lastCheck === rdHash) {
          console.log("No change, No worries")
        } else {
          console.log("There's a change")
          lastCheck = rdHash
          parseString(rawData, (err, result) => {
            if (err) {
              console.error("Issue parsing xml", err)
            } else {
              mongoose.connect(config.mongoconnectionstring)
                .then(connection => {
                  console.log(`${result.rss.channel[0].item.length} publications`)
                  Promise.all(result.rss.channel[0].item.map(item => {
                    return new Promise((resolve, reject) => {
                      Publication.findOne({ mediumId: getUrlId(item) })
                        .then(publication => {
                          if (publication) {
                            const itemHash = hash(item)
                            if (itemHash !== publication.checksum) {
                              console.log(`Publication Updated: ${item.title[0]}`)
                              // item has changed
                              return createUpdatePublication(publication, item)
                            } else {
                              resolve()
                            }
                          } else {
                            console.log(`New Publication: ${item.title[0]}`)
                            // item doesn't exist yet
                            const newPublication = new Publication({ mediumId: getUrlId(item) })
                            return createUpdatePublication(newPublication, item)
                          }
                        })
                        .then(created => resolve())
                    })
                  }))
                    .then(result => {
                      console.log('done')
                      mongoose.connection.close()
                    }) // Promise.all.then
                }) // mongoose.connect
            } // if (err)
          }) // parseString
        } // if (lastCheck === rawData)
      } catch (e) {
        console.error(`Generic Catch: ${e.message}`)
      }
    })
  }).on('error', e => {
    console.error(`Got error: ${e.message}`)
  })

})

console.log("cron scheduled")
<commit_msg>making feed pull single run so we can just use scheduler<commit_after>const https = require("https")
const parseString = require("xml2js").parseString
const mongoose = require("mongoose")
const Publication = require("./server/models/publication")
const config = require("config")
const hash = require("object-hash")
const cron = require("node-cron")

mongoose.Promise = global.Promise
let lastCheck // holds previous RSS feed, used to only run if different

// removes html tags from text
const cleanUpContent = text => {
  let plaintext = text.replace(/<\/?[^>]+(>|$)/g, " ")
  plaintext = plaintext.replace(/\s\s+/g, " ")
  return plaintext
}

// pulls the unique ID for the publication from the medium url
const getUrlId = item => {
  let id = item.guid[0]["_"]
  id = id.substring(id.lastIndexOf("/") + 1)
  return id
}

// handles all the data setting of the publication
const createUpdatePublication = (publication, rssItem) => {
  return new Promise((resolve, reject) => {
    publication.title = rssItem.title[0]
    publication.author = rssItem["dc:creator"][0]
    publication.link = rssItem["link"][0]
    publication.tags = rssItem.category.join(", ")
    publication.checksum = hash(rssItem)
    publication.published = new Date(rssItem.pubDate[0])
    publication.published_num = publication.published.getTime()
    publication.content = rssItem["content:encoded"][0]
    publication.mediumId = getUrlId(rssItem)
    publication.plaintext = cleanUpContent(publication.content)
    publication.short_description =
      publication.plaintext.length > 200
        ? `${publication.plaintext.substring(0, 197)}...`
        : publication.plaintext
    publication.image =
      getFirstImage(publication.content) ||
      "/attachments/default/publication.png"
    publication.approved = true
    publication.save().then(result => {
      resolve()
    })
  })
}

// retrieves the first image in the content, or null if none exists
const getFirstImage = content => {
  let index = content.indexOf("<img")
  if (index >= 0) {
    index = content.indexOf('src="', index)
    if (index >= 0) {
      index += 5
      let lastIndex = content.indexOf('"', index)
      let image = content.substring(index, lastIndex)
      if (image.indexOf("stat?event=post.clientViewed") >= 0) {
        // this is an image medium puts into rss feeds, don't want to use this
        return null
      }
      return image
    } else {
      return null
    }
  } else {
    return null
  }
}

console.log("Checking for new publications")

https
  .get(`https://medium.com/feed/${config.mediumId}`, res => {
    const statusCode = res.statusCode
    const contentType = res.headers["content-type"]

    let error
    if (statusCode !== 200) {
      error = new Error(`Request Failed.\n` + `Status Code: ${statusCode}`)
    } else if (!/^text\/xml/.test(contentType)) {
      error = new Error(
        `Invalid content-type.\n` +
          `Expected application/json but received ${contentType}`
      )
    }
    if (error) {
      console.error(error.message)
      // consume response data to free up memory
      res.resume()
      return
    }

    res.setEncoding("utf8")
    let rawData = ""
    res.on("data", chunk => (rawData += chunk))
    res.on("end", () => {
      try {
        let rdHash = hash(rawData)
        if (lastCheck === rdHash) {
          console.log("No change, No worries")
        } else {
          console.log("There's a change")
          lastCheck = rdHash
          parseString(rawData, (err, result) => {
            if (err) {
              console.error("Issue parsing xml", err)
            } else {
              mongoose
                .connect(config.mongoconnectionstring)
                .then(connection => {
                  console.log(
                    `${result.rss.channel[0].item.length} publications`
                  )
                  Promise.all(
                    result.rss.channel[0].item.map(item => {
                      return new Promise((resolve, reject) => {
                        Publication.findOne({ mediumId: getUrlId(item) })
                          .then(publication => {
                            if (publication) {
                              const itemHash = hash(item)
                              if (itemHash !== publication.checksum) {
                                console.log(
                                  `Publication Updated: ${item.title[0]}`
                                )
                                // item has changed
                                return createUpdatePublication(
                                  publication,
                                  item
                                )
                              } else {
                                resolve()
                              }
                            } else {
                              console.log(`New Publication: ${item.title[0]}`)
                              // item doesn't exist yet
                              const newPublication = new Publication({
                                mediumId: getUrlId(item)
                              })
                              return createUpdatePublication(
                                newPublication,
                                item
                              )
                            }
                          })
                          .then(created => resolve())
                      })
                    })
                  ).then(result => {
                    console.log("done")
                    mongoose.connection.close()
                  }) // Promise.all.then
                }) // mongoose.connect
            } // if (err)
          }) // parseString
        } // if (lastCheck === rawData)
      } catch (e) {
        console.error(`Generic Catch: ${e.message}`)
      }
    })
  })
  .on("error", e => {
    console.error(`Got error: ${e.message}`)
  })
<|endoftext|>