<commit_before>/* @flow */

import { CompositeDisposable } from "atom";
import React from "react";
import { observer } from "mobx-react";
import { action, observable, toJS } from "mobx";
import Display, {
  DEFAULT_SCROLL_HEIGHT
} from "@nteract/display-area/lib/display";
import { transforms, displayOrder } from "./transforms";
import Status from "./status";

import type { IObservableValue } from "mobx";
import type OutputStore from "./../../store/output";
import type Kernel from "./../../kernel";

type Props = {
  store: OutputStore,
  kernel: ?Kernel,
  destroy: Function,
  showResult: boolean
};

@observer
class ResultViewComponent extends React.Component {
  props: Props;
  el: ?HTMLElement;
  containerTooltip = new CompositeDisposable();
  buttonTooltip = new CompositeDisposable();
  expanded: IObservableValue<boolean> = observable(false);

  getAllText = () => {
    if (!this.el) return "";
    return this.el.innerText ? this.el.innerText.trim() : "";
  };

  handleClick = (event: MouseEvent) => {
    if (event.ctrlKey || event.metaKey) {
      this.openInEditor();
    } else {
      this.copyToClipboard();
    }
  };

  copyToClipboard = () => {
    atom.clipboard.write(this.getAllText());
    atom.notifications.addSuccess("Copied to clipboard");
  };

  openInEditor = () => {
    atom.workspace.open().then(editor => editor.insertText(this.getAllText()));
  };

  addCopyTooltip = (element: ?HTMLElement, comp: atom$CompositeDisposable) => {
    if (!element || !comp.disposables || comp.disposables.size > 0) return;
    comp.add(
      atom.tooltips.add(element, {
        title: () => {
          const ctrlOrCmdMessage = `Click to copy,
          ${process.platform === "darwin"
            ? "Cmd"
            : "Ctrl"}+Click to open in editor`;

          return this.props.store.executionCount
            ? `${ctrlOrCmdMessage} (Out[${this.props.store.executionCount}])`
            : ctrlOrCmdMessage;
        }
      })
    );
  };

  addCopyButtonTooltip = (element: ?HTMLElement) => {
    this.addCopyTooltip(element, this.buttonTooltip);
  };

  onWheel = (element: HTMLElement) => {
    return (event: WheelEvent) => {
      const clientHeight = element.clientHeight;
      const scrollHeight = element.scrollHeight;
      const scrollTop = element.scrollTop;
      const atTop = scrollTop !== 0 && event.deltaY < 0;
      const atBottom =
        scrollTop !== scrollHeight - clientHeight && event.deltaY > 0;

      if (clientHeight < scrollHeight && (atTop || atBottom)) {
        event.stopPropagation();
      }
    };
  };

  @action
  toggleExpand = () => {
    this.expanded.set(!this.expanded.get());
  };

  render() {
    const { outputs, status, isPlain, position } = this.props.store;

    const inlineStyle = {
      marginLeft: `${position.lineLength + 1}ch`,
      marginTop: `-${position.lineHeight}px`
    };

    if (outputs.length === 0 || this.props.showResult === false) {
      const kernel = this.props.kernel;
      return (
        <Status
          status={
            kernel && kernel.executionState !== "busy" && status === "running"
              ? "error"
              : status
          }
          style={inlineStyle}
        />
      );
    }

    return (
      <div
        className={isPlain ? "inline-container" : "multiline-container"}
        onClick={isPlain ? this.handleClick : false}
        style={
          isPlain
            ? inlineStyle
            : { maxWidth: `${position.editorWidth}ch`, margin: "0px" }
        }
      >
        <Display
          ref={ref => {
            if (!ref || !ref.el) return;
            this.el = ref.el;

            isPlain
              ? this.addCopyTooltip(ref.el, this.containerTooltip)
              : this.containerTooltip.dispose();

            // React's event handler doesn't properly handle event.stopPropagation() for
            // events outside the React context. Using proxy.el saves us a extra div.
            // We only need this in the text editor, therefore we check showStatus.
            if (!this.expanded.get() && !isPlain) {
              ref.el.addEventListener("wheel", this.onWheel(ref.el), {
                passive: true
              });
            }
          }}
          outputs={toJS(outputs)}
          displayOrder={displayOrder}
          transforms={transforms}
          theme="light"
          models={{}}
          expanded={this.expanded.get()}
        />
        {isPlain
          ? null
          :

              {this.el && this.el.scrollHeight > DEFAULT_SCROLL_HEIGHT
                ?

                : null}

}

    );
  }

  scrollToBottom() {
    if (
      !this.el ||
      this.expanded === true ||
      this.props.store.isPlain === true ||
      atom.config.get(`Hydrogen.autoScroll`) === false
    )
      return;
    const scrollHeight = this.el.scrollHeight;
    const height = this.el.clientHeight;
    const maxScrollTop = scrollHeight - height;
    this.el.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
  }

  componentDidUpdate() {
    this.scrollToBottom();
  }

  componentDidMount() {
    this.scrollToBottom();
  }

  componentWillUnmount() {
    this.containerTooltip.dispose();
    this.buttonTooltip.dispose();
  }
}

export default ResultViewComponent;
<commit_msg>Only display copy button if text to copy<commit_after>/* @flow */

import { CompositeDisposable } from "atom";
import React from "react";
import { observer } from "mobx-react";
import { action, observable, toJS } from "mobx";
import Display, {
  DEFAULT_SCROLL_HEIGHT
} from "@nteract/display-area/lib/display";
import { transforms, displayOrder } from "./transforms";
import Status from "./status";

import type { IObservableValue } from "mobx";
import type OutputStore from "./../../store/output";
import type Kernel from "./../../kernel";

type Props = {
  store: OutputStore,
  kernel: ?Kernel,
  destroy: Function,
  showResult: boolean
};

@observer
class ResultViewComponent extends React.Component {
  props: Props;
  el: ?HTMLElement;
  containerTooltip = new CompositeDisposable();
  buttonTooltip = new CompositeDisposable();
  expanded: IObservableValue<boolean> = observable(false);

  getAllText = () => {
    if (!this.el) return "";
    return this.el.innerText ? this.el.innerText.trim() : "";
  };

  handleClick = (event: MouseEvent) => {
    if (event.ctrlKey || event.metaKey) {
      this.openInEditor();
    } else {
      this.copyToClipboard();
    }
  };

  copyToClipboard = () => {
    atom.clipboard.write(this.getAllText());
    atom.notifications.addSuccess("Copied to clipboard");
  };

  openInEditor = () => {
    atom.workspace.open().then(editor => editor.insertText(this.getAllText()));
  };

  addCopyTooltip = (element: ?HTMLElement, comp: atom$CompositeDisposable) => {
    if (!element || !comp.disposables || comp.disposables.size > 0) return;
    comp.add(
      atom.tooltips.add(element, {
        title: () => {
          const ctrlOrCmdMessage = `Click to copy,
          ${process.platform === "darwin"
            ? "Cmd"
            : "Ctrl"}+Click to open in editor`;

          return this.props.store.executionCount
            ? `${ctrlOrCmdMessage} (Out[${this.props.store.executionCount}])`
            : ctrlOrCmdMessage;
        }
      })
    );
  };

  addCopyButtonTooltip = (element: ?HTMLElement) => {
    this.addCopyTooltip(element, this.buttonTooltip);
  };

  onWheel = (element: HTMLElement) => {
    return (event: WheelEvent) => {
      const clientHeight = element.clientHeight;
      const scrollHeight = element.scrollHeight;
      const scrollTop = element.scrollTop;
      const atTop = scrollTop !== 0 && event.deltaY < 0;
      const atBottom =
        scrollTop !== scrollHeight - clientHeight && event.deltaY > 0;

      if (clientHeight < scrollHeight && (atTop || atBottom)) {
        event.stopPropagation();
      }
    };
  };

  @action
  toggleExpand = () => {
    this.expanded.set(!this.expanded.get());
  };

  render() {
    const { outputs, status, isPlain, position } = this.props.store;

    const inlineStyle = {
      marginLeft: `${position.lineLength + 1}ch`,
      marginTop: `-${position.lineHeight}px`
    };

    if (outputs.length === 0 || this.props.showResult === false) {
      const kernel = this.props.kernel;
      return (
        <Status
          status={
            kernel && kernel.executionState !== "busy" && status === "running"
              ? "error"
              : status
          }
          style={inlineStyle}
        />
      );
    }

    return (
      <div
        className={isPlain ? "inline-container" : "multiline-container"}
        onClick={isPlain ? this.handleClick : false}
        style={
          isPlain
            ? inlineStyle
            : { maxWidth: `${position.editorWidth}ch`, margin: "0px" }
        }
      >
        <Display
          ref={ref => {
            if (!ref || !ref.el) return;
            this.el = ref.el;

            isPlain
              ? this.addCopyTooltip(ref.el, this.containerTooltip)
              : this.containerTooltip.dispose();

            // React's event handler doesn't properly handle event.stopPropagation() for
            // events outside the React context. Using proxy.el saves us a extra div.
            // We only need this in the text editor, therefore we check showStatus.
            if (!this.expanded.get() && !isPlain) {
              ref.el.addEventListener("wheel", this.onWheel(ref.el), {
                passive: true
              });
            }
          }}
          outputs={toJS(outputs)}
          displayOrder={displayOrder}
          transforms={transforms}
          theme="light"
          models={{}}
          expanded={this.expanded.get()}
        />
        {isPlain
          ? null
          :

              {this.getAllText().length > 0
                ?

                : null}

              {this.el && this.el.scrollHeight > DEFAULT_SCROLL_HEIGHT
                ?

                : null}

}

    );
  }

  scrollToBottom() {
    if (
      !this.el ||
      this.expanded === true ||
      this.props.store.isPlain === true ||
      atom.config.get(`Hydrogen.autoScroll`) === false
    )
      return;
    const scrollHeight = this.el.scrollHeight;
    const height = this.el.clientHeight;
    const maxScrollTop = scrollHeight - height;
    this.el.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
  }

  componentDidUpdate() {
    this.scrollToBottom();
  }

  componentDidMount() {
    this.scrollToBottom();
  }

  componentWillUnmount() {
    this.containerTooltip.dispose();
    this.buttonTooltip.dispose();
  }
}

export default ResultViewComponent;
<|endoftext|>


<commit_before>/* eslint no-underscore-dangle: 0 */

import { logger } from '@storybook/client-logger';

import StoryStore from './story_store';

const defaultDecorateStory = (getStory, decorators) =>
  decorators.reduce(
    (decorated, decorator) => context => decorator(() => decorated(context), context),
    getStory
  );

export default class ClientApi {
  constructor({ storyStore = new StoryStore(), decorateStory = defaultDecorateStory } = {}) {
    this._storyStore = storyStore;
    this._addons = {};
    this._globalDecorators = [];
    this._decorateStory = decorateStory;
  }

  setAddon = addon => {
    this._addons = {
      ...this._addons,
      ...addon,
    };
  };

  addDecorator = decorator => {
    this._globalDecorators.push(decorator);
  };

  clearDecorators = () => {
    this._globalDecorators = [];
  };

