<commit_before>/* See license.txt for terms of usage */

"use strict";

var self = require("sdk/self");

const { Cu, Ci } = require("chrome");
const { Trace, TraceError } = require("./core/trace.js").get(module.id);
const { extend } = require("sdk/core/heritage");
const { EventTarget } = require("sdk/event/target");
const { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
const { emit } = require("sdk/event/core");
const { defer } = require("sdk/core/promise");
const { Locale } = require("./core/locale.js");
const { Theme } = require("./chrome/theme.js");
const { getMostRecentBrowserWindow } = require("sdk/window/utils");
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});

// Register string bundles before any rep or chrome modules are loaded.
Locale.registerStringBundle("chrome://firebug/locale/firebug.properties");
Locale.registerStringBundle("chrome://firebug/locale/style-editor.properties");

const { Chrome } = require("./chrome/chrome.js");
const { WindowWatcher } = require("./chrome/windowWatcher.js");
const { StartButton } = require("./chrome/startButton.js");

// All top level modules should be required here.
require("./chrome/pageContextMenu.js");
require("./dom/domPanel.js");

// Reps
require("./reps/common.js");
require("./reps/storage.js"); // xxxHonza: needs to be here FIX ME
require("./reps/xpathresult.js"); // xxxHonza: needs to be here FIX ME
require("./reps/grip.js");
require("./reps/document.js");
require("./reps/array.js");
require("./reps/element.js");
require("./reps/object-with-url.js");
require("./reps/function.js");
require("./reps/text-node.js");
require("./reps/css-rule.js");
require("./reps/attribute.js");
require("./reps/named-node-map.js");
require("./reps/date-time.js");
require("./reps/window.js");
require("./reps/regexp.js");
require("./reps/stylesheet.js");
require("./reps/event.js");

// Hello World (will be removed at some point)
require("./helloWorld/helloWorldPanel.js");

// SDK changes (should be removed as soon as the API are built-in).
const ToolboxPatch = require("./sdk/toolboxPatch.js");
const MarkupViewPatch = require("./sdk/markup-view-patch.js");

// Overlays for built-in Toolbox panels.
const { InspectorOverlay } = require("./inspector/inspectorOverlay.js");
const { ConsoleOverlay } = require("./console/consoleOverlay.js");
const { DebuggerOverlay } = require("./debugger/debuggerOverlay.js");
const { StyleEditorOverlay } = require("./style/style-editor-overlay.js");
const { ProfilerOverlay } = require("./profiler/profiler-overlay.js");
const { NetworkOverlay } = require("./net/network-overlay.js");
const { OptionsOverlay } = require("./options/optionsOverlay.js");

// Map of all existing {@Chrome} instances. Every instance corresponds
// to one toolbox.
var chromes = new Map();

/**
 * This object represents the main Firebug application object.
 * It's singleton and there is one instance shared across all
 * browser windows.
 */
var Firebug = extend(EventTarget.prototype,
/** @lends Firebug */
{
  /**
   * The initialization happens when Firefox starts and Firebug extension
   * is loaded (see also the main.js module).
   * The logic here should be as simple as possible to make sure it
   * doesn't slow down the start up time. Typical action that can
   * be done here is global browser modifications like e.g.
   * inserting new menu items in Firefox (context) menus.
   * Actions related to Firebug UI should be done within
   * {@Chrome.inintialization} method.
   */
  initialize: function(options) {
    EventTarget.prototype.initialize.call(this);

    Trace.sysout("firebug.initialize; options: ", options);

    this.onToolboxReady = this.onToolboxReady.bind(this);
    this.onToolboxDestroyed = this.onToolboxDestroyed.bind(this);
    this.updateOption = this.updateOption.bind(this);

    // Hook developer tools events.
    gDevTools.on("toolbox-ready", this.onToolboxReady);
    gDevTools.on("toolbox-destroyed", this.onToolboxDestroyed);
    gDevTools.on("pref-changed", this.updateOption);

    // Iterate list of overlay-definitions and register them.
    // There are other built-in panels (hidden by default) like
    // e.g. Canvas and WebAudio that would also deserve
    // an overlay with support for Firebug theme.
    this.overlays = [
      {id: "inspector", ctor: InspectorOverlay},
      {id: "webconsole", ctor: ConsoleOverlay},
      {id: "jsdebugger", ctor: DebuggerOverlay},
      {id: "styleeditor", ctor: StyleEditorOverlay},
      {id: "profiler", ctor: ProfilerOverlay},
      {id: "netmonitor", ctor: NetworkOverlay},
      {id: "options", ctor: OptionsOverlay},
    ];

    for (let overlay of this.overlays)
      this.registerOverlay(overlay);

    // Firebug introduces a new theme that is available in the Options
    // panel (together with the built-in Light and Dark themes).
    Theme.registerFirebugTheme();

    // Watch browser window create/destroy events.
    WindowWatcher.initialize();
    StartButton.initialize();
  },

  /**
   * Register an overlay. This happens when Firebug extension initializes
   * itself at the very beginning (when Firefox starts or Firebug is just
   * installed or enabled).
   *
   * The purpose of the registration is to handle '{panel.id}-init' event
   * (that is fired every time a panel is created) and create an instance
   * of the overlay. List of actual overlay-instances for particular
   * {@Toolbox} is stored in corresponding {@Chrome} object.
   *
   * Panel initialization happens every time when a toolbox is created/opened
   * and the panel selected.
   * Note that instance of the toolbox is created for every tab in every
   * browser window. It can also be closed and created again for the same tab.
   */
  registerOverlay: function(overlay) {
    Trace.sysout("firebug.registerOverlay; " + overlay.id, overlay);

    // Listen for pane initialization event.
    let onApplyOverlay = (eventId, toolbox, panelFrame) => {
      Trace.sysout("firebug.onApplyOverlay; " + eventId, panelFrame);

      // Create new {@Chrome} instance for the current toolbox if it doesn't
      // exist yet. The panel 'init' event is send before 'toolbox-ready'
      // event and we need the chrome object now (to store overlay instances
      // created below).
      let chrome = this.getChrome(toolbox);
      if (!chrome) {
        chrome = new Chrome(toolbox);
        chromes.set(toolbox.target, chrome);
      }

      try {
        // Create instance of an overlay
        let instance = new overlay.ctor({
          panelFrame: panelFrame,
          toolbox: toolbox,
        });

        // Store overlay instance in the Chrome object, so we can clean
        // it up at the end.
        chrome.overlays[overlay.id] = instance;

        // Register for 'built' event (panel instance created).
        toolbox.once(overlay.id + "-build", (eventId, panel) => {
          Trace.sysout("firebug.applyOverlay; " + eventId, panel);
          instance.onBuild({toolbox: toolbox, panel: panel});
        });

        // Register for 'ready' event (panel frame loaded).
        toolbox.once(overlay.id + "-ready", (eventId, panel) => {
          Trace.sysout("firebug.applyOverlay; " + eventId, panel);
          instance.onReady({toolbox: toolbox, panel: panel});
        });
      }
      catch (err) {
        TraceError.sysout("chrome.initialize; Overlay for: " + overlay.id +
          " EXCEPTION " + err, err);
      }
    };

    // Use 'on' (not 'once') listener since the '*-init' event is sent
    // every time the toolbox is closed and opened again. The listener
    // will be removed in destroyOverlay method when Firebug is destroyed.
    gDevTools.on(overlay.id + "-init", onApplyOverlay);

    // Remember the listener, so we can remove at shutdown.
    overlay.onApplyOverlay = onApplyOverlay;
  },

  unregisterOverlay: function(overlay) {
    Trace.sysout("firebug.unregisterOverlay; " + overlay.id, overlay);

    // Remove the init listener.
    gDevTools.off(overlay.id + "-init", overlay.onApplyOverlay);
  },

  /**
   * Executed by the framework when Firebug is destroyed.
   * I happens when the entire extension is disabled, unloaded or
   * removed.
   */
  shutdown: function(reason) {
    Trace.sysout("firebug.shutdown; " + reason);

    gDevTools.off("toolbox-ready", this.onToolboxReady);
    gDevTools.off("toolbox-destroyed", this.onToolboxDestroyed);
    gDevTools.off("pref-changed", this.updateOption);

    // Unapply temporary patches
    ToolboxPatch.shutdown();
    MarkupViewPatch.shutdown();

    Theme.unregisterFirebugTheme();

    for (let overlay of this.overlays)
      this.unregisterOverlay(overlay);

    // Firebug is destroyed, so destroy also all existing chrome objects.
    for (let chrome of chromes.values())
      chrome.destroy();

    WindowWatcher.shutdown();
    StartButton.shutdown();
  },

  // Toolbox API & Events

  /**
   * Executed by the framework when {@Toolbox} is opened and ready to use.
   * There is one instance of the {@Toolbox} per browser window.
   */
  onToolboxReady: function(event, toolbox) {
    Trace.sysout("firebug.onToolboxReady; ", toolbox);

    // Chrome instance should exist already (see the registerOverlay method).
    if (!this.getChrome(toolbox)) {
      TraceError.sysout("firebug.onToolboxReady; ERROR no Chrome " +
        "instance for the current toolbox!", toolbox);
    }

    StartButton.onToolboxReady(toolbox);
  },

  onToolboxDestroyed: function(eventId, target) {
    Trace.sysout("firebug.onToolboxDestroyed;", target);

    var chrome = chromes.get(target);
    if (!chrome)
    {
      Trace.sysout("firebug.onToolboxDestroyed; ERROR unknown target!",
        target);
      return;
    }

    StartButton.onToolboxDestroyed(target);

    // A toolbox object has been destroyed, so destroy even the corresponding
    // {@Chrome} object.
    chrome.destroy();
    chromes.delete(target);
  },

  /**
   * Returns reference to the Toolbox associated with the parent
   * browser window.
   *
   * @param {Window} win A window in the current browser. If no
   * window is specified the toolbox from the current most recent
   * browser window is used.
   */
  getToolbox: function(win) {
    let tab = getCurrentTab(win);
    let target = devtools.TargetFactory.forTab(tab);
    return gDevTools.getToolbox(target);
  },

  /**
   * Open the toolbox with default panel selected.
   *
   * @param {Window} win A window in the current browser. If no
   * window is specified the toolbox from the current most recent
   * browser window is used.
   *
   * @returns A promise that is resolved as soon as the toolbox
   * is opened and fully ready (just before "toolbox-ready" event is fired).
   */
  showToolbox: function(win) {
    let tab = getCurrentTab(win);
    let target = devtools.TargetFactory.forTab(tab);
    return gDevTools.showToolbox(target);
  },

  /**
   * Destroy toolbox for the given window.
   */
  destroyToolbox: function(win) {
    let toolbox = this.getToolbox(win);
    if (toolbox)
      return toolbox.destroy();
  },

  // Options

  updateOption: function(eventType, data) {
    Trace.sysout("firebug.updateOption; ", data);

    emit(this, "updateOption", data.pref, data.newValue, data.oldValue);
  },

  getChrome: function(toolbox) {
    return chromes.get(toolbox.target);
  },

  // Commands

  about: function() {
    AddonManager.getAddonByID(self.id, function (addon) {
      let browser = getMostRecentBrowserWindow();
      browser.openDialog("chrome://mozapps/content/extensions/about.xul", "",
        "chrome,centerscreen,modal", addon);
    });
  }
});