  storiesOf = (kind, m) => {
    if (!kind && typeof kind !== 'string') {
      throw new Error('Invalid or missing kind provided for stories, should be a string');
    }

    if (!m) {
      logger.warn(
        `Missing 'module' parameter for story with a kind of '${kind}'. It will break your HMR`
      );
    }

    if (m && m.hot) {
      m.hot.dispose(() => {
        this._storyStore.removeStoryKind(kind);
        this._storyStore.incrementRevision();
      });
    }

    const localDecorators = [];
    const api = {
      kind,
    };

    // apply addons
    Object.keys(this._addons).forEach(name => {
      const addon = this._addons[name];
      api[name] = (...args) => {
        addon.apply(api, args);
        return api;
      };
    });

    api.add = (storyName, getStory) => {
      if (typeof storyName !== 'string') {
        throw new Error(`Invalid or missing storyName provided for a "${kind}" story.`);
      }

      if (this._storyStore.hasStory(kind, storyName)) {
        throw new Error(`Story of "${kind}" named "${storyName}" already exists`);
      }

      // Wrap the getStory function with each decorator. The first
      // decorator will wrap the story function. The second will
      // wrap the first decorator and so on.
      const decorators = [...localDecorators, ...this._globalDecorators];

      const fileName = m ? m.filename : null;

      // Add the fully decorated getStory function.
      this._storyStore.addStory(
        kind,
        storyName,
        this._decorateStory(getStory, decorators),
        fileName
      );
      return api;
    };

    api.addDecorator = decorator => {
      localDecorators.push(decorator);
      return api;
    };

    return api;
  };

  getStorybook = () =>
    this._storyStore.getStoryKinds().map(kind => {
      const fileName = this._storyStore.getStoryFileName(kind);

      const stories = this._storyStore.getStories(kind).map(name => {
        const render = this._storyStore.getStory(kind, name);
        return { name, render };
      });

      return { kind, fileName, stories };
    });
}
<commit_msg>`module.hot.dispose` is not defined in RN apparently<commit_after>/* eslint no-underscore-dangle: 0 */

import { logger } from '@storybook/client-logger';

import StoryStore from './story_store';

const defaultDecorateStory = (getStory, decorators) =>
  decorators.reduce(
    (decorated, decorator) => context => decorator(() => decorated(context), context),
    getStory
  );

export default class ClientApi {
  constructor({ storyStore = new StoryStore(), decorateStory = defaultDecorateStory } = {}) {
    this._storyStore = storyStore;
    this._addons = {};
    this._globalDecorators = [];
    this._decorateStory = decorateStory;
  }

  setAddon = addon => {
    this._addons = {
      ...this._addons,
      ...addon,
    };
  };

  addDecorator = decorator => {
    this._globalDecorators.push(decorator);
  };

  clearDecorators = () => {
    this._globalDecorators = [];
  };

  storiesOf = (kind, m) => {
    if (!kind && typeof kind !== 'string') {
      throw new Error('Invalid or missing kind provided for stories, should be a string');
    }

    if (!m) {
      logger.warn(
        `Missing 'module' parameter for story with a kind of '${kind}'. It will break your HMR`
      );
    }

    if (m && m.hot && m.hot.dispose) {
      m.hot.dispose(() => {
        this._storyStore.removeStoryKind(kind);
        this._storyStore.incrementRevision();
      });
    }

    const localDecorators = [];
    const api = {
      kind,
    };

    // apply addons
    Object.keys(this._addons).forEach(name => {
      const addon = this._addons[name];
      api[name] = (...args) => {
        addon.apply(api, args);
        return api;
      };
    });

    api.add = (storyName, getStory) => {
      if (typeof storyName !== 'string') {
        throw new Error(`Invalid or missing storyName provided for a "${kind}" story.`);
      }

      if (this._storyStore.hasStory(kind, storyName)) {
        throw new Error(`Story of "${kind}" named "${storyName}" already exists`);
      }

      // Wrap the getStory function with each decorator. The first
      // decorator will wrap the story function. The second will
      // wrap the first decorator and so on.
      const decorators = [...localDecorators, ...this._globalDecorators];

      const fileName = m ? m.filename : null;

      // Add the fully decorated getStory function.
      this._storyStore.addStory(
        kind,
        storyName,
        this._decorateStory(getStory, decorators),
        fileName
      );
      return api;
    };

    api.addDecorator = decorator => {
      localDecorators.push(decorator);
      return api;
    };

    return api;
  };

  getStorybook = () =>
    this._storyStore.getStoryKinds().map(kind => {
      const fileName = this._storyStore.getStoryFileName(kind);

      const stories = this._storyStore.getStories(kind).map(name => {
        const render = this._storyStore.getStory(kind, name);
        return { name, render };
      });

      return { kind, fileName, stories };
    });
}
<|endoftext|>


<commit_before>// This program is intended to provide a foundation for simulating disease
// transfer in a population with a variety of different factors, and across a
// range of strategies for preventing disease transfer

// factors:
// - Activity level (some people will have more interactions than others, maybe
//   males in general?

// We start with a population of unspecified individuals, and run a clock. At
// each tick of the clock we conduct an event. An event represents an
// opportunity for a disease transfer to occur. The population is seeded to
// provide a source for the disease.

// events model transmission rates, affected by the various strategies

var animatePairs = false;
var liveUpdateGraph = false;
var liveUpdateTree = false;

var borderColorHighlight = "#FFFF00";
var borderColorActive = "#808080";

// the population
var populationDimension;
var populationSize;
var population;
var infectiousCount = 0;
var infectedCount = 0;

// the stats to plot at the end of a run
var infectiousByDay = [];
var infectedByDay = [];

// the clock
var clock;
var day;
var clockDisplay;
var paused;
var eventRate;
var eventsPerDiem;

var lastAtomA, lastAtomB;

var createPopulation = function () {
    population = [];

    // create all the individuals
    for (var id = 0; id < populationSize; ++id) {
        var atom = Object.create (atomPrototype);
        atom.id = id;
        atom.events = [];

        // loop over the filters
        for (var i = 0, count = filters.length; i < count; ++i) {
            filters[i].init(atom);
        }

        // assign the atom to the population
        population[id] = atom;
    }

    // now seed one of the individuals with disease
    var randomIndex = Math.floor (Math.random () * populationSize);
    var atom = population[randomIndex];
    atom.setState (disease.INFECTED, null);
    infectiousCount = infectedCount = 1;
    infectiousByDay = [];
    infectedByDay = [];
};

var conductEvent = function () {
    // clear the last event info by denoting those atoms as "active"
    lastAtomA.link.style.stroke = borderColorActive;
    lastAtomB.link.style.stroke = borderColorActive;

    // randomly pair two individuals from the population
    var atomA = population[Math.floor(Math.random() * populationSize)];
    var atomB = population[Math.floor(Math.random() * populationSize)];
    while (!sampler.pair(atomA, atomB)) {
        atomB = population[Math.floor(Math.random() * populationSize)];
    }

    // highlight this pairing
    if (animatePairs) {
        atomA.link.style.stroke = borderColorHighlight;
        atomB.link.style.stroke = borderColorHighlight;
    }

    // loop over the filters
    var transmit = true;
    for (var i = 0, count = filters.length; transmit && (i < count) ; ++i) {
        transmit = filters[i].test(atomA, atomB);
    }

    // if the transmit event succeeds, do the deed
    if (transmit) {
        atomA.infectBy(atomB);
        atomB.infectBy(atomA);
        ++infectiousCount;
        ++infectedCount;
    }

    // save this information for the next event
    lastAtomA = atomA;
    lastAtomB = atomB;
};

var makeGraph = function () {
    var svg = PlotSvg.multipleLine("Infected Count vs. Day", "Day", "Infected (n)", [infectiousByDay, infectedByDay]);
    document.getElementById("chart").innerHTML = svg;
};

var makeTree = function () {
    // copy the population array, since we want to sort it
    var data = new Array(populationSize);
    for (var i = 0; i < populationSize; ++i) {
        data[i] = population[i];
    }

    // helper function to retrieve an infection event
    var getInfectionEvent = function (atom) {
        if ((atom.events.length > 0) &&
            (atom.events[0].state.name == "INFECTED") &&
            ("by" in atom.events[0])) {
            return atom.events[0];
        }
        return null;
    };

    // sort the population by the infection date
    data.sort(function (a, b) {
        var aEvent = getInfectionEvent(a);
        var bEvent = getInfectionEvent(b);
        if (aEvent != null) {
            if (bEvent != null) {
                return aEvent.day - bEvent.day;
            } else {
                return 1;
            }
        } else if (bEvent != null) {
            // already know that a is null
            return -1;
        } else {
            return 0;
        }
    });

    // sweep the population to build the infection tree
    for (var i = 0, count = data.length; i < count; ++i) {
        var atom = data[i];
        var infectionEvent = getInfectionEvent(atom);
        atom.parentId = (infectionEvent != null) ? infectionEvent.by.id : null;
    }

    // get the root of the tree
    var root = TreeSvg.extractTreeFromParentField(data, "id", "parentId");
    var helper = TreeSvg.getDefaultHelper ();
    helper.getId = function (node) { return node.id; };
    helper.getColor = function (node) { return node.state.color; };
    var svg = TreeSvg.renderWithHelper(root, helper);
    document.getElementById("tree").innerHTML = svg;
};

var allInfected = function () {
    // clear the final highlight
    lastAtomA.link.style.stroke = borderColorActive;
    lastAtomB.link.style.stroke = borderColorActive;

    // an event to say we're done
    simulatorFinished();
};

var startNewDay = function () {
    day = Math.floor(clock / eventsPerDiem);

    // sweep over the population to see if somebody becomes contagious or heals
    // or dies, or...
    for (var id = 0; id < populationSize; ++id) {
        var atom = population[id];

        switch (atom.state.name) {
            case disease.INFECTED.name: {
                var probability = ((day - atom.day()) - atom.state.daysMin) / (atom.state.daysMax - atom.state.daysMin);
                if (Math.random() < probability) {
                    // an infected individual might become clinical
                    atom.setState(disease.CLINICAL, null);
                }
            }
                break;
            case disease.CLINICAL.name: {
                var probability = ((day - atom.day()) - atom.state.daysMin) / (atom.state.daysMax - atom.state.daysMin);
                if (Math.random() < probability) {
                    // a clinical individual might get better, and become convalescent
                    if (disease.CONVALESCENT.recurrence == 0) {
                        --infectiousCount;
                    }
                    atom.setState(disease.CONVALESCENT, null);
                }
            }
                break;
            case disease.CONVALESCENT.name: {
                var probability = ((day - atom.day()) - atom.state.daysMin) / (atom.state.daysMax - atom.state.daysMin);
                if (Math.random() < probability) {
                    // a convalescent individual might return to healthy
                    --infectedCount;
                    atom.setState(disease.HEALTHY, null);
                } else if (Math.random() < atom.state.recurrence) {
                    // or a convalescent individual might have a recurrence
                    atom.setState(disease.CLINICAL, null);
                }
            }
                break;
        }
    }

    // accumulate the stats
    infectedByDay.push({ x: day, y: infectedCount });
    infectiousByDay.push({ x: day, y: infectiousCount });
    if (liveUpdateGraph) {
        makeGraph();
    }
    if (liveUpdateTree) {
        makeTree();
    }
};

var tick = function () {
    if (!paused) {
        if ((infectiousCount > 0) && (infectedCount < populationSize)) {
            // see if it's a new day
            if ((clock % eventsPerDiem) == 0) {
                // do things that happen on a per day basis - we do this here to
                // avoid bias in events
                startNewDay();
            }

            // advance the clock
            ++clock;

            // conduct an event and loop back @ 30Hz
            conductEvent();
            setTimeout(tick, animatePairs ? 50 : 1);
        } else {
            allInfected();
            paused = true;
        }
    }
    clockDisplay.textContent = "Day " + day + " (" + clock + ", " + infectedCount + "/" + populationSize + " = " + ((100.0 * infectedCount) / populationSize).toFixed(1) + "%)";
};