// Helpers

function getCurrentTab(win) {
  if (win) {
    let browserDoc = win.top.document;
    let browser = browserDoc.getElementById("content");
    return browser.selectedTab;
  }

  // xxxHonza: do we really want this fall-back?
  let browser = getMostRecentBrowserWindow();
  return browser.gBrowser.mCurrentTab;
}

// Exports from this module
exports.Firebug = Firebug;
<commit_msg>List of chrome instances is a prop of Firebug object<commit_after>/* See license.txt for terms of usage */

"use strict";

var self = require("sdk/self");

const { Cu, Ci } = require("chrome");
const { Trace, TraceError } = require("./core/trace.js").get(module.id);
const { extend } = require("sdk/core/heritage");
const { EventTarget } = require("sdk/event/target");
const { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
const { emit } = require("sdk/event/core");
const { defer } = require("sdk/core/promise");
const { Locale } = require("./core/locale.js");
const { Theme } = require("./chrome/theme.js");
const { getMostRecentBrowserWindow } = require("sdk/window/utils");
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});

// Register string bundles before any rep or chrome modules are loaded.
Locale.registerStringBundle("chrome://firebug/locale/firebug.properties");
Locale.registerStringBundle("chrome://firebug/locale/style-editor.properties");

const { Chrome } = require("./chrome/chrome.js");
const { WindowWatcher } = require("./chrome/windowWatcher.js");
const { StartButton } = require("./chrome/startButton.js");

// All top level modules should be required here.
require("./chrome/pageContextMenu.js");
require("./dom/domPanel.js");

// Reps
require("./reps/common.js");
require("./reps/storage.js"); // xxxHonza: needs to be here FIX ME
require("./reps/xpathresult.js"); // xxxHonza: needs to be here FIX ME
require("./reps/grip.js");
require("./reps/document.js");
require("./reps/array.js");
require("./reps/element.js");
require("./reps/object-with-url.js");
require("./reps/function.js");
require("./reps/text-node.js");
require("./reps/css-rule.js");
require("./reps/attribute.js");
require("./reps/named-node-map.js");
require("./reps/date-time.js");
require("./reps/window.js");
require("./reps/regexp.js");
require("./reps/stylesheet.js");
require("./reps/event.js");

// Hello World (will be removed at some point)
require("./helloWorld/helloWorldPanel.js");

// SDK changes (should be removed as soon as the API are built-in).
const ToolboxPatch = require("./sdk/toolboxPatch.js");
const MarkupViewPatch = require("./sdk/markup-view-patch.js");

// Overlays for built-in Toolbox panels.
const { InspectorOverlay } = require("./inspector/inspectorOverlay.js");
const { ConsoleOverlay } = require("./console/consoleOverlay.js");
const { DebuggerOverlay } = require("./debugger/debuggerOverlay.js");
const { StyleEditorOverlay } = require("./style/style-editor-overlay.js");
const { ProfilerOverlay } = require("./profiler/profiler-overlay.js");
const { NetworkOverlay } = require("./net/network-overlay.js");
const { OptionsOverlay } = require("./options/optionsOverlay.js");

/**
 * This object represents the main Firebug application object,
 * The Firebug you have known and loved.
 *
 * It's singleton and there is one instance shared across all
 * browser windows.
 */
var Firebug = extend(EventTarget.prototype,
/** @lends Firebug */
{
  /**
   * The initialization happens when Firefox starts and Firebug extension
   * is loaded (see also the main.js module).
   * The logic here should be as simple as possible to make sure it
   * doesn't slow down the start up time. Typical action that can
   * be done here is global browser modifications like e.g.
   * inserting new menu items in Firefox (context) menus.
   * Actions related to Firebug UI should be done within
   * {@Chrome.inintialization} method.
   */
  initialize: function(options) {
    EventTarget.prototype.initialize.call(this);

    Trace.sysout("firebug.initialize; options: ", options);

    // Map of all existing {@Chrome} instances. Every instance corresponds
    // to one native {@Toolbox} instance.
    this.chromes = new Map();

    // Bind DevTools even handlers.
    this.onToolboxReady = this.onToolboxReady.bind(this);
    this.onToolboxDestroyed = this.onToolboxDestroyed.bind(this);
    this.updateOption = this.updateOption.bind(this);

    // Hook developer tools events.
    gDevTools.on("toolbox-ready", this.onToolboxReady);
    gDevTools.on("toolbox-destroyed", this.onToolboxDestroyed);
    gDevTools.on("pref-changed", this.updateOption);

    // Iterate list of overlay-definitions and register them.
    // There are other built-in panels (hidden by default) like
    // e.g. Canvas and WebAudio that would also deserve
    // an overlay with support for Firebug theme.
    this.overlays = [
      {id: "inspector", ctor: InspectorOverlay},
      {id: "webconsole", ctor: ConsoleOverlay},
      {id: "jsdebugger", ctor: DebuggerOverlay},
      {id: "styleeditor", ctor: StyleEditorOverlay},
      {id: "profiler", ctor: ProfilerOverlay},
      {id: "netmonitor", ctor: NetworkOverlay},
      {id: "options", ctor: OptionsOverlay},
    ];

    for (let overlay of this.overlays)
      this.registerOverlay(overlay);

    // Firebug introduces a new theme that is available in the Options
    // panel (together with the built-in Light and Dark themes).
    Theme.registerFirebugTheme();

    // Watch browser window create/destroy events.
    WindowWatcher.initialize();
    StartButton.initialize();
  },

  /**
   * Register an overlay. This happens when Firebug extension initializes
   * itself at the very beginning (when Firefox starts or Firebug is just
   * installed or enabled).
   *
   * The purpose of the registration is to handle '{panel.id}-init' event
   * (that is fired every time a panel is created) and create an instance
   * of the overlay. List of actual overlay-instances for particular
   * {@Toolbox} is stored in corresponding {@Chrome} object.
   *
   * Panel initialization happens every time when a toolbox is created/opened
   * and the panel selected.
   * Note that instance of the toolbox is created for every tab in every
   * browser window. It can also be closed and created again for the same tab.
   */
  registerOverlay: function(overlay) {
    Trace.sysout("firebug.registerOverlay; " + overlay.id, overlay);

    // Listen for pane initialization event.
    let onApplyOverlay = (eventId, toolbox, panelFrame) => {
      Trace.sysout("firebug.onApplyOverlay; " + eventId, panelFrame);

      // Create new {@Chrome} instance for the current toolbox if it doesn't
      // exist yet. The panel 'init' event is send before 'toolbox-ready'
      // event and we need the chrome object now (to store overlay instances
      // created below).
      let chrome = this.getChrome(toolbox);
      if (!chrome) {
        chrome = new Chrome(toolbox);
        this.chromes.set(toolbox.target, chrome);
      }

      try {
        // Create instance of an overlay
        let instance = new overlay.ctor({
          panelFrame: panelFrame,
          toolbox: toolbox,
        });

        // Store overlay instance in the Chrome object, so we can clean
        // it up at the end.
        chrome.overlays[overlay.id] = instance;

        // Register for 'built' event (panel instance created).
        toolbox.once(overlay.id + "-build", (eventId, panel) => {
          Trace.sysout("firebug.applyOverlay; " + eventId, panel);
          instance.onBuild({toolbox: toolbox, panel: panel});
        });

        // Register for 'ready' event (panel frame loaded).
        toolbox.once(overlay.id + "-ready", (eventId, panel) => {
          Trace.sysout("firebug.applyOverlay; " + eventId, panel);
          instance.onReady({toolbox: toolbox, panel: panel});
        });
      }
      catch (err) {
        TraceError.sysout("chrome.initialize; Overlay for: " + overlay.id +
          " EXCEPTION " + err, err);
      }
    };

    // Use 'on' (not 'once') listener since the '*-init' event is sent
    // every time the toolbox is closed and opened again. The listener
    // will be removed in destroyOverlay method when Firebug is destroyed.
    gDevTools.on(overlay.id + "-init", onApplyOverlay);

    // Remember the listener, so we can remove at shutdown.
    overlay.onApplyOverlay = onApplyOverlay;
  },

  unregisterOverlay: function(overlay) {
    Trace.sysout("firebug.unregisterOverlay; " + overlay.id, overlay);

    // Remove the init listener.
    gDevTools.off(overlay.id + "-init", overlay.onApplyOverlay);
  },

  /**
   * Executed by the framework when Firebug is destroyed.
   * I happens when the entire extension is disabled, unloaded or
   * removed.
   */
  shutdown: function(reason) {
    Trace.sysout("firebug.shutdown; " + reason);

    gDevTools.off("toolbox-ready", this.onToolboxReady);
    gDevTools.off("toolbox-destroyed", this.onToolboxDestroyed);
    gDevTools.off("pref-changed", this.updateOption);

    // Unapply temporary patches
    ToolboxPatch.shutdown();
    MarkupViewPatch.shutdown();

    Theme.unregisterFirebugTheme();

    for (let overlay of this.overlays)
      this.unregisterOverlay(overlay);

    // Firebug is destroyed, so destroy also all existing chrome objects.
    for (let chrome of this.chromes.values())
      chrome.destroy();

    WindowWatcher.shutdown();
    StartButton.shutdown();
  },

  // Toolbox API & Events

  /**
   * Executed by the framework when {@Toolbox} is opened and ready to use.
   * There is one instance of the {@Toolbox} per browser window.
   */
  onToolboxReady: function(event, toolbox) {
    Trace.sysout("firebug.onToolboxReady; ", toolbox);

    // Chrome instance should exist already (see the registerOverlay method).
    if (!this.getChrome(toolbox)) {
      TraceError.sysout("firebug.onToolboxReady; ERROR no Chrome " +
        "instance for the current toolbox!", toolbox);
    }

    StartButton.onToolboxReady(toolbox);
  },

  onToolboxDestroyed: function(eventId, target) {
    Trace.sysout("firebug.onToolboxDestroyed;", target);

    var chrome = this.chromes.get(target);
    if (!chrome)
    {
      Trace.sysout("firebug.onToolboxDestroyed; ERROR unknown target!",
        target);
      return;
    }

    StartButton.onToolboxDestroyed(target);

    // A toolbox object has been destroyed, so destroy even the corresponding
    // {@Chrome} object.
    chrome.destroy();
    this.chromes.delete(target);
  },

  /**
   * Returns reference to the Toolbox associated with the parent
   * browser window.
   *
   * @param {Window} win A window in the current browser. If no
   * window is specified the toolbox from the current most recent
   * browser window is used.
   */
  getToolbox: function(win) {
    let tab = getCurrentTab(win);
    let target = devtools.TargetFactory.forTab(tab);
    return gDevTools.getToolbox(target);
  },

  /**
   * Open the toolbox with default panel selected.
   *
   * @param {Window} win A window in the current browser. If no
   * window is specified the toolbox from the current most recent
   * browser window is used.
   *
   * @returns A promise that is resolved as soon as the toolbox
   * is opened and fully ready (just before "toolbox-ready" event is fired).
   */
  showToolbox: function(win) {
    let tab = getCurrentTab(win);
    let target = devtools.TargetFactory.forTab(tab);
    return gDevTools.showToolbox(target);
  },

  /**
   * Destroy toolbox for the given window.
   */
  destroyToolbox: function(win) {
    let toolbox = this.getToolbox(win);
    if (toolbox)
      return toolbox.destroy();
  },

  // Options

  updateOption: function(eventType, data) {
    Trace.sysout("firebug.updateOption; ", data);

    emit(this, "updateOption", data.pref, data.newValue, data.oldValue);
  },

  getChrome: function(toolbox) {
    return this.chromes.get(toolbox.target);
  },

  // Commands

  about: function() {
    AddonManager.getAddonByID(self.id, function (addon) {
      let browser = getMostRecentBrowserWindow();
      browser.openDialog("chrome://mozapps/content/extensions/about.xul", "",
        "chrome,centerscreen,modal", addon);
    });
  }
});

// Helpers

function getCurrentTab(win) {
  if (win) {
    let browserDoc = win.top.document;
    let browser = browserDoc.getElementById("content");
    return browser.selectedTab;
  }

  // xxxHonza: do we really want this fall-back?
  let browser = getMostRecentBrowserWindow();
  return browser.gBrowser.mCurrentTab;
}

// Exports from this module
exports.Firebug = Firebug;
<|endoftext|>


<commit_before>/*
 * forever.js: Top level include for the forever module
 *
 * (C) 2010 and Charlie Robbins
 * MIT LICENCE
 *
 */

require.paths.unshift(__dirname);

var fs = require('fs'),
    colors = require('colors'),
    async = require('async'),
    path = require('path'),
    events = require('events'),
    exec = require('child_process').exec,
    timespan = require('timespan'),
    daemon = require('daemon');

var forever = exports, config;

//
// ### Export Components / Settings
// Export `version` and important Prototypes from `lib/forever/*`
//
forever.version     = [0, 4, 0];
forever.initialized = false;
forever.root        = path.join('/tmp', 'forever');
forever.Forever     = forever.Monitor = require('forever/monitor').Monitor;

//
// ### function load (options, [callback])
// #### @options {Object} Options to load into the forever module
// Initializes configuration for forever module
//
forever.load = function (options) {
  //
  // Setup the incoming options with default options.
  //
  options         = options || {};
  options.root    = options.root || forever.root,
  options.pidPath = options.pidPath || path.join(options.root, 'pids');

  //
  // If forever is initalized and the config directories are identical
  // simply return without creating directories
  //
  if (forever.initialized && forever.config.root === options.root &&
    forever.config.pidPath === options.pidPath) {
    return;
  }

  forever.config  = config = options;

  //
  // Syncronously create the `root` directory
  // and the `pid` directory for forever. Although there is
  // an additional overhead here of the sync action. It simplifies
  // the setup of forever dramatically.
  //
  function tryCreate (dir) {
    try { fs.mkdirSync(dir, 0755); }
    catch (ex) { }
  }

  tryCreate(config.root);
  tryCreate(config.pidPath);
  forever.initialized = true;
};

//
// Ensure forever will always be loaded the first time it is required.
//
forever.load();

//
// ### function stat (logFile, script, callback)
// #### @logFile {string} Path to the log file for this script
// #### @script {string} Path to the target script.
// #### @callback {function} Continuation to pass control back to
// Ensures that the logFile doesn't exist and that
// the target script does exist before executing callback.
//
forever.stat = function (logFile, script, callback) {
  fs.stat(logFile, function (err, stats) {
    if (!err) return callback(new Error('log file ' + logFile + ' exists.'));
    fs.stat(script, function (err, stats) {
      if (err) return callback(new Error('script ' + script + ' does not exist.'));
      callback(null);
    });
  });
};