var makeSvg = function () {
    // open the SVG and make the render port work like a mathematical system
    var svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"-1.125 -1.125 2.25 2.25\" preserveAspectRatio=\"xMidYMid meet\">";
    //svg += "<g id=\"root\" transform=\"scale(1, -1)\">";

    // compute the placement parameters
    var blockSize = 0.75;
    var spacing = 2.0 / (populationDimension + 1);
    var size = spacing * blockSize;
    var offset = -size / 2.0;

    // loop over the whole population to place each individual in the world
    for (var id = 0; id < populationSize; ++id) {
        // get the individual from the population
        var atom = population[id];

        // compute the position of the block
        var xy = atom.map();
        var x = -1 + ((xy.x + 1) * spacing) + offset;
        var y = -1 + ((xy.y + 1) * spacing) + offset;

        // loop over the filters
        for (var i = 0, count = filters.length; i < count ; ++i) {
            svg += filters[i].render(atom, x, y, size, size);
        }
    }
    //svg += "</g>";

    // add the clock
    svg += "<text id=\"clockDisplay\" x=\"-1\" y=\"-1\" font-family=\"Verdana\" font-size=\"0.075\" fill=\"#404040\">Ready</text>";

    // close the SVG
    svg += "</svg>";
    return svg;
};

var linkSvg = function () {
    for (var id = 0; id < populationSize; ++id) {
        var atom = population[id];
        var link = document.getElementById ("rect" + id);
        atom.link = link;
    }
    clockDisplay = document.getElementById ("clockDisplay");

    // create a dummy atom to clear status on so that we can remove an "if"
    // statement from the inner loop (this object will be abandoned very early)
    lastAtomA = {};
    lastAtomA.link = {};
    lastAtomA.link.style = {};
    lastAtomB = lastAtomA;
}

var toggleRun = function () {
    // pause and resume animation
    if (paused) {
        if ((infectiousCount == 0) || (infectedCount == populationSize)) {
            init();
        } else {
            paused = false;
            tick();
        }
    } else {
        paused = true;
    }
    return paused;
};

var init = function () {
    // reset the clock
    clock = day = 0;
    paused = true;

    createPopulation();
    var display = makeSvg();
    document.getElementById("display").innerHTML = display;
    linkSvg();
};
<commit_msg>update for new container helper in tree rendering<commit_after>// This program is intended to provide a foundation for simulating disease
// transfer in a population with a variety of different factors, and across a
// range of strategies for preventing disease transfer

// factors:
// - Activity level (some people will have more interactions than others, maybe
//   males in general?

// We start with a population of unspecified individuals, and run a clock. At
// each tick of the clock we conduct an event. An event represents an
// opportunity for a disease transfer to occur. The population is seeded to
// provide a source for the disease.

// events model transmission rates, affected by the various strategies

var animatePairs = false;
var liveUpdateGraph = false;
var liveUpdateTree = false;

var borderColorHighlight = "#FFFF00";
var borderColorActive = "#808080";

// the population
var populationDimension;
var populationSize;
var population;
var infectiousCount = 0;
var infectedCount = 0;

// the stats to plot at the end of a run
var infectiousByDay = [];
var infectedByDay = [];

// the clock
var clock;
var day;
var clockDisplay;
var paused;
var eventRate;
var eventsPerDiem;

var lastAtomA, lastAtomB;

var createPopulation = function () {
    population = [];

    // create all the individuals
    for (var id = 0; id < populationSize; ++id) {
        var atom = Object.create (atomPrototype);
        atom.id = id;
        atom.events = [];

        // loop over the filters
        for (var i = 0, count = filters.length; i < count; ++i) {
            filters[i].init(atom);
        }

        // assign the atom to the population
        population[id] = atom;
    }

    // now seed one of the individuals with disease
    var randomIndex = Math.floor (Math.random () * populationSize);
    var atom = population[randomIndex];
    atom.setState (disease.INFECTED, null);
    infectiousCount = infectedCount = 1;
    infectiousByDay = [];
    infectedByDay = [];
};

var conductEvent = function () {
    // clear the last event info by denoting those atoms as "active"
    lastAtomA.link.style.stroke = borderColorActive;
    lastAtomB.link.style.stroke = borderColorActive;

    // randomly pair two individuals from the population
    var atomA = population[Math.floor(Math.random() * populationSize)];
    var atomB = population[Math.floor(Math.random() * populationSize)];
    while (!sampler.pair(atomA, atomB)) {
        atomB = population[Math.floor(Math.random() * populationSize)];
    }

    // highlight this pairing
    if (animatePairs) {
        atomA.link.style.stroke = borderColorHighlight;
        atomB.link.style.stroke = borderColorHighlight;
    }

    // loop over the filters
    var transmit = true;
    for (var i = 0, count = filters.length; transmit && (i < count) ; ++i) {
        transmit = filters[i].test(atomA, atomB);
    }

    // if the transmit event succeeds, do the deed
    if (transmit) {
        atomA.infectBy(atomB);
        atomB.infectBy(atomA);
        ++infectiousCount;
        ++infectedCount;
    }

    // save this information for the next event
    lastAtomA = atomA;
    lastAtomB = atomB;
};

var makeGraph = function () {
    var svg = PlotSvg.multipleLine("Infected Count vs. Day", "Day", "Infected (n)", [infectiousByDay, infectedByDay]);
    document.getElementById("chart").innerHTML = svg;
};

var makeTree = function () {
    // copy the population array, since we want to sort it
    var data = new Array(populationSize);
    for (var i = 0; i < populationSize; ++i) {
        data[i] = population[i];
    }

    // helper function to retrieve an infection event
    var getInfectionEvent = function (atom) {
        if ((atom.events.length > 0) &&
            (atom.events[0].state.name == "INFECTED") &&
            ("by" in atom.events[0])) {
            return atom.events[0];
        }
        return null;
    };

    // sort the population by the infection date
    data.sort(function (a, b) {
        var aEvent = getInfectionEvent(a);
        var bEvent = getInfectionEvent(b);
        if (aEvent != null) {
            if (bEvent != null) {
                return aEvent.day - bEvent.day;
            } else {
                return 1;
            }
        } else if (bEvent != null) {
            // already know that a is null
            return -1;
        } else {
            return 0;
        }
    });

    // sweep the population to build the infection tree
    for (var i = 0, count = data.length; i < count; ++i) {
        var atom = data[i];
        var infectionEvent = getInfectionEvent(atom);
        atom.parentId = (infectionEvent != null) ? infectionEvent.by.id : null;
    }

    // get the root of the tree
    var root = TreeSvg.extractTreeFromParentField(data, "id", "parentId");
    var helper = TreeSvg.getDefaultHelper ();
    helper.getId = function (container) { return container.node.id; };
    helper.getColor = function (container) { return container.node.state.color; };
    var svg = TreeSvg.renderWithHelper(root, helper);
    document.getElementById("tree").innerHTML = svg;
};

var allInfected = function () {
    // clear the final highlight
    lastAtomA.link.style.stroke = borderColorActive;
    lastAtomB.link.style.stroke = borderColorActive;

    // an event to say we're done
    simulatorFinished();
};

var startNewDay = function () {
    day = Math.floor(clock / eventsPerDiem);

    // sweep over the population to see if somebody becomes contagious or heals
    // or dies, or...
    for (var id = 0; id < populationSize; ++id) {
        var atom = population[id];

        switch (atom.state.name) {
            case disease.INFECTED.name: {
                var probability = ((day - atom.day()) - atom.state.daysMin) / (atom.state.daysMax - atom.state.daysMin);
                if (Math.random() < probability) {
                    // an infected individual might become clinical
                    atom.setState(disease.CLINICAL, null);
                }
            }
                break;
            case disease.CLINICAL.name: {
                var probability = ((day - atom.day()) - atom.state.daysMin) / (atom.state.daysMax - atom.state.daysMin);
                if (Math.random() < probability) {
                    // a clinical individual might get better, and become convalescent
                    if (disease.CONVALESCENT.recurrence == 0) {
                        --infectiousCount;
                    }
                    atom.setState(disease.CONVALESCENT, null);
                }
            }
                break;
            case disease.CONVALESCENT.name: {
                var probability = ((day - atom.day()) - atom.state.daysMin) / (atom.state.daysMax - atom.state.daysMin);
                if (Math.random() < probability) {
                    // a convalescent individual might return to healthy
                    --infectedCount;
                    atom.setState(disease.HEALTHY, null);
                } else if (Math.random() < atom.state.recurrence) {
                    // or a convalescent individual might have a recurrence
                    atom.setState(disease.CLINICAL, null);
                }
            }
                break;
        }
    }

    // accumulate the stats
    infectedByDay.push({ x: day, y: infectedCount });
    infectiousByDay.push({ x: day, y: infectiousCount });
    if (liveUpdateGraph) {
        makeGraph();
    }
    if (liveUpdateTree) {
        makeTree();
    }
};

var tick = function () {
    if (!paused) {
        if ((infectiousCount > 0) && (infectedCount < populationSize)) {
            // see if it's a new day
            if ((clock % eventsPerDiem) == 0) {
                // do things that happen on a per day basis - we do this here to
                // avoid bias in events
                startNewDay();
            }

            // advance the clock
            ++clock;

            // conduct an event and loop back @ 30Hz
            conductEvent();
            setTimeout(tick, animatePairs ? 50 : 1);
        } else {
            allInfected();
            paused = true;
        }
    }
    clockDisplay.textContent = "Day " + day + " (" + clock + ", " + infectedCount + "/" + populationSize + " = " + ((100.0 * infectedCount) / populationSize).toFixed(1) + "%)";
};

var makeSvg = function () {
    // open the SVG and make the render port work like a mathematical system
    var svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"-1.125 -1.125 2.25 2.25\" preserveAspectRatio=\"xMidYMid meet\">";
    //svg += "<g id=\"root\" transform=\"scale(1, -1)\">";

    // compute the placement parameters
    var blockSize = 0.75;
    var spacing = 2.0 / (populationDimension + 1);
    var size = spacing * blockSize;
    var offset = -size / 2.0;

    // loop over the whole population to place each individual in the world
    for (var id = 0; id < populationSize; ++id) {
        // get the individual from the population
        var atom = population[id];

        // compute the position of the block
        var xy = atom.map();
        var x = -1 + ((xy.x + 1) * spacing) + offset;
        var y = -1 + ((xy.y + 1) * spacing) + offset;

        // loop over the filters
        for (var i = 0, count = filters.length; i < count ; ++i) {
            svg += filters[i].render(atom, x, y, size, size);
        }
    }
    //svg += "</g>";

    // add the clock
    svg += "<text id=\"clockDisplay\" x=\"-1\" y=\"-1\" font-family=\"Verdana\" font-size=\"0.075\" fill=\"#404040\">Ready</text>";

    // close the SVG
    svg += "</svg>";
    return svg;
};

var linkSvg = function () {
    for (var id = 0; id < populationSize; ++id) {
        var atom = population[id];
        var link = document.getElementById ("rect" + id);
        atom.link = link;
    }
    clockDisplay = document.getElementById ("clockDisplay");

    // create a dummy atom to clear status on so that we can remove an "if"
    // statement from the inner loop (this object will be abandoned very early)
    lastAtomA = {};
    lastAtomA.link = {};
    lastAtomA.link.style = {};
    lastAtomB = lastAtomA;
}

var toggleRun = function () {
    // pause and resume animation
    if (paused) {
        if ((infectiousCount == 0) || (infectedCount == populationSize)) {
            init();
        } else {
            paused = false;
            tick();
        }
    } else {
        paused = true;
    }
    return paused;
};

var init = function () {
    // reset the clock
    clock = day = 0;
    paused = true;

    createPopulation();
    var display = makeSvg();
    document.getElementById("display").innerHTML = display;
    linkSvg();
};
<|endoftext|>


<commit_before>const { getOAuthRequestToken, getAccessToken } = require('../sharer/twitterHelper')
const Twitter = require('twitter')

// Init
const init = ({ twitter_consumer_key, twitter_consumer_secret }, app) => {
  const consumerKey = twitter_consumer_key
  const consumerSecret = twitter_consumer_secret

  // EXPRESS
  app.get('/twitter/request_token', async (req, res) => {
    try {
      let { oauth_callback } = req.query
      oauth_callback = Buffer.from(oauth_callback, 'base64').toString('utf8')
      console.log(oauth_callback)
      let result = await getOAuthRequestToken({ consumerKey, consumerSecret, oauth_callback })
      res.send(JSON.stringify(result)) // send back url to login twitter
    } catch (error) {
      console.log('/twitter/request_token error', { error })
    }
  })

  // TWITTER hook callback
  app.get('/twitter/access_token', async (req, res) => {
    try {
      console.log('/twitter/access_token query', req.query)

      const { oauth_token, oauth_verifier, oauth_token_secret } = req.query

      let result = await getAccessToken({ oauth_token, oauth_token_secret, oauth_verifier, consumerKey, consumerSecret })
      res.send(JSON.stringify(result))
    } catch (error) {
      console.log('/twitter/access_token error', { error })
    }
  })

  app.post('/twitter/post', (req, res) => {
    const { accessToken, status } = req.body
    console.log('/twitter/post ', req.body)
    const tokenSplit = accessToken.split('_')
    const oauth_access_token = tokenSplit[0]
    const oauth_access_token_secret = tokenSplit[1]

    const client = new Twitter({
      consumer_key: consumerKey,
      consumer_secret: consumerSecret,
      access_token_key: oauth_access_token,
      access_token_secret: oauth_access_token_secret
    })

    client.post('statuses/update', { status }, (error, tweet, response) => {
      if (error) {
        console.log({ error })
        return res.send(JSON.stringify({ error }))
      }

      res.send(JSON.stringify({ tweet }))
      // console.log(tweet);  // Tweet body.
      // console.log(response);  // Raw response object.
    })
  })
}
module.exports = init
<commit_msg>chore(TWITTER): Add twitter api error response<commit_after>const { getOAuthRequestToken, getAccessToken } = require('../sharer/twitterHelper')
const Twitter = require('twitter')
const chalk = require('chalk')

// Init
const init = ({ twitter_consumer_key, twitter_consumer_secret }, app) => {
  const consumerKey = twitter_consumer_key
  const consumerSecret = twitter_consumer_secret

  if (!consumerKey || !consumerSecret) {
    console.warn(chalk.bgYellow('TWITTER consumerSecret not found! Therefore TWITTER feature will not work'))
    return
  }

  // EXPRESS
  app.get('/twitter/request_token', async (req, res) => {
    try {
      let { oauth_callback } = req.query
      oauth_callback = Buffer.from(oauth_callback, 'base64').toString('utf8')
      console.log(oauth_callback)
      let result = await getOAuthRequestToken({ consumerKey, consumerSecret, oauth_callback })
      res.send(JSON.stringify(result)) // send back url to login twitter
    } catch (error) {
      console.log('/twitter/request_token error', { error })
      res.status(500).send(error.toString())
    }
  })

  // TWITTER hook callback
  app.get('/twitter/access_token', async (req, res) => {
    try {
      console.log('/twitter/access_token query', req.query)

      const { oauth_token, oauth_verifier, oauth_token_secret } = req.query

      let result = await getAccessToken({ oauth_token, oauth_token_secret, oauth_verifier, consumerKey, consumerSecret })
      res.send(JSON.stringify(result))
    } catch (error) {
      console.log('/twitter/access_token error', { error })
      res.status(500).send(error.toString())
    }
  })

  app.post('/twitter/post', (req, res) => {
    try {
      const { accessToken, status } = req.body
      console.log('/twitter/post ', req.body)
      const tokenSplit = accessToken.split('_')
      const oauth_access_token = tokenSplit[0]
      const oauth_access_token_secret = tokenSplit[1]

      const client = new Twitter({
        consumer_key: consumerKey,
        consumer_secret: consumerSecret,
        access_token_key: oauth_access_token,
        access_token_secret: oauth_access_token_secret
      })

      client.post('statuses/update', { status }, (error, tweet, response) => {
        if (error) {
          console.log({ error })
          return res.status(500).send(JSON.stringify({ error }))
        }

        res.send(JSON.stringify({ tweet }))
        // console.log(tweet);  // Tweet body.
        // console.log(response);  // Raw response object.
      })
    } catch (error) {
      res.status(500).send(error.toString())
    }
  })
}
module.exports = init
<|endoftext|>


<commit_before>var dgram  = require('dgram');
var net    = require('net');
var util   = require('util');
var events = require('events');
var clone  = require('clone');
var os     = require('os');
var _      = require('underscore');
var packet = require('./packet');

var port = 56700;

var debug = false;

function init() {
	var l = new Lifx();
	l.on('ready', function() {
		l.startDiscovery(1000);
	});
	return l;
}

function Lifx() {
	events.EventEmitter.call(this);
	this.gateways = {};     // hash of gateway ids to gateway objects
	this.bulbs = {};        // hash of bulb ids to bulb objects
	this.udpClient = dgram.createSocket("udp4");
	this._intervalID = null;
	this._localIPs = getMyIPs();
	this._initNetwork();
	this._setupPacketListener();
	this._setupGatewayListener();
}
require('util').inherits(Lifx, require('events').EventEmitter);

Lifx.prototype._initNetwork = function() {
	var self = this;
	this.udpClient.on("error", function (err) {
		console.error("*** UDP error " + err);
		self.emit('error', err);
	});
	this.udpClient.on("message", function (msg, rinfo) {
		// Check it didn't come from us
		if (self._localIPs.indexOf(rinfo.address) > -1) {
			return;
		}
		if (debug) console.log(" U- " + msg.toString("hex"));
		var pkt = packet.fromBytes(msg);
        if (pkt)
		    self.emit('rawpacket', pkt, rinfo);
	});
	this.udpClient.bind(port, "0.0.0.0", function() {
		self.udpClient.setBroadcast(true);
		self.emit('ready');
	});
}

Lifx.prototype._sendPacket = function(dstIp, dstPort, packet) {
	if (debug) console.log(" U+ " + packet.toString("hex"));
	this.udpClient.send(packet, 0, packet.length, dstPort, dstIp, function(err, bytes) {/* jshint unused: false */
	});
}

Lifx.prototype.startDiscovery = function(interval) {
	// Now send the discovery packets
	var self = this;
	this._intervalID = setInterval(function() {
		self._sendPacket("255.255.255.255", port, packet.getPanGateway());
	}, interval);
};

Lifx.prototype.stopDiscovery = function() {
	clearInterval(this._intervalID);
};

Lifx.prototype._setupPacketListener = function() {
	var self = this;

	this.on('rawpacket', function(pkt, rinfo) {
                var bulb, found = false, i;

		switch (pkt.packetTypeShortName) {

			case 'panGateway':
				// Got a notification of a gateway.  Check if it's new, using valid UDP, and if it is then handle accordingly
				if (pkt.payload.service == 1 && pkt.payload.port > 0) {
					var gw = {ip:rinfo.address, port:pkt.payload.port, site:pkt.preamble.site,
							service: pkt.payload.service,
							protocol: pkt.preamble.protocol,
							bulbAddress: pkt.preamble.bulbAddress.toString("hex")
						};

					if (!self.gateways[gw.ip]) {
						//console.log(JSON.stringify(gw));
						self.gateways[gw.ip] = gw;
						self.emit('gateway', gw);
					}
				}
				break;

			case 'lightStatus':
				// Got a notification of a light's status.  Check if it's a new light, and handle it accordingly.
				var bulb = self.bulbs[pkt.preamble.bulbAddress.toString('hex')];
				if (bulb) {
					bulb.state = pkt.payload;
				}
				else {
					bulb = {addr:pkt.preamble.bulbAddress, name:pkt.payload.bulbLabel, state: pkt.payload};
					self.bulbs[bulb.addr.toString('hex')] = bulb;
					self.emit('bulb', bulb);
				}

				// Even if it's not new, emit updated info about the state of the bulb
                                bulb.state = { hue:        pkt.payload.hue,
                                               saturation: pkt.payload.saturation,
                                               brightness: pkt.payload.brightness,
                                               kelvin:     pkt.payload.kelvin,
                                               dim:        pkt.payload.dim,
                                               power:      pkt.payload.power
                                             };
				self.emit('bulbstate', bulb);
				break;

			case 'powerState':
				bulb = {addr:pkt.preamble.bulbAddress, state: {power: pkt.payload.onoff}}
				self.emit('bulbpower', bulb);
				break;

			case 'bulbLabel':
				bulb = {addr:pkt.preamble.bulbAddress, name:pkt.payload.label}
				self.emit('bulblabel', bulb);
				break;

			case 'getPanGateway':
				break;

			default:
				if (debug) {
					console.log('Unhandled packet of type ['+pkt.packetTypeShortName+']');
					console.log(pkt.payload);
				}
				self.emit('packet', pkt);
				break;
		}
	});

};

Lifx.prototype._setupGatewayListener = function() {
	var self = this;
	self.on('gateway', function(gw) {
		// Ask the gateway for new bulbs
		self._sendPacket(gw.ip, gw.port, packet.getLightState({site:gw.site}));

		// Also slow down the discovery packets, to be polite to the network
		self.stopDiscovery();
		self.startDiscovery(10000);
	});
};

Lifx.prototype.close = function() {
	// Remove things from the event loop and clean up
	this.stopDiscovery();
	this.udpClient.close();
};

Lifx.prototype._sendToOneOrAll = function(command, bulb) {
	var self = this;

//	var gw = this.gateways["192.168.0.103"];

	var bulbAddress = null;

	if (typeof bulb != 'undefined') {
		// Overwrite the bulb address here
		if (Buffer.isBuffer(bulb)) {
			bulbAddress = bulb;
		} else if (typeof bulb.addr != 'undefined') {
			bulbAddress = bulb.addr;
		} else {
			// Check if it's a hex string of a known bulb addr
			var b = self.bulbs[bulb]
			if (b) {
				bulbAddress = b.addr;
			}
			else {
				throw "Unknown bulb: " + bulb;
			}
		}
		bulbAddress.copy(command, 8);
	}

	_(this.gateways).each(function(gw, ip) {
		var siteAddress = gw.site;
		siteAddress.copy(command, 16);
		self._sendPacket(gw.ip, gw.port, command);
	});
};

Lifx.prototype.sendToAll = function(command) {
	this._sendToOneOrAll(command);
};

Lifx.prototype.sendToOne = function(command, bulb) {
	this._sendToOneOrAll(command, bulb);
};

/////////// Fun methods ////////////

// Turn lights on
Lifx.prototype.lightsOn = function(bulb) {
	this._sendToOneOrAll(packet.setPowerState({onoff:0xff, protocol: 0x1400}), bulb);
};

// Turn lights off
Lifx.prototype.lightsOff = function(bulb) {
	this._sendToOneOrAll(packet.setPowerState({onoff:0, protocol: 0x1400}), bulb);
};

// Set bulbs to a particular colour
// Pass in 16-bit numbers for each param - they will be byte shuffled as appropriate
Lifx.prototype.lightsColour = function(hue, sat, lum, whitecol, timing, bulb) {
	var params = {stream:0, hue:hue, saturation:sat, brightness:lum, kelvin:whitecol, fadeTime:timing, protocol: 0x1400};
	var message = packet.setLightColour(params);
	this._sendToOneOrAll(message, bulb);
};

// Request status from bulbs
Lifx.prototype.requestStatus = function() {
	this._sendToOneOrAll(packet.getLightState());
};