//
// ### function start (script, options)
// #### @script {string} Location of the script to run.
// #### @options {Object} Configuration for forever instance.
// Starts a script with forever
//
forever.start = function (script, options) {
  return new forever.Monitor(script, options).start();
};

//
// ### function startDaemon (script, options)
// #### @script {string} Location of the script to run.
// #### @options {Object} Configuration for forever instance.
// Starts a script with forever as a daemon
//
forever.startDaemon = function (script, options) {
  options.logFile = forever.logFilePath(options.logFile);
  options.pidFile = forever.pidFilePath(options.pidFile);
  var runner = new forever.Monitor(script, options);

  daemon.daemonize(options.logFile, options.pidFile, function (err, pid) {
    if (err) return runner.emit('error', err);

    //
    // Remark: This should work, but the fd gets screwed up
    //         with the daemon process.
    //
    // process.on('exit', function () {
    //   fs.unlinkSync(options.pidFile);
    // });

    process.pid = pid;
    runner.start();
  });

  return runner;
};

//
// ### function stop (target, [format])
// #### @target {string} Index or script name to stop
// #### @format {boolean} Indicated if we should CLI format the returned output.
// Stops the process(es) with the specified index or script name
// in the list of all processes
//
forever.stop = function (target, format, restart) {
  var emitter = new events.EventEmitter(),
      processes = getAllProcesses(),
      results = [];

  var procs = /(\d+)/.test(target) ? forever.findByIndex(target, processes)
                                   : forever.findByScript(target, processes);

  if (procs && procs.length > 0) {
    procs.forEach(function (proc) {
      process.kill(proc.foreverPid);
      process.kill(proc.pid);
    });

    process.nextTick(function () {
      emitter.emit('stop', format ? forever.list(true, procs) : procs);
    });
  }
  else {
    process.nextTick(function () {
      emitter.emit('error', new Error('Cannot find forever process: ' + target));
    });
  }

  return emitter;
};

//
// ### function restart (target, format)
// #### @target {string} Index or script name to restart
// #### @format {boolean} Indicated if we should CLI format the returned output.
// Restarts the process(es) with the specified index or script name
// in the list of all processes
//
forever.restart = function (target, format) {
  var emitter = new events.EventEmitter(),
      runner = forever.stop(target, false);

  runner.on('stop', function (procs) {
    if (procs && procs.length > 0) {
      async.forEach(procs, function (proc, next) {
        //
        // We need to spawn a new process running the forever CLI
        // here because we want each process to daemonize separately
        // without the main process running `forever restart myscript.js`
        // daemonizing itself.
        //
        var restartCommand = [
          'forever',
          'start',
          '-d', proc.sourceDir,
          // Remark: Is using a new logfile good behavior?
          //'-l', path.basename(proc.logFile),
          proc.file,
          proc.options.join(' ')
        ].join(' ');

        exec(restartCommand, function (err, stdout, stderr) {
          next();
        });
      }, function () {
        emitter.emit('restart', format ? forever.list(true, procs) : procs);
      });
    }
    else {
      emitter.emit('error', new Error('Cannot find forever process: ' + target));
    }
  });

  // Bubble up the error to the appropriate EventEmitter instance.
  runner.on('error', function (err) {
    emitter.emit('error', err);
  });

  return emitter;
};

//
// ### function findByIndex (index, processes)
// #### @index {string} Index of the process to find.
// #### @processes {Array} Set of processes to find in.
// Finds the process with the specified index.
//
forever.findByIndex = function (index, processes) {
  return processes && [processes[parseInt(index)]];
};

//
// ### function findByScript (script, processes)
// #### @script {string} The name of the script to find.
// #### @processes {Array} Set of processes to find in.
// Finds the process with the specified script name.
//
forever.findByScript = function (script, processes) {
  return processes.filter(function (p) { return p.file === script });
};

//
// ### function stopAll (format)
// #### @format {boolean} Value indicating if we should format output
// Stops all processes managed by forever.
//
forever.stopAll = function (format) {
  var emitter = new events.EventEmitter(),
      processes = getAllProcesses() || [],
      pids = getAllPids(processes);

  if (format) {
    processes = forever.list(format, processes);
  }

  if (pids && processes) {
    var fPids = pids.map(function (pid) { return pid.foreverPid }),
        cPids = pids.map(function (pid) { return pid.pid });

    fPids.concat(cPids).forEach(function (pid) {
      process.kill(pid);
    });

    process.nextTick(function () {
      emitter.emit('stopAll', processes);
    });
  }
  else {
    process.nextTick(function () {
      emitter.emit('stopAll', null);
    });
  }

  return emitter;
};

//
// ### function list (format)
// #### @format {boolean} If set, will return a formatted string of data
// Returns the list of all process data managed by forever.
//
forever.list = function (format, procs) {
  var formatted = [], procs = procs || getAllProcesses();
  if (!procs) return null;

  if (format) {
    var index = 0, maxLen = 0;
    // Iterate over the procs to see which has the longest options string
    procs.forEach(function (proc) {
      proc.length = [proc.file].concat(proc.options).join(' ').length;
      if (proc.length > maxLen) maxLen = proc.length;
    });

    procs.forEach(function (proc) {
      // Create padding string to keep output aligned
      var padding = new Array(maxLen - proc.length + 1).join(' ');
      formatted.push(formatProcess(proc, index, padding));
      index++;
    });
  }

  return format ? formatted.join('\n') : procs;
};

//
// ### function cleanUp ()
// Utility function for removing excess pid and
// config, and log files used by forever.
//
forever.cleanUp = function (cleanLogs) {
  var emitter = new events.EventEmitter(),
      processes = getAllProcesses(true);

  if (cleanLogs) forever.cleanLogsSync(processes);

  if (processes && processes.length > 0) {
    var checked = 0;
    processes.forEach(function (proc) {
      checkProcess(proc.pid, function (child) {
        checkProcess(proc.foreverPid, function (manager) {
          if (!child && !manager || proc.dead) {
            if (proc.pidFile) {
              fs.unlink(proc.pidFile, function () {
                // Ignore errors
              });
            }

            fs.unlink(path.join(config.pidPath, proc.foreverPid + '.fvr'), function () {
              // Ignore errors
            });

            if (cleanLogs) {
              fs.unlink(proc.logFile, function () { /* Ignore Errors */ });
            }
          }

          checked++;
          if (checked === processes.length) {
            emitter.emit('cleanUp');
          }
        });
      });
    });
  }
  else {
    process.nextTick(function () {
      emitter.emit('cleanUp');
    });
  }

  return emitter;
};

//
// ### function cleanLogsSync (processes)
// #### @processes {Array} The set of all forever processes
// Removes all log files from the root forever directory
// that do not belong to current running forever processes.
//
forever.cleanLogsSync = function (processes) {
  var files = fs.readdirSync(config.root),
      runningLogs = processes && processes.map(function (p) { return p.logFile.split('/').pop() });

  files.forEach(function (file) {
    if (/\.log$/.test(file) && (!runningLogs || runningLogs.indexOf(file) === -1)) {
      fs.unlinkSync(path.join(config.root, file));
    }
  });
};

//
// ### function randomString (bits)
// #### @bits {Number} Bit-length of the base64 string to return.
// Returns a pseude-random ASCII string which contains at least
// the specified number of bits of entropy the return value is a string of
// length ⌈bits/6⌉ of characters from the base64 alphabet.
//
forever.randomString = function (bits) {
  var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+',
      rand, i, ret = '';

  //
  // in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)
  //
  while (bits > 0) {
    rand = Math.floor(Math.random()*0x100000000) // 32-bit integer
    // base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.
    for (i=26; i>0 && bits>0; i-=6, bits-=6) {
      ret+=chars[0x3F & rand >>> i];
    }
  }
  return ret;
};

//
// ### function logFilePath (logFile)
// #### @logFile {string} Log file path
// Determines the full logfile path name
//
forever.logFilePath = function(logFile) {
  if (logFile && logFile[0] === "/") {
    return logFile;
  } else {
    return path.join(forever.config.root, logFile || "forever.log");
  }
};

//
// ### function pidFilePath (pidFile)
// #### @logFile {string} Pid file path
// Determines the full pid file path name
//
forever.pidFilePath = function(pidFile) {
  if (pidFile && pidFile[0] === "/") {
    return pidFile;
  } else {
    return path.join(config.pidPath, pidFile);
  }
};