// Discover on demand
Lifx.prototype.discover = function() {
    this.stopDiscovery();
    this.startDiscovery(1000);
}

module.exports = {
    init: init,
    packet: packet,
    setDebug: function (d) {
        debug = d;
        packet.setDebug(d);
    }
};

// Utility method to get a list of local IP addresses
function getMyIPs() {
	var ips = [];
	var ifs = os.networkInterfaces();
	for (var i in ifs) {
		for (var j in ifs[i]) {
			ips.push(ifs[i][j].address);
		}
	}
	return ips;
}<commit_msg>Emit version state<commit_after>var dgram  = require('dgram');
var net    = require('net');
var util   = require('util');
var events = require('events');
var clone  = require('clone');
var os     = require('os');
var _      = require('underscore');
var packet = require('./packet');

var port = 56700;

var debug = false;

function init() {
	var l = new Lifx();
	l.on('ready', function() {
		l.startDiscovery(1000);
	});
	return l;
}

function Lifx() {
	events.EventEmitter.call(this);
	this.gateways = {};     // hash of gateway ids to gateway objects
	this.bulbs = {};        // hash of bulb ids to bulb objects
	this.udpClient = dgram.createSocket("udp4");
	this._intervalID = null;
	this._localIPs = getMyIPs();
	this._initNetwork();
	this._setupPacketListener();
	this._setupGatewayListener();
}
require('util').inherits(Lifx, require('events').EventEmitter);

Lifx.prototype._initNetwork = function() {
	var self = this;
	this.udpClient.on("error", function (err) {
		console.error("*** UDP error " + err);
		self.emit('error', err);
	});
	this.udpClient.on("message", function (msg, rinfo) {
		// Check it didn't come from us
		if (self._localIPs.indexOf(rinfo.address) > -1) {
			return;
		}
		if (debug) console.log(" U- " + msg.toString("hex"));
		var pkt = packet.fromBytes(msg);
        if (pkt)
		    self.emit('rawpacket', pkt, rinfo);
	});
	this.udpClient.bind(port, "0.0.0.0", function() {
		self.udpClient.setBroadcast(true);
		self.emit('ready');
	});
}

Lifx.prototype._sendPacket = function(dstIp, dstPort, packet) {
	if (debug) console.log(" U+ " + packet.toString("hex"));
	this.udpClient.send(packet, 0, packet.length, dstPort, dstIp, function(err, bytes) {/* jshint unused: false */
	});
}

Lifx.prototype.startDiscovery = function(interval) {
	// Now send the discovery packets
	var self = this;
	this._intervalID = setInterval(function() {
		self._sendPacket("255.255.255.255", port, packet.getPanGateway());
	}, interval);
};

Lifx.prototype.stopDiscovery = function() {
	clearInterval(this._intervalID);
};

Lifx.prototype._setupPacketListener = function () {
    var self = this;

    this.on('rawpacket', function (pkt, rinfo) {
        var bulb, found = false, i;

        switch (pkt.packetTypeShortName) {

            case 'panGateway':
                // Got a notification of a gateway.  Check if it's new, using valid UDP, and if it is then handle accordingly
                if (pkt.payload.service == 1 && pkt.payload.port > 0) {
                    var gw = { ip: rinfo.address, port: pkt.payload.port, site: pkt.preamble.site,
                        service: pkt.payload.service,
                        protocol: pkt.preamble.protocol,
                        bulbAddress: pkt.preamble.bulbAddress.toString("hex")
                    };

                    if (!self.gateways[gw.ip]) {
                        //console.log(JSON.stringify(gw));
                        self.gateways[gw.ip] = gw;
                        self.emit('gateway', gw);
                    }
                }
                break;

            case 'lightStatus':
                // Got a notification of a light's status.  Check if it's a new light, and handle it accordingly.
                var bulb = self.bulbs[pkt.preamble.bulbAddress.toString('hex')];
                if (bulb) {
                    bulb.state = pkt.payload;
                }
                else {
                    bulb = { addr: pkt.preamble.bulbAddress, name: pkt.payload.bulbLabel, state: pkt.payload };
                    self.bulbs[bulb.addr.toString('hex')] = bulb;
                    self.emit('bulb', bulb);
                }

                // Even if it's not new, emit updated info about the state of the bulb
                bulb.state = { hue: pkt.payload.hue,
                    saturation: pkt.payload.saturation,
                    brightness: pkt.payload.brightness,
                    kelvin: pkt.payload.kelvin,
                    dim: pkt.payload.dim,
                    power: pkt.payload.power
                };
                self.emit('bulbstate', bulb);
                break;

            case 'powerState':
                bulb = { addr: pkt.preamble.bulbAddress, state: { power: pkt.payload.onoff} }
                self.emit('bulbpower', bulb);
                break;

            case 'bulbLabel':
                bulb = { addr: pkt.preamble.bulbAddress, name: pkt.payload.label }
                self.emit('bulblabel', bulb);
                break;

            case 'getPanGateway':
                break;

            case 'versionState':
                bulb = { addr: pkt.preamble.bulbAddress, version: pkt.payload };
                self.emit('version', bulb);
                break;

            default:
                if (debug) {
                    console.log('Unhandled packet of type [' + pkt.packetTypeShortName + ']');
                    console.log(pkt.payload);
                }
                self.emit('packet', pkt);
                break;
        }
    });

};

Lifx.prototype._setupGatewayListener = function() {
	var self = this;
	self.on('gateway', function(gw) {
		// Ask the gateway for new bulbs
		self._sendPacket(gw.ip, gw.port, packet.getLightState({site:gw.site}));

		// Also slow down the discovery packets, to be polite to the network
		self.stopDiscovery();
		self.startDiscovery(10000);
	});
};

Lifx.prototype.close = function() {
	// Remove things from the event loop and clean up
	this.stopDiscovery();
	this.udpClient.close();
};

Lifx.prototype._sendToOneOrAll = function(command, bulb) {
	var self = this;

//	var gw = this.gateways["192.168.0.103"];

	var bulbAddress = null;

	if (typeof bulb != 'undefined') {
		// Overwrite the bulb address here
		if (Buffer.isBuffer(bulb)) {
			bulbAddress = bulb;
		} else if (typeof bulb.addr != 'undefined') {
			bulbAddress = bulb.addr;
		} else {
			// Check if it's a hex string of a known bulb addr
			var b = self.bulbs[bulb]
			if (b) {
				bulbAddress = b.addr;
			}
			else {
				throw "Unknown bulb: " + bulb;
			}
		}
		bulbAddress.copy(command, 8);
	}

	_(this.gateways).each(function(gw, ip) {
		var siteAddress = gw.site;
		siteAddress.copy(command, 16);
		self._sendPacket(gw.ip, gw.port, command);
	});
};

Lifx.prototype.sendToAll = function(command) {
	this._sendToOneOrAll(command);
};

Lifx.prototype.sendToOne = function(command, bulb) {
	this._sendToOneOrAll(command, bulb);
};

/////////// Fun methods ////////////

// Turn lights on
Lifx.prototype.lightsOn = function(bulb) {
	this._sendToOneOrAll(packet.setPowerState({onoff:0xff, protocol: 0x1400}), bulb);
};

// Turn lights off
Lifx.prototype.lightsOff = function(bulb) {
	this._sendToOneOrAll(packet.setPowerState({onoff:0, protocol: 0x1400}), bulb);
};

// Set bulbs to a particular colour
// Pass in 16-bit numbers for each param - they will be byte shuffled as appropriate
Lifx.prototype.lightsColour = function(hue, sat, lum, whitecol, timing, bulb) {
	var params = {stream:0, hue:hue, saturation:sat, brightness:lum, kelvin:whitecol, fadeTime:timing, protocol: 0x1400};
	var message = packet.setLightColour(params);
	this._sendToOneOrAll(message, bulb);
};

// Request status from bulbs
Lifx.prototype.requestStatus = function() {
	this._sendToOneOrAll(packet.getLightState());
};

// Discover on demand
Lifx.prototype.discover = function() {
    this.stopDiscovery();
    this.startDiscovery(1000);
}

module.exports = {
    init: init,
    packet: packet,
    setDebug: function (d) {
        debug = d;
        packet.setDebug(d);
    }
};

// Utility method to get a list of local IP addresses
function getMyIPs() {
	var ips = [];
	var ifs = os.networkInterfaces();
	for (var i in ifs) {
		for (var j in ifs[i]) {
			ips.push(ifs[i][j].address);
		}
	}
	return ips;
}<|endoftext|>


<commit_before>import React from 'react';
import CountUp from '../../src';
import Highlight from 'react-highlight.js';
import 'highlight.js/styles/solarized-dark.css';
import 'sanitize.css/lib/sanitize.css';
import styles from './App.css';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      start: 0,
      end: (new Date()).getFullYear() || 2016,
      duration: 3,
    };

    this.startAnimation = this.startAnimation.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    const { name, value } = event.target;

    this.setState({ [name]: Math.round(value) });
  }

  startAnimation(event) {
    this.forceUpdate();
    event.preventDefault();
  }

  render() {
    const { start, end, duration } = this.state;
    return (

React CountUp

            A React component wrapper around CountUp.js.
            This component counts up a number in an animated way.

Installation

          <Highlight language="bash">
            npm install react-countup --save
          </Highlight>

Usage

          <Highlight language="javascript">
{`import React from 'react';
import { render } from 'react-dom';
import CountUp from 'react-countup';

render(
  <CountUp start={0} end={2016} />,
  document.getElementById('root')
)`}
          </Highlight>

Demo

            <CountUp className={styles.CountUp} start={start} end={end} duration={duration} />

          <form className={styles.Form}>

                Start:
                <input
                  className={styles.TextField}
                  type="text"
                  name="start"
                  value={start}
                  onChange={this.handleChange}
                />

                End:
                <input
                  className={styles.TextField}
                  type="text"
                  name="end"
                  value={end}
                  onChange={this.handleChange}
                />

                Duration in seconds:
                <input
                  className={styles.TextField}
                  type="text"
                  name="duration"
                  value={duration}
                  onChange={this.handleChange}
                />

            <button className={styles.Button} onClick={this.startAnimation}>Start animation</button>
          </form>

            Handcrafted with
              ❤
             by Glenn Reyes

    );
  }
}

export default App;
<commit_msg>Live update code block<commit_after>import React from 'react';
import CountUp from '../../src';
import Highlight from 'react-highlight.js';
import 'highlight.js/styles/solarized-dark.css';
import 'sanitize.css/lib/sanitize.css';
import styles from './App.css';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      start: 0,
      end: (new Date()).getFullYear() || 2016,
      duration: 3,
    };

    this.startAnimation = this.startAnimation.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    const { name, value } = event.target;

    this.setState({ [name]: Math.round(value) });
  }

  startAnimation(event) {
    this.forceUpdate();
    event.preventDefault();
  }

  render() {
    const { start, end, duration } = this.state;
    return (

React CountUp

            A React component wrapper around CountUp.js.
            This component counts up a number in an animated way.

Installation

          <Highlight language="bash">
            npm install react-countup --save
          </Highlight>

Usage

          <Highlight language="javascript">
{`import React from 'react';
import { render } from 'react-dom';
import CountUp from 'react-countup';

render(
  <CountUp start={${start}} end={${end}} duration={${duration}} />,
  document.getElementById('root')
)`}
          </Highlight>

Demo

            <CountUp className={styles.CountUp} start={start} end={end} duration={duration} />

          <form className={styles.Form}>

                Start:
                <input
                  className={styles.TextField}
                  type="text"
                  name="start"
                  value={start}
                  onChange={this.handleChange}
                />

                End:
                <input
                  className={styles.TextField}
                  type="text"
                  name="end"
                  value={end}
                  onChange={this.handleChange}
                />

                Duration in seconds:
                <input
                  className={styles.TextField}
                  type="text"
                  name="duration"
                  value={duration}
                  onChange={this.handleChange}
                />

            <button className={styles.Button} onClick={this.startAnimation}>Start animation</button>
          </form>

            Handcrafted with
              ❤
             by Glenn Reyes

    );
  }
}

export default App;
<|endoftext|>


<commit_before>const _ = require(`lodash`)
const Promise = require(`bluebird`)
const path = require(`path`)
const slash = require(`slash`)

const LodashModuleReplacementPlugin = require(`lodash-webpack-plugin`)

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions

  return new Promise((resolve, reject) => {
    const blogPostTemplate = path.resolve(`src/templates/template-blog-post.js`)
    const tagPagesTemplate = path.resolve(`src/templates/template-tag-page.js`)
    graphql(
      `
        {
          allMarkdownRemark(
            limit: 1000
            filter: { frontmatter: { draft: { ne: true } } }
          ) {
            edges {
              node {
                fields {
                  slug
                }
                frontmatter {
                  tags
                }
              }
            }
          }
        }
      `
    ).then(result => {
      if (result.errors) {
        console.log(result.errors)
      }

      // Create blog posts pages.
      result.data.allMarkdownRemark.edges.forEach(edge => {
        createPage({
          path: edge.node.fields.slug, // required
          component: slash(blogPostTemplate),
          context: {
            slug: edge.node.fields.slug,
            highlight: edge.node.frontmatter.highlight,
            shadow: edge.node.frontmatter.shadow,
          },
        })
      })

      // Create tag pages.
      let tags = []
      result.data.allMarkdownRemark.edges.forEach(edge => {
        if (_.get(edge, `node.frontmatter.tags`)) {
          tags = tags.concat(edge.node.frontmatter.tags)
        }
      })
      tags = _.uniq(tags)
      tags.forEach(tag => {
        const tagPath = `/tags/${_.kebabCase(tag)}/`
        createPage({
          path: tagPath,
          component: tagPagesTemplate,
          context: {
            tag,
          },
        })
      })

      resolve()
    })
  })
}

// Add custom url pathname for blog posts.
exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `File`) {
    const parsedFilePath = path.parse(node.absolutePath)
    const slug = `/${parsedFilePath.dir.split(`---`)[1]}/`
    createNodeField({ node, name: `slug`, value: slug })
  } else if (
    node.internal.type === `MarkdownRemark` &&
    typeof node.slug === `undefined`
  ) {
    const fileNode = getNode(node.parent)
    createNodeField({
      node,
      name: `slug`,
      value: fileNode.fields.slug,
    })

    if (node.frontmatter.tags) {
      const tagSlugs = node.frontmatter.tags.map(
        tag => `/tags/${_.kebabCase(tag)}/`
      )
      createNodeField({ node, name: `tagSlugs`, value: tagSlugs })
    }
  }
}

// Sass and Lodash.
exports.onCreateWebpackConfig = ({ stage, actions }) => {
  switch (stage) {
    case `build-javascript`:
      actions.setWebpackConfig({
        plugins: [new LodashModuleReplacementPlugin()],
      })
  }
}
<commit_msg>example-using-remark reject Promise on error (#9633)<commit_after>const _ = require(`lodash`)
const Promise = require(`bluebird`)
const path = require(`path`)
const slash = require(`slash`)

const LodashModuleReplacementPlugin = require(`lodash-webpack-plugin`)

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions

  return new Promise((resolve, reject) => {
    const blogPostTemplate = path.resolve(`src/templates/template-blog-post.js`)
    const tagPagesTemplate = path.resolve(`src/templates/template-tag-page.js`)
    graphql(
      `
        {
          allMarkdownRemark(
            limit: 1000
            filter: { frontmatter: { draft: { ne: true } } }
          ) {
            edges {
              node {
                fields {
                  slug
                }
                frontmatter {
                  tags
                }
              }
            }
          }
        }
      `
    ).then(result => {
      if (result.errors) {
        return reject(result.errors)
      }

      // Create blog posts pages.
      result.data.allMarkdownRemark.edges.forEach(edge => {
        createPage({
          path: edge.node.fields.slug, // required
          component: slash(blogPostTemplate),
          context: {
            slug: edge.node.fields.slug,
            highlight: edge.node.frontmatter.highlight,
            shadow: edge.node.frontmatter.shadow,
          },
        })
      })

      // Create tag pages.
      let tags = []
      result.data.allMarkdownRemark.edges.forEach(edge => {
        if (_.get(edge, `node.frontmatter.tags`)) {
          tags = tags.concat(edge.node.frontmatter.tags)
        }
      })
      tags = _.uniq(tags)
      tags.forEach(tag => {
        const tagPath = `/tags/${_.kebabCase(tag)}/`
        createPage({
          path: tagPath,
          component: tagPagesTemplate,
          context: {
            tag,
          },
        })
      })

      return resolve()
    })
  })
}

// Add custom url pathname for blog posts.
exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `File`) {
    const parsedFilePath = path.parse(node.absolutePath)
    const slug = `/${parsedFilePath.dir.split(`---`)[1]}/`
    createNodeField({ node, name: `slug`, value: slug })
  } else if (
    node.internal.type === `MarkdownRemark` &&
    typeof node.slug === `undefined`
  ) {
    const fileNode = getNode(node.parent)
    createNodeField({
      node,
      name: `slug`,
      value: fileNode.fields.slug,
    })

    if (node.frontmatter.tags) {
      const tagSlugs = node.frontmatter.tags.map(
        tag => `/tags/${_.kebabCase(tag)}/`
      )
      createNodeField({ node, name: `tagSlugs`, value: tagSlugs })
    }
  }
}

// Sass and Lodash.
exports.onCreateWebpackConfig = ({ stage, actions }) => {
  switch (stage) {
    case `build-javascript`:
      actions.setWebpackConfig({
        plugins: [new LodashModuleReplacementPlugin()],
      })
  }
}
<|endoftext|>


<commit_before>/** LineupManager is the heart of Radio auto-asaad. It is a deamon responsible
for creating lineups for the Liquidsoap and schedule their playback. The
workflow in this class in a nutshell is as follows:

1- Generate a basic lineup based on the informatio provided in LineupTemplate
section of the config file. Store it in 'lineups' folder
2- Calculate the precise program timing and validate it to make sure to
overlapping happens
3- Generate a compiled version of the lineup that contains metadata information
about scheduling and other system-related info
3- Watch the lineup file (uncompiled version) for changes. If a change occurs
(by readio admin) the lineup should be recompiled and scheduled items must
be updated accordingly.

Note: We provide radio admins a set of web-service and/or script handlers
to modify the program lineups.
**/

/* ProgramTemplate objects look likes this:
 {
    "Id": "id"
    "PreShow": {
      "Clips": [list-clips-from-media-section],
      "FillerClip": "filler-from-media-section"
    },
    "Show": {
      "StartTime": {
        "CalculationMethod": "dynamic/static",
        "Calculator": "func_name/time-in-HH:mm:ss"
      },
      "OnLineupDecided": "func_name",
      "Clips": [clips-from-media-section]
    }
  }
*/

/* Program objects look like this:
  {
    "Id": "id"
    "PreShow": {
      "Clips": [
        {
            "Path": "uri-to-media",
            "Description": "description"
        }
      ],
      "FillerClip": {
        "Path": "uri-to-media",
        "Description": "description"
      }
    },
    "Show": {
      "StartTime": "time-in-HH:mm:ss",
      "Clips": [
        {
            "Path": "uri-to-media",
            "Description": "description"
        }
      ]
    }
  }
*/

/* CompiledProgram objects looks like this:
 {
    "Id": "id"
    "PreShow": {
      "Meta": {
          "TentativeStartTime": "time-in-HH:mm:ss",
          "TentativeEndTime": "time-in-HH:mm:ss"
      },
      "Scheduler": {
          "ScheduledAt": "time-in-HH:mm:ss",
          "ScheulerId": "cron-id"
      },
      "Clips": [
        {
            "Duration": length,
            "Path": "uri-to-media",
            "Description": "description"
        }
      ],
      "FillerClip": {
        "Path": "uri-to-media",
        "Description": "description"
      }
    },
    "Show": {
      "Meta": {
          "TentativeStartTime": "time-in-HH:mm:ss",
          "TentativeEndTime": "time-in-HH:mm:ss"
      }
      "Clips": [
        {
            "Length": length,
            "Path": "uri-to-media",
            "Description": "description"
        }
      ]
    }
  }
*/

var LineupManager = function(radioConfig, cwd, radioObj) {
    this.logger = null;
    this.fs = require('fs');

    this.moment = require('moment');
    // Custom format so that files are easier to read
    this.moment.prototype.toJSON = function() {
        return this.format();
    }

    this.config = radioConfig;
    this.cwd = cwd;
    // This is the radio object who calls us, should implement any
    // radio specific logic in the form of callbacks.
    this.radio = radioObj;

    // Today
    this.today = {
        date: null,
        lineupFilePath: "",
        lineupFileWatcher: null,
        lineup: {},
        compiledLineup: {}
    };
}

LineupManager.prototype.startMainLoop = function() {
    var Logger = require("../logger");

    var self = this;

    var lineupWatcher = function() {
        // Watch the lineup file for changes
        try {
            self.today.lineupFileWatcher = self.fs.watch(lineupFilePath, "",
                function(eventType, fileName) {
                    if (eventType == 'change') {
                        // Recompile
                        self.compileLineup();
                    }
                    // else?
                });
        } catch (e) {
            // If the lineup file does not exist, generate it from template
            self.generateLineup();
            self.compileLineup();
        }
    }

    var resetRadio = function() {
        // unwatch the old file
        if (self.today.lineupFileWatcher != null) {
            self.fs.close(self.today.lineupFileWatcher);
        }

        // start preparing for today
        self.today.date = self.moment().format("YYYY-MM-DD");
        self.today.lineupFilePath = self.cwd + "/lineups/" + self.radio.id + "-" + self.today.date + ".json";

        self.logger = new Logger(self.cwd + "/logs/lm-" + self.radio.id + "-" + self.today.date + ".log");

        self.radio.reset(self.today.date, function() {
            self.today.selectorIdx = self.radio.getSelectorIdx();

            lineupWatcher();
        })
    }

    resetRadio();

    // Wake up at the end of the day and reset the manager
    var tomorrow = this.moment().add(1, 'day').set('hour', 0).set('minute', 0).set('second', 0);
    var nextDayStartsInMillis = tomorrow.diff(this.moment());

    setTimeout(function() {

            // reset lineup manager, the file watcher will hence generate the new
            // lineup automatically.
            resetRadio(self);

        }, nextDayStartsInMillis);

    this.logger.info("Scheduled next lineup regeneration in " + nextDayStartsInMillis +
                "ms. (" + tomorrow.toString() + ")");

}

LineupManager.prototype.hasPreProgram = function(programTemplate) {
    return programTemplate.PreShow ? true: false;
}

LineupManager.prototype.generateLineup = function() {

    this.today.lineup.Programs = [];

    for (var i = 0; i < this.config.LineupTemplate.length; i++) {
        var programTemplate = this.config.LineupTemplate[i];

        // Decide for the program from the template
        var program = this.createProgramFromTemplate(programTemplate);

        // The show might have start time. If the program has a pre-program show,
        // it is mandatory to specify start time for it!
        //console.log(program.Show.StartTime)
        this.today.lineup.Programs.push(program);
    }

    // Persist
    this.fs.writeFileSync(this.today.lineupFilePath, JSON.stringify(this.today.lineup, null, 2), 'utf-8');

}