//
// ### function checkProcess (pid, callback)
// #### @pid {string} pid of the process to check
// #### @callback {function} Continuation to pass control backto.
// Utility function to check to see if a pid is running
//
function checkProcess (pid, callback) {
  exec('ps ' + pid + ' | grep -v PID', function (err, stdout, stderr) {
    if (err) return callback(false);
    callback(stdout.indexOf(pid) !== -1);
  });
};

//
// ### function formatProcess (proc index, padding)
// #### @proc {Object} Process to format
// #### @index {Number} Index of the process in the set of all processes
// #### @padding {string} Padding to add to the formatted output
// Returns a formatted string for the process @proc at
// the specified index.
//
function formatProcess (proc, index, padding) {
  // Create an array of the output we can later join
  return ['  [' + index + ']', proc.file.green]
    .concat(proc.options.map(function (opt) { return opt.green }))
    .concat([padding + '[' + proc.pid + ',', proc.foreverPid + ']'])
    .concat(proc.logFile.magenta)
    .concat(timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow)
    .join(' ');
};

//
// ### function getAllProcess ([findDead])
// #### @findDead {boolean} Optional parameter that indicates to return dead procs
// Returns all data for processes managed by forever.
//
function getAllProcesses (findDead) {
  var results = [], processes = {},
      files = fs.readdirSync(config.pidPath);

  if (files.length === 0) return null;

  files.forEach(function (file) {
    try {
      var fullPath = path.join(config.pidPath, file),
          data = fs.readFileSync(fullPath).toString();

      switch (file.match(/\.(\w{3})$/)[1]) {
        case 'pid':
          var pid = parseInt(data);
          if (!processes[pid]) processes[pid] = { foreverPid: pid };
          break;

        case 'fvr':
          var child = JSON.parse(data);
          processes[child.foreverPid] = child;
          break;
      }
    }
    catch (ex) {
      // Ignore errors
    }
  });

  Object.keys(processes).forEach(function (key) {
    if (!processes[key].pid && !findDead) return;
    else if (!processes[key].pid) processes[key].dead = true;
    results.push(processes[key]);
  });

  return results;
};

//
// ### function getAllPids ()
// Returns the set of all pids managed by forever.
// e.x. [{ pid: 12345, foreverPid: 12346 }, ...]
//
function getAllPids (processes) {
  processes = processes || getAllProcesses();
  if (processes) {
    return processes.map(function (proc) {
      return {
        pid: proc.pid,
        foreverPid: proc.foreverPid
      }
    });
  }

  return null;
};<commit_msg>forever.stat append flag<commit_after>/*
 * forever.js: Top level include for the forever module
 *
 * (C) 2010 and Charlie Robbins
 * MIT LICENCE
 *
 */

require.paths.unshift(__dirname);

var fs = require('fs'),
    colors = require('colors'),
    async = require('async'),
    path = require('path'),
    events = require('events'),
    exec = require('child_process').exec,
    timespan = require('timespan'),
    daemon = require('daemon');

var forever = exports, config;

//
// ### Export Components / Settings
// Export `version` and important Prototypes from `lib/forever/*`
//
forever.version     = [0, 4, 0];
forever.initialized = false;
forever.root        = path.join('/tmp', 'forever');
forever.Forever     = forever.Monitor = require('forever/monitor').Monitor;

//
// ### function load (options, [callback])
// #### @options {Object} Options to load into the forever module
// Initializes configuration for forever module
//
forever.load = function (options) {
  //
  // Setup the incoming options with default options.
  //
  options         = options || {};
  options.root    = options.root || forever.root,
  options.pidPath = options.pidPath || path.join(options.root, 'pids');

  //
  // If forever is initalized and the config directories are identical
  // simply return without creating directories
  //
  if (forever.initialized && forever.config.root === options.root &&
    forever.config.pidPath === options.pidPath) {
    return;
  }

  forever.config  = config = options;

  //
  // Syncronously create the `root` directory
  // and the `pid` directory for forever. Although there is
  // an additional overhead here of the sync action. It simplifies
  // the setup of forever dramatically.
  //
  function tryCreate (dir) {
    try { fs.mkdirSync(dir, 0755); }
    catch (ex) { }
  }

  tryCreate(config.root);
  tryCreate(config.pidPath);
  forever.initialized = true;
};

//
// Ensure forever will always be loaded the first time it is required.
//
forever.load();

//
// ### function stat (logFile, script, callback)
// #### @logFile {string} Path to the log file for this script
// #### @logAppend {boolean} Optional. True Prevent failure if the log file exists.
// #### @script {string} Path to the target script.
// #### @callback {function} Continuation to pass control back to
// Ensures that the logFile doesn't exist and that
// the target script does exist before executing callback.
//
forever.stat = function (logFile, script, callback) {
  var logAppend,
      realCallback = callback;
  if (arguments.length === 4) {
    logAppend = callback;
    realCallback = arguments[3];
  }

  fs.stat(script, function (err, stats) {
    if (err) return realCallback(new Error('script ' + script + ' does not exist.'));

    if (logAppend) {
      realCallback(null);
      return;
    }

    fs.stat(logFile, function (err, stats) {
      if (!err) return realCallback(new Error('log file ' + logFile + ' exists.'));
      realCallback(null);
    });
  });
};

//
// ### function start (script, options)
// #### @script {string} Location of the script to run.
// #### @options {Object} Configuration for forever instance.
// Starts a script with forever
//
forever.start = function (script, options) {
  return new forever.Monitor(script, options).start();
};

//
// ### function startDaemon (script, options)
// #### @script {string} Location of the script to run.
// #### @options {Object} Configuration for forever instance.
// Starts a script with forever as a daemon
//
forever.startDaemon = function (script, options) {
  options.logFile = forever.logFilePath(options.logFile);
  options.pidFile = forever.pidFilePath(options.pidFile);
  var runner = new forever.Monitor(script, options);

  daemon.daemonize(options.logFile, options.pidFile, function (err, pid) {
    if (err) return runner.emit('error', err);

    //
    // Remark: This should work, but the fd gets screwed up
    //         with the daemon process.
    //
    // process.on('exit', function () {
    //   fs.unlinkSync(options.pidFile);
    // });

    process.pid = pid;
    runner.start();
  });

  return runner;
};

//
// ### function stop (target, [format])
// #### @target {string} Index or script name to stop
// #### @format {boolean} Indicated if we should CLI format the returned output.
// Stops the process(es) with the specified index or script name
// in the list of all processes
//
forever.stop = function (target, format, restart) {
  var emitter = new events.EventEmitter(),
      processes = getAllProcesses(),
      results = [];

  var procs = /(\d+)/.test(target) ? forever.findByIndex(target, processes)
                                   : forever.findByScript(target, processes);

  if (procs && procs.length > 0) {
    procs.forEach(function (proc) {
      process.kill(proc.foreverPid);
      process.kill(proc.pid);
    });

    process.nextTick(function () {
      emitter.emit('stop', format ? forever.list(true, procs) : procs);
    });
  }
  else {
    process.nextTick(function () {
      emitter.emit('error', new Error('Cannot find forever process: ' + target));
    });
  }

  return emitter;
};

//
// ### function restart (target, format)
// #### @target {string} Index or script name to restart
// #### @format {boolean} Indicated if we should CLI format the returned output.
// Restarts the process(es) with the specified index or script name
// in the list of all processes
//
forever.restart = function (target, format) {
  var emitter = new events.EventEmitter(),
      runner = forever.stop(target, false);

  runner.on('stop', function (procs) {
    if (procs && procs.length > 0) {
      async.forEach(procs, function (proc, next) {
        //
        // We need to spawn a new process running the forever CLI
        // here because we want each process to daemonize separately
        // without the main process running `forever restart myscript.js`
        // daemonizing itself.
        //
        var restartCommand = [
          'forever',
          'start',
          '-d', proc.sourceDir,
          // Remark: Is using a new logfile good behavior?
          //'-l', path.basename(proc.logFile),
          proc.file,
          proc.options.join(' ')
        ].join(' ');

        exec(restartCommand, function (err, stdout, stderr) {
          next();
        });
      }, function () {
        emitter.emit('restart', format ? forever.list(true, procs) : procs);
      });
    }
    else {
      emitter.emit('error', new Error('Cannot find forever process: ' + target));
    }
  });

  // Bubble up the error to the appropriate EventEmitter instance.
  runner.on('error', function (err) {
    emitter.emit('error', err);
  });

  return emitter;
};

//
// ### function findByIndex (index, processes)
// #### @index {string} Index of the process to find.
// #### @processes {Array} Set of processes to find in.
// Finds the process with the specified index.
//
forever.findByIndex = function (index, processes) {
  return processes && [processes[parseInt(index)]];
};