LineupManager.prototype.createProgramFromTemplate = function(programTemplate) {
    var program = {};
    program.Id = programTemplate.Id;

    if (this.hasPreProgram(programTemplate)) {
        this.decideProgramPreShowClipsFromTemplate(programTemplate, program);
    }
    this.decideProgramShowClipsFromTemplate(programTemplate, program);

    // If the program has a pre-show we must make sure that it is defined
    // correctly
    if (this.hasPreProgram(programTemplate)) {
        if (programTemplate.Show.StartTime == undefined || programTemplate.Show.StartTime == null) {
            throw "Program " + program.Id + " has a pre-show but does not specify exact start time for the program";
        }
    }

    if (programTemplate.Show.StartTime) {
        if (programTemplate.Show.StartTime.CalculationMethod == 'static') {
            program.Show.StartTime = moment(programTemplate.Show.StartTime.At, ['h:m:s', 'H:m:s']);
        } else {
            program.Show.StartTime = moment(this.radio[programTemplate.Show.StartTime.Calculator](program));
        }
    }

    return program;
}

LineupManager.prototype.decideProgramPreShowClipsFromTemplate = function(programTemplate, program) {
    this.logger.info("-- BEGIN OF PRE-SHOW LINEUP ARRANGEMENT FOR " + programTemplate.Id);

    program.PreShow = {}
    program.PreShow.Clips = [];

    for (var i = 0; i < programTemplate.PreShow.Clips.length; i++) {
        var mediaIdx = 0;
        // decide the index of media to be played in the slot
        mediaIdx = this.myMod(this.today.selectorIdx, this.config.Media[programTemplate.PreShow.Clips[i]].length);

        // resolve the media file path before writing down to lineup
        var media = {};
        Object.assign(media, this.config.Media[programTemplate.PreShow.Clips[i]][mediaIdx]);

        media.path = this.config.Media.BaseDir + media.path;

        this.logger.info("* I have decided to play " + programTemplate.PreShow.Clips[i] + " with index: " + mediaIdx);

        program.PreShow.Clips.push(media);
    }

    var fillerMedia = this.config.Media[programTemplate.PreShow.FillerClip][0];
    program.PreShow.FillerClip = fillerMedia;

    this.logger.info("* Filler clip is " + fillerMedia.path);

    this.logger.info("-- END OF PRE-SHOW LINEUP FOR " + programTemplate.Id);
}

LineupManager.prototype.decideProgramShowClipsFromTemplate = function(programTemplate, program) {
    this.logger.info("-- BEGIN OF PROGRAM LINEUP ARRANGEMENT FOR " + programTemplate.Id);

    program.Show = {}
    program.Show.Clips = [];

    for (var i = 0; i < programTemplate.Show.Clips.length; i++) {
        var mediaIdx = 0;
        // decide the index of media to be played in the slot
        mediaIdx = this.myMod(this.today.selectorIdx, this.config.Media[programTemplate.Show.Clips[i]].length);

        // resolve the media file path before writing down to lineup
        var media = {};
        Object.assign(media, this.config.Media[programTemplate.Show.Clips[i]][mediaIdx]);
        media.path = this.config.Media.BaseDir + media.path;

        this.logger.info("* I have decided to play " + programTemplate.Show.Clips[i] + " with index: " + mediaIdx);

        program.Show.Clips.push(media);
    }

    this.logger.info("-- END OF PROGRAM LINEUP FOR " + programTemplate.Id);
}

LineupManager.prototype.myMod = function(m, n) {
    return ((m % n) + n) % n;
}

LineupManager.prototype.compileLineup = function() {
    this.today.compiledLineup.Programs = [];

    // Calculate the start and end times for all programs
    for (var i = 0; i < this.today.lineup.Programs.length; i++) {

        // Prepare compiled program object
        compiledProgram = {};
        Object.assign(compiledProgram, this.today.lineup.Programs[i]);

        this.logger.info("Compiling Lineup - Pass 1 (Timing Calculation)");
        this.calculateProgramTimes(this.today.lineup.Programs[i], compiledProgram);

        this.today.compiledLineup.Programs.push(compiledProgram);
    }

    // Validate that there is no overlap
    this.validateLineup();

    // Schedule the playback
    this.scheduleLineupPlayback();

    // Persist the compiled lineup
    this.fs.writeFileSync(this.today.lineupFilePath + ".compiled", JSON.stringify(this.today.compiledLineup, null, 2), 'utf-8');

}

LineupManager.prototype.calculateProgramTimes = function(program, compiledProgram) {

    // Calculate the show start and end time
    compiledProgram.Show.Meta = {};

    // If this show is not scheduled for a specific time, then calculate timing
    // based on previous programs (and it should not have a pre-show program)
    if (program.Show.StartTime == undefined) {
        // Should start playback right after the last show
        var mostRecentProgram = this.today.compiledLineup.Programs[this.today.compiledLineup.Programs.length - 1];
        compiledProgram.Show.Meta.TentativeStartTime = mostRecentProgram.Meta.TentativeEndTime;
    } else {
        compiledProgram.Show.Meta.TentativeStartTime = program.Show.StartTime;
    }

    // Calculate the end time
    var totalShowDuration = 0;
    for (var i = 0; i < program.Show.Clips.length; i++) {
        var clip = program.Show.Clips[i]

        var clipDuration = this.getMediaDuration(clip);
        totalShowDuration += clipDuration;
        compiledProgram.Show.Clips[i].Duration = clipDuration;
    }

    compiledProgram.Show.Meta.TotalDuration = Math.ceil(totalShowDuration);
    compiledProgram.Show.Meta.TentativeEndTime = moment(compiledProgram.Show.Meta.TentativeStartTime).add(compiledProgram.Show.Meta.TotalDuration, 'seconds');

    // If there is a pre-show available calculate the timing for that too
    if (this.hasPreProgram(program)) {
        compiledProgram.PreShow.Meta = {};
        // Preshow ends when the main show begins
        compiledProgram.PreShow.Meta.TentativeEndTime = compiledProgram.Show.Meta.TentativeStartTime;

        // Calculate the start time of the preshow
        var totalPreShowDuration = 0;
        for (var i = 0; i < program.PreShow.Clips.length; i++) {
            var clip = program.PreShow.Clips[i]

            var clipDuration = this.getMediaDuration(clip);
            totalPreShowDuration += clipDuration;
            compiledProgram.PreShow.Clips[i].Duration = clipDuration;
        }

        compiledProgram.PreShow.Meta.TotalDuration = Math.ceil(totalPreShowDuration);
        compiledProgram.PreShow.Meta.TentativeStartTime = moment(compiledProgram.PreShow.Meta.TentativeEndTime).subtract(compiledProgram.Show.Meta.TotalDuration, 'seconds');
    }
}

LineupManager.prototype.validateLineup = function() {
    var latestEndTimeObserved = 0;
    for (var i = 0; i < this.today.compiledLineup.Programs.length; i++) {
        var currentProgram = this.today.compiledLineup.Programs[i];

        var currentProgramStartTime = this.hasPreProgram(currentProgram) ?
                        currentProgram.PreShow.Meta.TentativeStartTime :
                        currentProgram.Show.Meta.TentativeStartTime;

        if (currentProgramStartTime < latestEndTimeObserved) {
            throw "Program " + currentProgram.Id + " overlaps with the program before that.";
        } else {
            latestEndTimeObserved = currentProgramStartTime;
        }
    }
}

LineupManager.prototype.scheduleLineupPlayback = function() {
    for (var i = 0; i < this.today.compiledLineup.Programs.length; i++) {
        var currentProgram = this.today.compiledLineup.Programs[i];

        // if the program is explicitly marked for scheduling (StartTime is specified for it), do it!
        if (currentProgram.Show.StartTime) {
            this.schedulePlayback(currentProgram)
        }
    }
}

// implemented in subclasses
LineupManager.prototype.schedulePlayback = function(programTime) {
    console.log("Not implemented!");
}

// implemented in subclasses
LineupManager.prototype.getMediaDuration = function(media) {
    console.log("Not implemented!");
}

LineupManager.prototype.DeploymentMode = {
    STANDALONE: "standalone",
    LIQUIDSOAP: "liquidsoap"
}

LineupManager.build = function(deploymentMode, radioConfig, configFile, radioObj) {
    var clazz;
    switch (deploymentMode) {
        case LineupManager.prototype.DeploymentMode.STANDALONE:
            clazz = require('./standalone/standalone-lineup-manager');
            break;
        case LineupManager.prototype.DeploymentMode.LIQUIDSOAP:
            clazz = require('./liquidsoap/liquidsoap-lineup-manager');
            break;
    }
    return new clazz(radioConfig, configFile, radioObj);
}

module.exports = LineupManager;
<commit_msg>liquidsoap stabalizing<commit_after>/** LineupManager is the heart of Radio auto-asaad. It is a deamon responsible
for creating lineups for the Liquidsoap and schedule their playback. The
workflow in this class in a nutshell is as follows:

1- Generate a basic lineup based on the informatio provided in LineupTemplate
section of the config file. Store it in 'lineups' folder
2- Calculate the precise program timing and validate it to make sure to
overlapping happens
3- Generate a compiled version of the lineup that contains metadata information
about scheduling and other system-related info
3- Watch the lineup file (uncompiled version) for changes. If a change occurs
(by readio admin) the lineup should be recompiled and scheduled items must
be updated accordingly.

Note: We provide radio admins a set of web-service and/or script handlers
to modify the program lineups.
**/

/* ProgramTemplate objects look likes this:
 {
    "Id": "id"
    "PreShow": {
      "Clips": [list-clips-from-media-section],
      "FillerClip": "filler-from-media-section"
    },
    "Show": {
      "StartTime": {
        "CalculationMethod": "dynamic/static",
        "Calculator": "func_name/time-in-HH:mm:ss"
      },
      "OnLineupDecided": "func_name",
      "Clips": [clips-from-media-section]
    }
  }
*/

/* Program objects look like this:
  {
    "Id": "id"
    "PreShow": {
      "Clips": [
        {
            "Path": "uri-to-media",
            "Description": "description"
        }
      ],
      "FillerClip": {
        "Path": "uri-to-media",
        "Description": "description"
      }
    },
    "Show": {
      "StartTime": "time-in-HH:mm:ss",
      "Clips": [
        {
            "Path": "uri-to-media",
            "Description": "description"
        }
      ]
    }
  }
*/

/* CompiledProgram objects looks like this:
 {
    "Id": "id"
    "PreShow": {
      "Meta": {
          "TentativeStartTime": "time-in-HH:mm:ss",
          "TentativeEndTime": "time-in-HH:mm:ss"
      },
      "Scheduler": {
          "ScheduledAt": "time-in-HH:mm:ss",
          "ScheulerId": "cron-id"
      },
      "Clips": [
        {
            "Duration": length,
            "Path": "uri-to-media",
            "Description": "description"
        }
      ],
      "FillerClip": {
        "Path": "uri-to-media",
        "Description": "description"
      }
    },
    "Show": {
      "Meta": {
          "TentativeStartTime": "time-in-HH:mm:ss",
          "TentativeEndTime": "time-in-HH:mm:ss"
      }
      "Clips": [
        {
            "Length": length,
            "Path": "uri-to-media",
            "Description": "description"
        }
      ]
    }
  }
*/

var LineupManager = function(radioConfig, cwd, radioObj) {
    this.logger = null;
    this.fs = require('fs');

    this.moment = require('moment');
    // Custom format so that files are easier to read
    this.moment.prototype.toJSON = function() {
        return this.format();
    }

    this.config = radioConfig;
    this.cwd = cwd;
    // This is the radio object who calls us, should implement any
    // radio specific logic in the form of callbacks.
    this.radio = radioObj;

    // Today
    this.today = {
        date: null,
        lineupFilePath: "",
        lineupFileWatcher: null,
        lineup: {},
        compiledLineup: {}
    };
}