//
// ### function findByScript (script, processes)
// #### @script {string} The name of the script to find.
// #### @processes {Array} Set of processes to find in.
// Finds the process with the specified script name.
//
forever.findByScript = function (script, processes) {
  return processes.filter(function (p) { return p.file === script });
};

//
// ### function stopAll (format)
// #### @format {boolean} Value indicating if we should format output
// Stops all processes managed by forever.
//
forever.stopAll = function (format) {
  var emitter = new events.EventEmitter(),
      processes = getAllProcesses() || [],
      pids = getAllPids(processes);

  if (format) {
    processes = forever.list(format, processes);
  }

  if (pids && processes) {
    var fPids = pids.map(function (pid) { return pid.foreverPid }),
        cPids = pids.map(function (pid) { return pid.pid });

    fPids.concat(cPids).forEach(function (pid) {
      process.kill(pid);
    });

    process.nextTick(function () {
      emitter.emit('stopAll', processes);
    });
  }
  else {
    process.nextTick(function () {
      emitter.emit('stopAll', null);
    });
  }

  return emitter;
};

//
// ### function list (format)
// #### @format {boolean} If set, will return a formatted string of data
// Returns the list of all process data managed by forever.
//
forever.list = function (format, procs) {
  var formatted = [], procs = procs || getAllProcesses();
  if (!procs) return null;

  if (format) {
    var index = 0, maxLen = 0;
    // Iterate over the procs to see which has the longest options string
    procs.forEach(function (proc) {
      proc.length = [proc.file].concat(proc.options).join(' ').length;
      if (proc.length > maxLen) maxLen = proc.length;
    });

    procs.forEach(function (proc) {
      // Create padding string to keep output aligned
      var padding = new Array(maxLen - proc.length + 1).join(' ');
      formatted.push(formatProcess(proc, index, padding));
      index++;
    });
  }

  return format ? formatted.join('\n') : procs;
};

//
// ### function cleanUp ()
// Utility function for removing excess pid and
// config, and log files used by forever.
//
forever.cleanUp = function (cleanLogs) {
  var emitter = new events.EventEmitter(),
      processes = getAllProcesses(true);

  if (cleanLogs) forever.cleanLogsSync(processes);

  if (processes && processes.length > 0) {
    var checked = 0;
    processes.forEach(function (proc) {
      checkProcess(proc.pid, function (child) {
        checkProcess(proc.foreverPid, function (manager) {
          if (!child && !manager || proc.dead) {
            if (proc.pidFile) {
              fs.unlink(proc.pidFile, function () {
                // Ignore errors
              });
            }

            fs.unlink(path.join(config.pidPath, proc.foreverPid + '.fvr'), function () {
              // Ignore errors
            });

            if (cleanLogs) {
              fs.unlink(proc.logFile, function () { /* Ignore Errors */ });
            }
          }

          checked++;
          if (checked === processes.length) {
            emitter.emit('cleanUp');
          }
        });
      });
    });
  }
  else {
    process.nextTick(function () {
      emitter.emit('cleanUp');
    });
  }

  return emitter;
};

//
// ### function cleanLogsSync (processes)
// #### @processes {Array} The set of all forever processes
// Removes all log files from the root forever directory
// that do not belong to current running forever processes.
//
forever.cleanLogsSync = function (processes) {
  var files = fs.readdirSync(config.root),
      runningLogs = processes && processes.map(function (p) { return p.logFile.split('/').pop() });

  files.forEach(function (file) {
    if (/\.log$/.test(file) && (!runningLogs || runningLogs.indexOf(file) === -1)) {
      fs.unlinkSync(path.join(config.root, file));
    }
  });
};

//
// ### function randomString (bits)
// #### @bits {Number} Bit-length of the base64 string to return.
// Returns a pseude-random ASCII string which contains at least
// the specified number of bits of entropy the return value is a string of
// length ⌈bits/6⌉ of characters from the base64 alphabet.
//
forever.randomString = function (bits) {
  var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+',
      rand, i, ret = '';

  //
  // in v8, Math.random() yields 32 pseudo-random bits (in spidermonkey it gives 53)
  //
  while (bits > 0) {
    rand = Math.floor(Math.random()*0x100000000) // 32-bit integer
    // base 64 means 6 bits per character, so we use the top 30 bits from rand to give 30/6=5 characters.
    for (i=26; i>0 && bits>0; i-=6, bits-=6) {
      ret+=chars[0x3F & rand >>> i];
    }
  }
  return ret;
};

//
// ### function logFilePath (logFile)
// #### @logFile {string} Log file path
// Determines the full logfile path name
//
forever.logFilePath = function(logFile) {
  if (logFile && logFile[0] === "/") {
    return logFile;
  } else {
    return path.join(forever.config.root, logFile || "forever.log");
  }
};

//
// ### function pidFilePath (pidFile)
// #### @logFile {string} Pid file path
// Determines the full pid file path name
//
forever.pidFilePath = function(pidFile) {
  if (pidFile && pidFile[0] === "/") {
    return pidFile;
  } else {
    return path.join(config.pidPath, pidFile);
  }
};

//
// ### function checkProcess (pid, callback)
// #### @pid {string} pid of the process to check
// #### @callback {function} Continuation to pass control backto.
// Utility function to check to see if a pid is running
//
function checkProcess (pid, callback) {
  exec('ps ' + pid + ' | grep -v PID', function (err, stdout, stderr) {
    if (err) return callback(false);
    callback(stdout.indexOf(pid) !== -1);
  });
};

//
// ### function formatProcess (proc index, padding)
// #### @proc {Object} Process to format
// #### @index {Number} Index of the process in the set of all processes
// #### @padding {string} Padding to add to the formatted output
// Returns a formatted string for the process @proc at
// the specified index.
//
function formatProcess (proc, index, padding) {
  // Create an array of the output we can later join
  return ['  [' + index + ']', proc.file.green]
    .concat(proc.options.map(function (opt) { return opt.green }))
    .concat([padding + '[' + proc.pid + ',', proc.foreverPid + ']'])
    .concat(proc.logFile.magenta)
    .concat(timespan.fromDates(new Date(proc.ctime), new Date()).toString().yellow)
    .join(' ');
};

//
// ### function getAllProcess ([findDead])
// #### @findDead {boolean} Optional parameter that indicates to return dead procs
// Returns all data for processes managed by forever.
//
function getAllProcesses (findDead) {
  var results = [], processes = {},
      files = fs.readdirSync(config.pidPath);

  if (files.length === 0) return null;

  files.forEach(function (file) {
    try {
      var fullPath = path.join(config.pidPath, file),
          data = fs.readFileSync(fullPath).toString();

      switch (file.match(/\.(\w{3})$/)[1]) {
        case 'pid':
          var pid = parseInt(data);
          if (!processes[pid]) processes[pid] = { foreverPid: pid };
          break;

        case 'fvr':
          var child = JSON.parse(data);
          processes[child.foreverPid] = child;
          break;
      }
    }
    catch (ex) {
      // Ignore errors
    }
  });

  Object.keys(processes).forEach(function (key) {
    if (!processes[key].pid && !findDead) return;
    else if (!processes[key].pid) processes[key].dead = true;
    results.push(processes[key]);
  });

  return results;
};

//
// ### function getAllPids ()
// Returns the set of all pids managed by forever.
// e.x. [{ pid: 12345, foreverPid: 12346 }, ...]
//
function getAllPids (processes) {
  processes = processes || getAllProcesses();
  if (processes) {
    return processes.map(function (proc) {
      return {
        pid: proc.pid,
        foreverPid: proc.foreverPid
      }
    });
  }

  return null;
};<|endoftext|>


<commit_before>// Design Notes
// ----------------------------------------------------------------------------
// TODO:
// - prepare for export
// - add directory creatin
// - add delete() function
// - add directory removal
// - add verify() function
// - add touch() function
// - finish implementing timeSync()
// - fix time comparison on isModified

// Setup
// ----------------------------------------------------------------------------
var fs = require('fs');
var path = require('path');
var jsftp = require('jsftp');
var async = require('async');
var config = require(process.cwd() + '/config.json');

var settings;
var ftp;

var update = [];
var remove = [];

// process functions
// ----------------------------------------------------------------------------
function walkLocal(dir, callback) {
  var results = [];
  // walk the directory
  fs.readdir(dir, function(err, list) {
    if (err) return callback(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      // exit if all files are processed
      if (!file) return callback(null, results);
      // skip ignore files
      if (settings.ignore.indexOf(file) != -1) {
        next();
        return;
      }
      // get file/dir name/stats
      var path = dir + '/' + file;
      fs.stat(path, function(err, stat) {
        // handle directories
        if (stat.isDirectory()) {
          walkLocal(path, function(err, res) { // recurse & shit
            results = results.concat(res);
            next();
          });
          return;
        }
        // handle files
        if (stat.isFile()) {
          results.push({
            'id':trimPathRoot(settings.local, path),
            'size':stat.size,
            'time':new Date(stat.ctime)
          });
          next();
          return;
        }
        // skip everything else
        else { next(); }
      });
    })();
  });
};

function walkRemote(dir, callback) {
  var results = [];
  // walk the directory
  ftp.ls(dir, function(err, list) {
    if (err) return callback(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      // exit if all files are processed
      if (!file) return callback(null, results);
      // skip ignore files
      if (settings.ignore.indexOf(file.name) != -1) {
        next();
        return;
      }
      // get file/dir name/stats
      var path = dir + '/' + file.name;
      // handle directories
      if (file.type == 1) {
        walkRemote(path, function(err, res) { // recurse & shit
          results = results.concat(res);
          next();
        });
        return;
      }
      // handle files
      if (file.type = 2) {
        results.push({
          'id':trimPathRoot(settings.remote, path),
          'size':+file.size,
          'time':new Date(file.time)
        });
        next();
        return;
      }
      // skip everything else (ex sumlinks)
      else { next(); }
    })();
  });
}

// upload a file to the remote server
function upload(file, callback) {
  var local = settings.local + file;
  var remote = settings.remote + file;
  fs.readFile(local, function(err, buffer) {
    if(err) {
      console.error(err);
      callback(err);
    }
    else {
      ftp.put(buffer, remote, function(err) {
        if (err) {
          console.error(err);
          callback(err);
        }
        else {
          console.log(file + " - uploaded successfuly");
          callback();
        }
      });
    }
  });
}

function remove(file, callback) {
  return;
}

// helper functions
// ----------------------------------------------------------------------------

// maps a file lookup table from an array of file objects
function lookupTable(array) {
  //if (!array) { return []; }
  var lookup = [];
  for (var i = 0, len = array.length; i < len; i++) {
    lookup[i] = array[i].id;
  }
  return lookup;
}

// prints an array in a human-readable format
function prettyPrint(results) {
  return JSON.stringify(results, null, '\t');
}

// compare local vs remote file sizes
function isDifferent(lfile, rfile) {
  return (lfile.size != rfile.size);
}

// compare a local vs remote file for modification
function isModified(lfile, rfile) {
  // round to the nearest minute
  //var minutes = 1000 * 60;
  //var hours = 1000 * 60 * 60;
  //var ltime = new Date(((Math.round(ltime / minutes) * minutes) - (ltimeOffset * hours)));
  //var rtime = new Date(((Math.round(rtime / minutes) * minutes) - (rtimeOffset * hours)));
  //var ltime = new Date(Math.round(ltime / minutes) * minutes);
  //var rtime = new Date(Math.round(rtime / minutes) * minutes);
  //console.log('Compare:');
  //console.log('lTime: ' + ltime);
  //console.log('rTime: ' + rtime);
}

// synchronizes the local and remote clocks
function syncTime(ltime, rtime) {
  // get the current date/time
  var now = new Date();
  // calculate the local time offset
  var ltimeOffset = (now.getTimezoneOffset() / 60);
  // round to the nearest hour
  var coeff = 1000 * 60 * 60;
  // greenwich mean time
  var gmtime = new Date((Math.round(ltime / coeff) - ltimeOffset) * coeff);
  // local time
  var ltime = new Date(Math.round(ltime / coeff) * coeff);
  // remote time
  var rtime = new Date(Math.round(rtime / coeff) * coeff);
  // calculate the remote time offset
  var rtimeOffset = (rtime - ltime) / coeff;
  console.log('Offsets:');
  console.log('loffset: +' + ltimeOffset);
  console.log('roffset: +' + rtimeOffset);
}

// trims the base dir of from the file path
function trimPathRoot(root, path) {
  var rdirs = root.split('/');
  var fdirs = path.split('/');
  return '/' + fdirs.splice((rdirs.length), (fdirs.length-rdirs.length)).join('/');
}

// init
// ----------------------------------------------------------------------------

var sync = exports = {
  settings: {
    'host': config.host,
    'port': config.port || 21,
    'user': config.user,
    'pass': config.pass,
    'local': config.local || process.cwd(),
    'remote': config.remote || '/',
    'ignore': config.ignore || {},
    'maxConnections': 1,
    'ltimeOffset': 0,
    'rtimeOffset': 0
  },

  local: [],
  remote: [],
  add: [],

  setup: function(callback) {
    settings = exports.settings;
    console.log('Setup...');
    ftp = new jsftp({
      host: settings.host,
      port: settings.port,
      user: settings.user,
      pass: settings.pass
    });
    callback(null, 'setup complete');
  },

  collect: function(callback) {
    console.log('Collecting');
    console.log('-------------------------------------------------------------');
    async.series([
      function(callback) {
        walkLocal(settings.local, callback);
      },
      function(callback) {
        walkRemote(settings.remote, callback);
      }
    ], function(err, results) {
      sync.local = results[0];
      sync.remote = results[1];
      console.log('Local Files:');
      console.log(prettyPrint(sync.local));
      console.log('Remote Files:');
      console.log(prettyPrint(sync.remote));
      callback(null, 'collection complete');
    });
  },

  consolidate: function(callback) {
    console.log('Consolidating');
    console.log('-------------------------------------------------------------');
    // create lookup tebles for easy comparison
    var rFiles = lookupTable(sync.remote);
    var lFiles = lookupTable(sync.local);
    // run the comparison
    rFiles.forEach(function(file) {
      var lIDX = lFiles.indexOf(file);
      if (lIDX != -1) {
        var rIDX = rFiles.indexOf(file);
        var lFile = sync.local[lIDX];
        var rFile = sync.remote[rIDX];
        if (isDifferent(sync.local[lIDX], sync.remote[rIDX])) {
          update.push(file);
        }
        // mark matches for removal
        lFiles[lIDX] = '';
        rFiles[rIDX] = '';
      }
    });
    // remove matches from local and remote tables
    rFiles.forEach(function(file) {
      if(file === '') { return; }
      remove.push(file);
    });
    lFiles.forEach(function(file) {
      if(file === '') { return; }
      sync.add.push(file);
    });

    // output the results
    console.log('Updates:');
    console.log(prettyPrint(update));
    console.log('Add:');
    console.log(prettyPrint(sync.add));
    console.log('Remove:');
    console.log(prettyPrint(remove));
    callback(null, 'consolidation complete');
  },

  commit: function(callback) {
    console.log('Committing');
    console.log('-------------------------------------------------------------');
    async.series([
      function(callback) {
        if (sync.add.length == 0) { callback(null, 'no additions'); return; }
        async.mapLimit(sync.add, settings.maxConnections, upload, function (err) {
          if (err) {
            callback(err, 'additions failed');
          }
          else {
            callback(null, 'additions complete');
          }
        });
      },
      function(callback) {
        if (update.length == 0) { callback(null, 'no updates'); return; }
        async.mapLimit(update, settings.maxConnections, upload, function (err) {
          if (err) {
            callback(err, 'updates failed');
          }
          else {
            callback(null, 'updates complete');
          }
        });
      },
      function(callback) {
        if (remove.length == 0) { callback(null, 'no removals'); return; }
        // TODO: add stuff here
        callback(null, 'removals complete');
      }
    ],
    function(err, results) {
      console.log(results);
      callback(null, 'commit complete');
    });
  },

  run: function(callback) {
    async.series([
      function(callback) {
        sync.setup(callback);
      },
      function(callback) {
        sync.collect(callback);
      },
      function(callback) {
        sync.consolidate(callback);
      },
      function(callback) {
        sync.commit(callback);
      }
    ],
    function(err, results) {
      if (err) {
        if (callback) {
          callback(err);
        }
        else {
          console.error(err);
          process.exit(code=1);
        }
      }
      else {
        if (callback) {
          callback(null);
        }
        else {
          console.log(results);
          process.exit(code=0)
        }
      }
    });
  }
};

module.exports = exports;<commit_msg>Added update[] to module.exports<commit_after>// Design Notes
// ----------------------------------------------------------------------------
// TODO:
// - prepare for export
// - add directory creatin
// - add delete() function
// - add directory removal
// - add verify() function
// - add touch() function
// - finish implementing timeSync()
// - fix time comparison on isModified

// Setup
// ----------------------------------------------------------------------------
var fs = require('fs');
var path = require('path');
var jsftp = require('jsftp');
var async = require('async');
var config = require(process.cwd() + '/config.json');

var settings;
var ftp;

var remove = [];