LineupManager.prototype.startMainLoop = function() {
    var Logger = require("../logger");

    var self = this;

    var lineupWatcher = function() {
        // Watch the lineup file for changes
        try {
            self.today.lineupFileWatcher = self.fs.watch(lineupFilePath, "",
                function(eventType, fileName) {
                    if (eventType == 'change') {
                        // Recompile
                        self.compileLineup();
                    }
                    // else?
                });
        } catch (e) {
            // If the lineup file does not exist, generate it from template
            self.generateLineup();
            self.compileLineup();
        }
    }

    var resetRadio = function() {
        // unwatch the old file
        if (self.today.lineupFileWatcher != null) {
            self.fs.close(self.today.lineupFileWatcher);
        }

        // start preparing for today
        self.today.date = self.moment().format("YYYY-MM-DD");
        self.today.lineupFilePath = self.cwd + "/lineups/" + self.radio.id + "-" + self.today.date + ".json";

        self.logger = new Logger(self.cwd + "/logs/lm-" + self.radio.id + "-" + self.today.date + ".log");

        self.radio.reset(self.today.date, function() {
            self.today.selectorIdx = self.radio.getSelectorIdx();

            lineupWatcher();
        })
    }

    resetRadio();

    // Wake up at the end of the day and reset the manager
    var tomorrow = this.moment().add(1, 'day').set('hour', 0).set('minute', 0).set('second', 0);
    var nextDayStartsInMillis = tomorrow.diff(this.moment());

    setTimeout(function() {

            // reset lineup manager, the file watcher will hence generate the new
            // lineup automatically.
            resetRadio(self);

        }, nextDayStartsInMillis);

    this.logger.info("Scheduled next lineup regeneration in " + nextDayStartsInMillis +
                "ms. (" + tomorrow.toString() + ")");

}

LineupManager.prototype.hasPreProgram = function(programTemplate) {
    return programTemplate.PreShow ? true: false;
}

LineupManager.prototype.generateLineup = function() {

    this.today.lineup.Programs = [];

    for (var i = 0; i < this.config.LineupTemplate.length; i++) {
        var programTemplate = this.config.LineupTemplate[i];

        // Decide for the program from the template
        var program = this.createProgramFromTemplate(programTemplate);

        // The show might have start time. If the program has a pre-program show,
        // it is mandatory to specify start time for it!
        //console.log(program.Show.StartTime)
        this.today.lineup.Programs.push(program);
    }

    // Persist
    this.fs.writeFileSync(this.today.lineupFilePath, JSON.stringify(this.today.lineup, null, 2), 'utf-8');

}

LineupManager.prototype.createProgramFromTemplate = function(programTemplate) {
    var program = {};
    program.Id = programTemplate.Id;

    if (this.hasPreProgram(programTemplate)) {
        this.decideProgramPreShowClipsFromTemplate(programTemplate, program);
    }
    this.decideProgramShowClipsFromTemplate(programTemplate, program);

    // If the program has a pre-show we must make sure that it is defined
    // correctly
    if (this.hasPreProgram(programTemplate)) {
        if (programTemplate.Show.StartTime == undefined || programTemplate.Show.StartTime == null) {
            throw "Program " + program.Id + " has a pre-show but does not specify exact start time for the program";
        }
    }

    if (programTemplate.Show.StartTime) {
        if (programTemplate.Show.StartTime.CalculationMethod == 'static') {
            program.Show.StartTime = moment(programTemplate.Show.StartTime.At, ['h:m:s', 'H:m:s']);
        } else {
            program.Show.StartTime = moment(this.radio[programTemplate.Show.StartTime.Calculator](program));
        }
    }

    return program;
}

LineupManager.prototype.decideProgramPreShowClipsFromTemplate = function(programTemplate, program) {
    this.logger.info("-- BEGIN OF PRE-SHOW LINEUP ARRANGEMENT FOR " + programTemplate.Id);

    program.PreShow = {}
    program.PreShow.Clips = [];

    for (var i = 0; i < programTemplate.PreShow.Clips.length; i++) {
        var mediaIdx = 0;
        // decide the index of media to be played in the slot
        mediaIdx = this.myMod(this.today.selectorIdx, this.config.Media[programTemplate.PreShow.Clips[i]].length);

        // resolve the media file path before writing down to lineup
        var media = {};
        Object.assign(media, this.config.Media[programTemplate.PreShow.Clips[i]][mediaIdx]);

        media.path = this.config.Media.BaseDir + media.path;

        this.logger.info("* I have decided to play " + programTemplate.PreShow.Clips[i] + " with index: " + mediaIdx);

        program.PreShow.Clips.push(media);
    }

    var fillerMedia = this.config.Media[programTemplate.PreShow.FillerClip][0];
    program.PreShow.FillerClip = fillerMedia;

    this.logger.info("* Filler clip is " + fillerMedia.path);

    this.logger.info("-- END OF PRE-SHOW LINEUP FOR " + programTemplate.Id);
}

LineupManager.prototype.decideProgramShowClipsFromTemplate = function(programTemplate, program) {
    this.logger.info("-- BEGIN OF PROGRAM LINEUP ARRANGEMENT FOR " + programTemplate.Id);

    program.Show = {}
    program.Show.Clips = [];

    for (var i = 0; i < programTemplate.Show.Clips.length; i++) {
        var mediaIdx = 0;
        // decide the index of media to be played in the slot
        mediaIdx = this.myMod(this.today.selectorIdx, this.config.Media[programTemplate.Show.Clips[i]].length);

        // resolve the media file path before writing down to lineup
        var media = {};
        Object.assign(media, this.config.Media[programTemplate.Show.Clips[i]][mediaIdx]);
        media.path = this.config.Media.BaseDir + media.path;

        this.logger.info("* I have decided to play " + programTemplate.Show.Clips[i] + " with index: " + mediaIdx);

        program.Show.Clips.push(media);
    }

    this.logger.info("-- END OF PROGRAM LINEUP FOR " + programTemplate.Id);
}

LineupManager.prototype.myMod = function(m, n) {
    return ((m % n) + n) % n;
}

LineupManager.prototype.compileLineup = function() {
    this.today.compiledLineup.Programs = [];

    // Calculate the start and end times for all programs
    for (var i = 0; i < this.today.lineup.Programs.length; i++) {

        // Prepare compiled program object
        compiledProgram = {};
        Object.assign(compiledProgram, this.today.lineup.Programs[i]);

        this.logger.info("Compiling Lineup - Pass 1 (Timing Calculation)");
        this.calculateProgramTimes(this.today.lineup.Programs[i], compiledProgram);

        this.today.compiledLineup.Programs.push(compiledProgram);
    }

    // Validate that there is no overlap
    this.logger.info("Compiling Lineup - Pass 2 (Validation)");
    this.validateLineup();

    // Schedule the playback
    this.logger.info("Compiling Lineup - Pass 3 (Scheduling)");
    this.scheduleLineupPlayback();

    // Persist the compiled lineup
    this.fs.writeFileSync(this.today.lineupFilePath + ".compiled", JSON.stringify(this.today.compiledLineup, null, 2), 'utf-8');

}

LineupManager.prototype.calculateProgramTimes = function(program, compiledProgram) {

    // Calculate the show start and end time
    compiledProgram.Show.Meta = {};

    // If this show is not scheduled for a specific time, then calculate timing
    // based on previous programs (and it should not have a pre-show program)
    if (program.Show.StartTime == undefined) {
        // Should start playback right after the last show
        var mostRecentProgram = this.today.compiledLineup.Programs[this.today.compiledLineup.Programs.length - 1];
        compiledProgram.Show.Meta.TentativeStartTime = mostRecentProgram.Meta.TentativeEndTime;
    } else {
        compiledProgram.Show.Meta.TentativeStartTime = program.Show.StartTime;
    }

    // Calculate the end time
    var totalShowDuration = 0;
    for (var i = 0; i < program.Show.Clips.length; i++) {
        var clip = program.Show.Clips[i]

        var clipDuration = this.getMediaDuration(clip);
        totalShowDuration += clipDuration;
        compiledProgram.Show.Clips[i].Duration = clipDuration;
    }

    compiledProgram.Show.Meta.TotalDuration = Math.ceil(totalShowDuration);
    compiledProgram.Show.Meta.TentativeEndTime = moment(compiledProgram.Show.Meta.TentativeStartTime).add(compiledProgram.Show.Meta.TotalDuration, 'seconds');

    // If there is a pre-show available calculate the timing for that too
    if (this.hasPreProgram(program)) {
        compiledProgram.PreShow.Meta = {};
        // Preshow ends when the main show begins
        compiledProgram.PreShow.Meta.TentativeEndTime = compiledProgram.Show.Meta.TentativeStartTime;

        // Calculate the start time of the preshow
        var totalPreShowDuration = 0;
        for (var i = 0; i < program.PreShow.Clips.length; i++) {
            var clip = program.PreShow.Clips[i]

            var clipDuration = this.getMediaDuration(clip);
            totalPreShowDuration += clipDuration;
            compiledProgram.PreShow.Clips[i].Duration = clipDuration;
        }

        compiledProgram.PreShow.Meta.TotalDuration = Math.ceil(totalPreShowDuration);
        compiledProgram.PreShow.Meta.TentativeStartTime = moment(compiledProgram.PreShow.Meta.TentativeEndTime).subtract(compiledProgram.Show.Meta.TotalDuration, 'seconds');
    }
}

LineupManager.prototype.validateLineup = function() {
    var latestEndTimeObserved = 0;
    for (var i = 0; i < this.today.compiledLineup.Programs.length; i++) {
        var currentProgram = this.today.compiledLineup.Programs[i];

        var currentProgramStartTime = this.hasPreProgram(currentProgram) ?
                        currentProgram.PreShow.Meta.TentativeStartTime :
                        currentProgram.Show.Meta.TentativeStartTime;

        if (currentProgramStartTime < latestEndTimeObserved) {
            throw "Program " + currentProgram.Id + " overlaps with the program before that.";
        } else {
            latestEndTimeObserved = currentProgramStartTime;
        }
    }
}

LineupManager.prototype.scheduleLineupPlayback = function() {
    for (var i = 0; i < this.today.compiledLineup.Programs.length; i++) {
        var currentProgram = this.today.compiledLineup.Programs[i];

        // if the program is explicitly marked for scheduling (StartTime is specified for it), do it!
        if (currentProgram.Show.StartTime) {
            this.schedulePlayback(currentProgram)
        }
    }
}

// implemented in subclasses
LineupManager.prototype.schedulePlayback = function(programTime) {
    console.log("Not implemented!");
}

// implemented in subclasses
LineupManager.prototype.getMediaDuration = function(media) {
    console.log("Not implemented!");
}

LineupManager.prototype.DeploymentMode = {
    STANDALONE: "standalone",
    LIQUIDSOAP: "liquidsoap"
}

LineupManager.build = function(deploymentMode, radioConfig, configFile, radioObj) {
    var clazz;
    switch (deploymentMode) {
        case LineupManager.prototype.DeploymentMode.STANDALONE:
            clazz = require('./standalone/standalone-lineup-manager');
            break;
        case LineupManager.prototype.DeploymentMode.LIQUIDSOAP:
            clazz = require('./liquidsoap/liquidsoap-lineup-manager');
            break;
    }
    return new clazz(radioConfig, configFile, radioObj);
}

module.exports = LineupManager;
<|endoftext|>