// process functions
// ----------------------------------------------------------------------------
function walkLocal(dir, callback) {
  var results = [];
  // walk the directory
  fs.readdir(dir, function(err, list) {
    if (err) return callback(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      // exit if all files are processed
      if (!file) return callback(null, results);
      // skip ignore files
      if (settings.ignore.indexOf(file) != -1) {
        next();
        return;
      }
      // get file/dir name/stats
      var path = dir + '/' + file;
      fs.stat(path, function(err, stat) {
        // handle directories
        if (stat.isDirectory()) {
          walkLocal(path, function(err, res) { // recurse & shit
            results = results.concat(res);
            next();
          });
          return;
        }
        // handle files
        if (stat.isFile()) {
          results.push({
            'id':trimPathRoot(settings.local, path),
            'size':stat.size,
            'time':new Date(stat.ctime)
          });
          next();
          return;
        }
        // skip everything else
        else { next(); }
      });
    })();
  });
};

function walkRemote(dir, callback) {
  var results = [];
  // walk the directory
  ftp.ls(dir, function(err, list) {
    if (err) return callback(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      // exit if all files are processed
      if (!file) return callback(null, results);
      // skip ignore files
      if (settings.ignore.indexOf(file.name) != -1) {
        next();
        return;
      }
      // get file/dir name/stats
      var path = dir + '/' + file.name;
      // handle directories
      if (file.type == 1) {
        walkRemote(path, function(err, res) { // recurse & shit
          results = results.concat(res);
          next();
        });
        return;
      }
      // handle files
      if (file.type = 2) {
        results.push({
          'id':trimPathRoot(settings.remote, path),
          'size':+file.size,
          'time':new Date(file.time)
        });
        next();
        return;
      }
      // skip everything else (ex sumlinks)
      else { next(); }
    })();
  });
}

// upload a file to the remote server
function upload(file, callback) {
  var local = settings.local + file;
  var remote = settings.remote + file;
  fs.readFile(local, function(err, buffer) {
    if(err) {
      console.error(err);
      callback(err);
    }
    else {
      ftp.put(buffer, remote, function(err) {
        if (err) {
          console.error(err);
          callback(err);
        }
        else {
          console.log(file + " - uploaded successfuly");
          callback();
        }
      });
    }
  });
}

function remove(file, callback) {
  return;
}

// helper functions
// ----------------------------------------------------------------------------

// maps a file lookup table from an array of file objects
function lookupTable(array) {
  //if (!array) { return []; }
  var lookup = [];
  for (var i = 0, len = array.length; i < len; i++) {
    lookup[i] = array[i].id;
  }
  return lookup;
}

// prints an array in a human-readable format
function prettyPrint(results) {
  return JSON.stringify(results, null, '\t');
}

// compare local vs remote file sizes
function isDifferent(lfile, rfile) {
  return (lfile.size != rfile.size);
}

// compare a local vs remote file for modification
function isModified(lfile, rfile) {
  // round to the nearest minute
  //var minutes = 1000 * 60;
  //var hours = 1000 * 60 * 60;
  //var ltime = new Date(((Math.round(ltime / minutes) * minutes) - (ltimeOffset * hours)));
  //var rtime = new Date(((Math.round(rtime / minutes) * minutes) - (rtimeOffset * hours)));
  //var ltime = new Date(Math.round(ltime / minutes) * minutes);
  //var rtime = new Date(Math.round(rtime / minutes) * minutes);
  //console.log('Compare:');
  //console.log('lTime: ' + ltime);
  //console.log('rTime: ' + rtime);
}

// synchronizes the local and remote clocks
function syncTime(ltime, rtime) {
  // get the current date/time
  var now = new Date();
  // calculate the local time offset
  var ltimeOffset = (now.getTimezoneOffset() / 60);
  // round to the nearest hour
  var coeff = 1000 * 60 * 60;
  // greenwich mean time
  var gmtime = new Date((Math.round(ltime / coeff) - ltimeOffset) * coeff);
  // local time
  var ltime = new Date(Math.round(ltime / coeff) * coeff);
  // remote time
  var rtime = new Date(Math.round(rtime / coeff) * coeff);
  // calculate the remote time offset
  var rtimeOffset = (rtime - ltime) / coeff;
  console.log('Offsets:');
  console.log('loffset: +' + ltimeOffset);
  console.log('roffset: +' + rtimeOffset);
}

// trims the base dir of from the file path
function trimPathRoot(root, path) {
  var rdirs = root.split('/');
  var fdirs = path.split('/');
  return '/' + fdirs.splice((rdirs.length), (fdirs.length-rdirs.length)).join('/');
}

// init
// ----------------------------------------------------------------------------

var sync = exports = {
  settings: {
    'host': config.host,
    'port': config.port || 21,
    'user': config.user,
    'pass': config.pass,
    'local': config.local || process.cwd(),
    'remote': config.remote || '/',
    'ignore': config.ignore || {},
    'maxConnections': 1,
    'ltimeOffset': 0,
    'rtimeOffset': 0
  },

  local: [],
  remote: [],
  add: [],
  update: [],

  setup: function(callback) {
    settings = exports.settings;
    console.log('Setup...');
    ftp = new jsftp({
      host: settings.host,
      port: settings.port,
      user: settings.user,
      pass: settings.pass
    });
    callback(null, 'setup complete');
  },

  collect: function(callback) {
    console.log('Collecting');
    console.log('-------------------------------------------------------------');
    async.series([
      function(callback) {
        walkLocal(settings.local, callback);
      },
      function(callback) {
        walkRemote(settings.remote, callback);
      }
    ], function(err, results) {
      sync.local = results[0];
      sync.remote = results[1];
      console.log('Local Files:');
      console.log(prettyPrint(sync.local));
      console.log('Remote Files:');
      console.log(prettyPrint(sync.remote));
      callback(null, 'collection complete');
    });
  },

  consolidate: function(callback) {
    console.log('Consolidating');
    console.log('-------------------------------------------------------------');
    // create lookup tebles for easy comparison
    var rFiles = lookupTable(sync.remote);
    var lFiles = lookupTable(sync.local);
    // run the comparison
    rFiles.forEach(function(file) {
      var lIDX = lFiles.indexOf(file);
      if (lIDX != -1) {
        var rIDX = rFiles.indexOf(file);
        var lFile = sync.local[lIDX];
        var rFile = sync.remote[rIDX];
        if (isDifferent(sync.local[lIDX], sync.remote[rIDX])) {
          sync.update.push(file);
        }
        // mark matches for removal
        lFiles[lIDX] = '';
        rFiles[rIDX] = '';
      }
    });
    // remove matches from local and remote tables
    rFiles.forEach(function(file) {
      if(file === '') { return; }
      remove.push(file);
    });
    lFiles.forEach(function(file) {
      if(file === '') { return; }
      sync.add.push(file);
    });

    // output the results
    console.log('Updates:');
    console.log(prettyPrint(sync.update));
    console.log('Add:');
    console.log(prettyPrint(sync.add));
    console.log('Remove:');
    console.log(prettyPrint(remove));
    callback(null, 'consolidation complete');
  },

  commit: function(callback) {
    console.log('Committing');
    console.log('-------------------------------------------------------------');
    async.series([
      function(callback) {
        if (sync.add.length == 0) { callback(null, 'no additions'); return; }
        async.mapLimit(sync.add, settings.maxConnections, upload, function (err) {
          if (err) {
            callback(err, 'additions failed');
          }
          else {
            callback(null, 'additions complete');
          }
        });
      },
      function(callback) {
        if (sync.update.length == 0) { callback(null, 'no updates'); return; }
        async.mapLimit(sync.update, settings.maxConnections, upload, function (err) {
          if (err) {
            callback(err, 'updates failed');
          }
          else {
            callback(null, 'updates complete');
          }
        });
      },
      function(callback) {
        if (remove.length == 0) { callback(null, 'no removals'); return; }
        // TODO: add stuff here
        callback(null, 'removals complete');
      }
    ],
    function(err, results) {
      console.log(results);
      callback(null, 'commit complete');
    });
  },

  run: function(callback) {
    async.series([
      function(callback) {
        sync.setup(callback);
      },
      function(callback) {
        sync.collect(callback);
      },
      function(callback) {
        sync.consolidate(callback);
      },
      function(callback) {
        sync.commit(callback);
      }
    ],
    function(err, results) {
      if (err) {
        if (callback) {
          callback(err);
        }
        else {
          console.error(err);
          process.exit(code=1);
        }
      }
      else {
        if (callback) {
          callback(null);
        }
        else {
          console.log(results);
          process.exit(code=0)
        }
      }
    });
  }
};

module.exports = exports;<|endoftext|>