<commit_before>import Bacon           from "baconjs"
import React           from "react"
import ReactDOM        from "react-dom"
import {Model as Atom} from "bacon.model"

import {classes, A, Button, Input, InputValue, UL, Span} from "bacon.react.html"

const model = initialRaw => {
  const initial = initialRaw.map((item, id) => ({...item, id}))
  const itemsAtom = Atom(initial)
  let counter = initial.length

  const addItem = title =>
    itemsAtom.modify(is => is.concat({id: counter++, title, isDone: false}))

  const setItem = ({id, title, isDone}) =>
    itemsAtom.modify(is =>
    is.map(i => i.id === id ? {id, title, isDone} : i))

  const remItem = ({id}) =>
    itemsAtom.modify(is => is.filter(i => i.id !== id))

  const toggleAll = () =>
    itemsAtom.modify(is =>
    is.map(is.some(i => !i.isDone)
           ? (i => ({...i, isDone: true}))
           : (i => ({...i, isDone: false}))))

  const clean = () => itemsAtom.modify(is => is.filter(i => !i.isDone))

  return {all: itemsAtom,
          active: itemsAtom.map(is => is.filter(i => !i.isDone)),
          completed: itemsAtom.map(is => is.filter(i => i.isDone)),
          addItem, setItem, remItem, toggleAll, clean}
}

const web = m => {
  const routes = [{hash: "#/",          items: m.all,       title: "All"},
                  {hash: "#/active",    items: m.active,    title: "Active"},
                  {hash: "#/completed", items: m.completed, title: "Completed"}]

  const routeAtom = Atom()
  window.onhashchange = () =>
    routeAtom.set(routes.find(r => r.hash === window.location.hash) || routes[0])
  window.onhashchange()

  const editingAtom = Atom(null)
  const newAtom = Atom("")

  const todos = (editing, items) =>
    items.map(({id, title, isDone}) =>

        <input className="toggle" type="checkbox" checked={isDone}
               onChange={_ => m.setItem({id, title, isDone: !isDone})}/>
         editingAtom.set(id)}
               className="view">{title}
        <button className="destroy" onClick={_ => m.remItem({id})}/>
        {editing !== id ? null : (() => {
          const textAtom = Atom(title)
          const exit = () => editingAtom.set(null)
          const save = () =>
            {const newTitle = textAtom.get().trim()
             exit()
             newTitle !== "" && m.setItem({id, title: newTitle, isDone})}
          return <InputValue didMount={c => c && c.focus()} type="text"
                   value={textAtom} className="edit" onBlur={save}
                   onKeyDown={e => e.which === 13 && save() ||
                                   e.which === 27 && exit()}/>})()}

)

  return

todos

        <InputValue type="text" value={newAtom} className="new-todo"
           placeholder="What needs to be done?"
           onKeyDown={e => {
             const t = newAtom.get().trim()
             e.which === 13 && t !== "" && m.addItem(t) && newAtom.set("")}}/>

        <Input className="toggle-all" onChange={m.toggleAll}
          type="checkbox" checked={m.active.map(a => a.length === 0)}/>

{routeAtom.flatMapLatest(
          ({items}) => Bacon.combineWith(editingAtom, items, todos))}

        {m.active.map(
          i => `${i.length} item${i.length === 1 ? "" : "s"}`)}

{routes.map(r =>

             classes(cr.hash === r.hash && "selected"))}
               onClick={() => window.location.hash = r.hash}>{r.title}

)
        }

        <Button className="clear-completed" onClick={m.clean}
                hidden={m.completed.map(c => c.length === 0)}>
          Clear completed</Button>

Double-click to edit a todo

}

const storeKey = "react.bacon-todos"

const m = model(JSON.parse(localStorage.getItem(storeKey) || "[]"))
m.all.onValue(is => localStorage.setItem(storeKey, JSON.stringify(is)))

ReactDOM.render(web(m), document.getElementById("app"))
<commit_msg>Drop ML'isms.<commit_after>import Bacon           from "baconjs"
import React           from "react"
import ReactDOM        from "react-dom"
import {Model as Atom} from "bacon.model"

import {classes, A, Button, Input, InputValue, UL, Span} from "bacon.react.html"

const model = initialRaw => {
  const initial = initialRaw.map((item, id) => ({...item, id}))
  const itemsAtom = Atom(initial)
  let counter = initial.length

  const addItem = title =>
    itemsAtom.modify(is => is.concat({id: counter++, title, isDone: false}))

  const setItem = ({id, title, isDone}) =>
    itemsAtom.modify(is =>
    is.map(i => i.id === id ? {id, title, isDone} : i))

  const remItem = ({id}) =>
    itemsAtom.modify(is => is.filter(i => i.id !== id))

  const toggleAll = () =>
    itemsAtom.modify(is =>
    is.map(is.some(i => !i.isDone)
           ? (i => ({...i, isDone: true}))
           : (i => ({...i, isDone: false}))))

  const clean = () => itemsAtom.modify(is => is.filter(i => !i.isDone))

  return {all: itemsAtom,
          active: itemsAtom.map(is => is.filter(i => !i.isDone)),
          completed: itemsAtom.map(is => is.filter(i => i.isDone)),
          addItem, setItem, remItem, toggleAll, clean}
}

const web = m => {
  const routes = [{hash: "#/",          items: m.all,       title: "All"},
                  {hash: "#/active",    items: m.active,    title: "Active"},
                  {hash: "#/completed", items: m.completed, title: "Completed"}]

  const routeAtom = Atom()
  window.onhashchange = () =>
    routeAtom.set(routes.find(r => r.hash === window.location.hash) || routes[0])
  window.onhashchange()

  const editingAtom = Atom(null)
  const newAtom = Atom("")

  const todos = (editing, items) =>
    items.map(({id, title, isDone}) =>

        <input className="toggle" type="checkbox" checked={isDone}
               onChange={() => m.setItem({id, title, isDone: !isDone})}/>
         editingAtom.set(id)}
               className="view">{title}
        <button className="destroy" onClick={() => m.remItem({id})}/>
        {editing !== id ? null : (() => {
          const textAtom = Atom(title)
          const exit = () => editingAtom.set(null)
          const save = () =>
            {const newTitle = textAtom.get().trim()
             exit()
             newTitle !== "" && m.setItem({id, title: newTitle, isDone})}
          return <InputValue didMount={c => c && c.focus()} type="text"
                   value={textAtom} className="edit" onBlur={save}
                   onKeyDown={e => e.which === 13 && save() ||
                                   e.which === 27 && exit()}/>})()}

)

  return

todos

        <InputValue type="text" value={newAtom} className="new-todo"
           placeholder="What needs to be done?"
           onKeyDown={e => {
             const t = newAtom.get().trim()
             e.which === 13 && t !== "" && m.addItem(t) && newAtom.set("")}}/>

        <Input className="toggle-all" onChange={m.toggleAll}
          type="checkbox" checked={m.active.map(a => a.length === 0)}/>

{routeAtom.flatMapLatest(
          ({items}) => Bacon.combineWith(editingAtom, items, todos))}

        {m.active.map(
          i => `${i.length} item${i.length === 1 ? "" : "s"}`)}

{routes.map(r =>

             classes(cr.hash === r.hash && "selected"))}
               onClick={() => window.location.hash = r.hash}>{r.title}

)
        }

        <Button className="clear-completed" onClick={m.clean}
                hidden={m.completed.map(c => c.length === 0)}>
          Clear completed</Button>

Double-click to edit a todo

}

const storeKey = "react.bacon-todos"

const m = model(JSON.parse(localStorage.getItem(storeKey) || "[]"))
m.all.onValue(is => localStorage.setItem(storeKey, JSON.stringify(is)))

ReactDOM.render(web(m), document.getElementById("app"))
<|endoftext|>


<commit_before>// Bing maps API: https://docs.microsoft.com/en-us/bingmaps/rest-services/

L.BingLayer = L.TileLayer.extend({
	options: {
		// imagerySet: https://docs.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata#template-parameters
		// supported:
		// - Aerial, AerialWithLabels (Deprecated), AerialWithLabelsOnDemand
		// - Road (Deprecated), RoadOnDemand
		// - CanvasDark, CanvasLight, CanvasGray
		// not supported: Birdseye*, Streetside
		type: 'RoadOnDemand',

		// https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes
		culture: '',

		// https://docs.microsoft.com/en-us/bingmaps/articles/custom-map-styles-in-bing-maps#custom-map-styles-in-the-rest-and-tile-services
		style: '',

		// https://blogs.bing.com/maps/2015/02/12/high-ppi-maps-now-available-in-the-bing-maps-ajax-control
		// not documented in REST API docs, but working
		// warning: deprecated imagery sets may not support some values (depending also on zoom level)
		retinaDpi: 'd2',

		attribution: 'Bing',
		minZoom: 1,
		maxZoom: 21
		// Actual `maxZoom` value may be less, depending on imagery set / coverage area
		// - 19~20 for all 'Aerial*'
		// - 20 for 'Road' (Deprecated)
	},

	initialize: function (key, options) {
		if (typeof key === 'object') {
			options = key;
			key = false;
		}
		L.TileLayer.prototype.initialize.call(this, null, options);

		if (key) { this.options.key = key; }
	},

	tile2quad: function (x, y, z) {
		var quad = '';
		for (var i = z; i > 0; i--) {
			var digit = 0;
			var mask = 1 << i - 1;
			if ((x & mask) !== 0) { digit += 1; }
			if ((y & mask) !== 0) { digit += 2; }
			quad = quad + digit;
		}
		return quad;
	},

	getTileUrl: function (coords) {
		var data = {
			subdomain: this._getSubdomain(coords),
			quadkey: this.tile2quad(coords.x, coords.y, this._getZoomForUrl()),
			culture: this.options.culture // compatibility for deprecated imagery sets ('Road' etc)
		};
		return L.Util.template(this._url, data);
	},

	loadMetadata: function () {
		if (this.metaRequested) { return; }
		this.metaRequested = true;
		var _this = this;
		var cbid = '_bing_metadata_' + L.Util.stamp(this);
		window[cbid] = function (meta) {
			window[cbid] = undefined;
			var e = document.getElementById(cbid);
			e.parentNode.removeChild(e);
			if (meta.errorDetails) {
				throw new Error(meta.errorDetails);
			}
			_this.initMetadata(meta);
		};
		var urlScheme = document.location.protocol === 'file:' ? 'http' :
			document.location.protocol.slice(0, -1);
		var url = urlScheme + '://dev.virtualearth.net/REST/v1/Imagery/Metadata/' + this.options.type;
		url += L.Util.getParamString({
			jsonp: cbid,
			UriScheme: urlScheme,
			include: 'ImageryProviders',
			key: this.options.key,
			culture: this.options.culture,
			style: this.options.style
		});
		var script = document.createElement('script');
		script.type = 'text/javascript';
		script.src = url;
		script.id = cbid;
		document.getElementsByTagName('head')[0].appendChild(script);
	},

	initMetadata: function (meta) {
		var options = this.options;
		var r = meta.resourceSets[0].resources[0];
		if (!r.imageUrl) { throw new Error('imageUrl not found in response'); }
		if (r.imageUrlSubdomains) { options.subdomains = r.imageUrlSubdomains; }
		this._url = r.imageUrl;
		this._providers = [];
		if (r.imageryProviders) {
			for (var i = 0; i < r.imageryProviders.length; i++) {
				var p = r.imageryProviders[i];
				for (var j = 0; j < p.coverageAreas.length; j++) {
					var c = p.coverageAreas[j];
					var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false};
					var bounds = L.latLngBounds(
							[c.bbox[0]+0.01, c.bbox[1]+0.01],
							[c.bbox[2]-0.01, c.bbox[3]-0.01]
					);
					coverage.bounds = bounds;
					coverage.attrib = p.attribution;
					this._providers.push(coverage);
				}
			}
		}
		if (options.retinaDpi && options.detectRetina && options.zoomOffset) {
			this._url += '&dpi=' + options.retinaDpi;
		}
		this._update();
	},

	_update: function (center) {
		if (!this._url || !this._map) { return; }
		this._update_attribution();
		L.GridLayer.prototype._update.call(this, center);
	},

	_update_attribution: function () {
		var bounds = this._map.getBounds();
		bounds = L.latLngBounds(bounds.getSouthWest().wrap(), bounds.getNorthEast().wrap());
		var zoom = this._getZoomForUrl();
		for (var i = 0; i < this._providers.length; i++) {
			var p = this._providers[i];
			if ((zoom <= p.zoomMax && zoom >= p.zoomMin) &&
					bounds.intersects(p.bounds)) {
				if (!p.active && this._map.attributionControl)
					this._map.attributionControl.addAttribution(p.attrib);
				p.active = true;
			} else {
				if (p.active && this._map.attributionControl)
					this._map.attributionControl.removeAttribution(p.attrib);
				p.active = false;
			}
		}
	},

	onAdd: function (map) {
		// Note: Metadata could be loaded earlier, on layer initialize,
		//       but according to docs even such request is billable:
		//       https://docs.microsoft.com/en-us/bingmaps/getting-started/bing-maps-dev-center-help/understanding-bing-maps-transactions#rest-services
		//       That's why it's important to defer it till BingLayer is actually added to map
		this.loadMetadata();
		L.GridLayer.prototype.onAdd.call(this, map);
	},

	onRemove: function (map) {
		for (var i = 0; i < this._providers.length; i++) {
			var p = this._providers[i];
			if (p.active && this._map.attributionControl) {
				this._map.attributionControl.removeAttribution(p.attrib);
				p.active = false;
			}
		}
		L.GridLayer.prototype.onRemove.call(this, map);
	}
});

L.bingLayer = function (key, options) {
	return new L.BingLayer(key, options);
};
<commit_msg>L.BingLayer: refactor: separate callRestService from loadMetadata<commit_after>// Bing maps API: https://docs.microsoft.com/en-us/bingmaps/rest-services/

L.BingLayer = L.TileLayer.extend({
	options: {
		// imagerySet: https://docs.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata#template-parameters
		// supported:
		// - Aerial, AerialWithLabels (Deprecated), AerialWithLabelsOnDemand
		// - Road (Deprecated), RoadOnDemand
		// - CanvasDark, CanvasLight, CanvasGray
		// not supported: Birdseye*, Streetside
		type: 'RoadOnDemand',

		// https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes
		culture: '',

		// https://docs.microsoft.com/en-us/bingmaps/articles/custom-map-styles-in-bing-maps#custom-map-styles-in-the-rest-and-tile-services
		style: '',

		// https://blogs.bing.com/maps/2015/02/12/high-ppi-maps-now-available-in-the-bing-maps-ajax-control
		// not documented in REST API docs, but working
		// warning: deprecated imagery sets may not support some values (depending also on zoom level)
		retinaDpi: 'd2',

		attribution: 'Bing',
		minZoom: 1,
		maxZoom: 21
		// Actual `maxZoom` value may be less, depending on imagery set / coverage area
		// - 19~20 for all 'Aerial*'
		// - 20 for 'Road' (Deprecated)
	},

	initialize: function (key, options) {
		if (typeof key === 'object') {
			options = key;
			key = false;
		}
		L.TileLayer.prototype.initialize.call(this, null, options);

		if (key) { this.options.key = key; }
	},

	tile2quad: function (x, y, z) {
		var quad = '';
		for (var i = z; i > 0; i--) {
			var digit = 0;
			var mask = 1 << i - 1;
			if ((x & mask) !== 0) { digit += 1; }
			if ((y & mask) !== 0) { digit += 2; }
			quad = quad + digit;
		}
		return quad;
	},

	getTileUrl: function (coords) {
		var data = {
			subdomain: this._getSubdomain(coords),
			quadkey: this.tile2quad(coords.x, coords.y, this._getZoomForUrl()),
			culture: this.options.culture // compatibility for deprecated imagery sets ('Road' etc)
		};
		return L.Util.template(this._url, data);
	},

	callRestService: function (request, callback, context) {
		context = context || this;
		var uniqueName = '_bing_metadata_' + L.Util.stamp(this);
		while (window[uniqueName]) { uniqueName += '_'; }
		request += '&jsonp=' + uniqueName;
		var script = document.createElement('script');
		script.setAttribute('type', 'text/javascript');
		script.setAttribute('src', request);
		window[uniqueName] = function (response) {
			delete window[uniqueName];
			script.remove();
			if (response.errorDetails) {
				throw new Error(response.errorDetails);
			}
			callback.call(context, response);
		};
		document.body.appendChild(script);
	},

	loadMetadata: function () {
		if (this.metaRequested) { return; }
		this.metaRequested = true;
		var urlScheme = document.location.protocol === 'file:' ? 'http' :
			document.location.protocol.slice(0, -1);
		var url = urlScheme + '://dev.virtualearth.net/REST/v1/Imagery/Metadata/' + this.options.type;
		url += L.Util.getParamString({
			UriScheme: urlScheme,
			include: 'ImageryProviders',
			key: this.options.key,
			culture: this.options.culture,
			style: this.options.style
		});
		this.callRestService(url, this.initMetadata);
	},

	initMetadata: function (meta) {
		var options = this.options;
		var r = meta.resourceSets[0].resources[0];
		if (!r.imageUrl) { throw new Error('imageUrl not found in response'); }
		if (r.imageUrlSubdomains) { options.subdomains = r.imageUrlSubdomains; }
		this._url = r.imageUrl;
		this._providers = [];
		if (r.imageryProviders) {
			for (var i = 0; i < r.imageryProviders.length; i++) {
				var p = r.imageryProviders[i];
				for (var j = 0; j < p.coverageAreas.length; j++) {
					var c = p.coverageAreas[j];
					var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false};
					var bounds = L.latLngBounds(
							[c.bbox[0]+0.01, c.bbox[1]+0.01],
							[c.bbox[2]-0.01, c.bbox[3]-0.01]
					);
					coverage.bounds = bounds;
					coverage.attrib = p.attribution;
					this._providers.push(coverage);
				}
			}
		}
		if (options.retinaDpi && options.detectRetina && options.zoomOffset) {
			this._url += '&dpi=' + options.retinaDpi;
		}
		this._update();
	},

	_update: function (center) {
		if (!this._url || !this._map) { return; }
		this._update_attribution();
		L.GridLayer.prototype._update.call(this, center);
	},

	_update_attribution: function () {
		var bounds = this._map.getBounds();
		bounds = L.latLngBounds(bounds.getSouthWest().wrap(), bounds.getNorthEast().wrap());
		var zoom = this._getZoomForUrl();
		for (var i = 0; i < this._providers.length; i++) {
			var p = this._providers[i];
			if ((zoom <= p.zoomMax && zoom >= p.zoomMin) &&
					bounds.intersects(p.bounds)) {
				if (!p.active && this._map.attributionControl)
					this._map.attributionControl.addAttribution(p.attrib);
				p.active = true;
			} else {
				if (p.active && this._map.attributionControl)
					this._map.attributionControl.removeAttribution(p.attrib);
				p.active = false;
			}
		}
	},

	onAdd: function (map) {
		// Note: Metadata could be loaded earlier, on layer initialize,
		//       but according to docs even such request is billable:
		//       https://docs.microsoft.com/en-us/bingmaps/getting-started/bing-maps-dev-center-help/understanding-bing-maps-transactions#rest-services
		//       That's why it's important to defer it till BingLayer is actually added to map
		this.loadMetadata();
		L.GridLayer.prototype.onAdd.call(this, map);
	},

	onRemove: function (map) {
		for (var i = 0; i < this._providers.length; i++) {
			var p = this._providers[i];
			if (p.active && this._map.attributionControl) {
				this._map.attributionControl.removeAttribution(p.attrib);
				p.active = false;
			}
		}
		L.GridLayer.prototype.onRemove.call(this, map);
	}
});

L.bingLayer = function (key, options) {
	return new L.BingLayer(key, options);
};
<|endoftext|>


<commit_before>"use strict"

const EventEmitter = require("events");
const PassThrough = require("stream").PassThrough;
const Sequence = require("./Sequence");
const Link = require("./Link");
const cache_ns = "s:";

module.exports = (scope, sequence_id, options = {}) => {

    let role = (options.session ? options.session.role : null) || scope.env.role;
    let stream = scope.cache.get(cache_ns + sequence_id);
    if (!stream) {
        stream = Stream(scope, sequence_id, options.objectMode);
        scope.cache.set(cache_ns + sequence_id, stream);
        Sequence(scope, sequence_id, role, stream);
    }

    return stream;
};

function Stream (scope, sequence_id, objectMode = true) {

    const stream = new EventEmitter();
    stream.readable = true;
    stream.writable = true;
    stream.objectMode = objectMode;
    stream.id = sequence_id;

    stream._i = new PassThrough({objectMode: objectMode});
    stream._i.on("unpipe", () => stream.end());
    stream._i.on("error", error => stream.emit("error", error));
    stream._i.cork();

    stream.write = chunk => {
        stream._chunk = chunk;
        !stream.ended && stream._i.write(chunk);
    };

    stream.end = chunk => {
		scope.cache.del(cache_ns + sequence_id);
        stream.ready ? stream._i.end(chunk) : stream.once("ready", () => stream._i.end(chunk));
        stream.ended = true;
        stream.readable = false;
        stream.writable = false;
	};

    stream.cork = () => stream._i.cork();
    stream.uncork = () => stream._i.uncork();

    stream.unpipe = dest => stream._i.unpipe(dest);
    stream.pipe = dest => {return stream._i.pipe(dest)};

    return stream;
}
<commit_msg>remove obsolete require<commit_after>"use strict"

const EventEmitter = require("events");
const PassThrough = require("stream").PassThrough;
const Sequence = require("./Sequence");
const cache_ns = "s:";

module.exports = (scope, sequence_id, options = {}) => {

    let role = (options.session ? options.session.role : null) || scope.env.role;
    let stream = scope.cache.get(cache_ns + sequence_id);
    if (!stream) {
        stream = Stream(scope, sequence_id, options.objectMode);
        scope.cache.set(cache_ns + sequence_id, stream);
        Sequence(scope, sequence_id, role, stream);
    }

    return stream;
};

function Stream (scope, sequence_id, objectMode = true) {

    const stream = new EventEmitter();
    stream.readable = true;
    stream.writable = true;
    stream.objectMode = objectMode;
    stream.id = sequence_id;

    stream._i = new PassThrough({objectMode: objectMode});
    stream._i.on("unpipe", () => stream.end());
    stream._i.on("error", error => stream.emit("error", error));
    stream._i.cork();

    stream.write = chunk => {
        stream._chunk = chunk;
        !stream.ended && stream._i.write(chunk);
    };

    stream.end = chunk => {
		scope.cache.del(cache_ns + sequence_id);
        stream.ready ? stream._i.end(chunk) : stream.once("ready", () => stream._i.end(chunk));
        stream.ended = true;
        stream.readable = false;
        stream.writable = false;
	};

    stream.cork = () => stream._i.cork();
    stream.uncork = () => stream._i.uncork();

    stream.unpipe = dest => stream._i.unpipe(dest);
    stream.pipe = dest => {return stream._i.pipe(dest)};

    return stream;
}
<|endoftext|>


<commit_before>/*
 * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of fiware-iotagent-lib
 *
 * fiware-iotagent-lib is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * fiware-iotagent-lib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with fiware-iotagent-lib.
 * If not, see http://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::daniel.moranjimenez@telefonica.com
 *
 * Modified by: Daniel Calvo - ATOS Research & Innovation
 */

/* eslint-disable no-prototype-builtins */

const logger = require('logops');
const errors = require('../../errors');
const constants = require('../../constants');
const intoTrans = require('../common/domain').intoTrans;
const revalidator = require('revalidator');
const moment = require('moment');
const context = {
    op: 'IoTAgentNGSI.RestUtils'
};
const _ = require('underscore');
const request = require('request');
const async = require('async');
const apply = async.apply;
const ngsiService = require('../ngsi/ngsiService');
const config = require('../../commonConfig');

/**
 * Checks all the mandatory attributes in the selected array are present in the presented body object.
 *
 * @param {Array}  mandatoryAttributes      List of the names of the attributes that must be present in the body.
 * @param {Object} body                     Body whose attributes are going to be checked.
 */
function checkMandatoryQueryParams(mandatoryAttributes, body, callback) {
    const missing = [];

    for (const p in mandatoryAttributes) {
        let found = false;

        for (const i in body) {
            if (body.hasOwnProperty(i)) {
                if (i === mandatoryAttributes[p]) {
                    found = true;
                }
            }
        }

        if (!found) {
            missing.push(mandatoryAttributes[p]);
        }
    }

    if (missing.length !== 0) {
        const error = new errors.MissingAttributes('Missing attributes: ' + JSON.stringify(missing));
        error.code = '400';

        callback(error);
    } else {
        callback(null, body);
    }
}

/**
 * Generates a Express middleware that checks for the pressence of all the mandatory attributes of a certain kind
 * (headers, query params, and so), returning a BAD_REQUEST error if any one is not found. The kind of attribute
 * to check can be specified with the 'attribute' parameter.
 *
 * @param {Array} attribute                     Request attribute against which the list will be matched.
 * @param {Array} mandatoryAttributes           List of mandatory headers.
 * @return {Function}                           The generated middleware.
 */
function checkRequestAttributes(attribute, mandatoryAttributes) {
    return function headerChecker(req, res, next) {
        const headerKeys = _.keys(req[attribute]);
        const missing = [];

        for (let i = 0; i < mandatoryAttributes.length; i++) {
            if (headerKeys.indexOf(mandatoryAttributes[i]) < 0) {
                missing.push(mandatoryAttributes[i]);
            }
        }

        if (missing.length !== 0) {
            next(new errors.MissingHeaders(JSON.stringify(missing)));
        } else {
            next();
        }
    };
}

/**
 * Generates a middleware that checks the request body against the given revalidator template.
 *
 * @param {Object} template         Loaded JSON Scheam Template.
 * @return {Function}               Express middleware that checks the validity of the body.
 */
function checkBody(template) {
    return function bodyMiddleware(req, res, next) {
        const errorList = revalidator.validate(req.body, template);

        if (errorList.valid) {
            next();
        } else {
            logger.debug(context, 'Errors found validating request: %j', errorList);
            next(new errors.WrongSyntax('Errors found validating request.'));
        }
    };
}

/**
 * Checks if the timestamp properties of NGSIv2 entities are valid ISO8601 dates.
 *
 * @param {Object} payload         NGSIv2 payload to be analyzed.
 * @return {Boolean}               true if timestamp attributes are valid ISO8601. false if not.
 */
function IsValidTimestampedNgsi2(payload) {
    function isValidTimestampedNgsi2Entity(entity) {
        for (const i in entity) {
            if (entity.hasOwnProperty(i)) {
                if (i === constants.TIMESTAMP_ATTRIBUTE && !moment(entity[i].value, moment.ISO_8601).isValid()) {
                    return false;
                }
            }
        }

        return true;
    }

    if (payload instanceof Array) {
        for (let i = 0; i < payload.length; i++) {
            if (!isValidTimestampedNgsi2Entity(payload[i])) {
                return false;
            }
        }

        return true;
    }
    return isValidTimestampedNgsi2Entity(payload);
}

/**
 * Checks if timestamp attributes are included in NGSIv2 entities.
 *
 * @param {Object} payload         NGSIv2 payload to be analyzed.
 * @return {Boolean}               true if timestamp attributes are included. false if not.
 */
function isTimestampedNgsi2(payload) {
    function isTimestampedNgsi2Entity(entity) {
        for (const i in entity) {
            if (entity.hasOwnProperty(i)) {
                if (i === constants.TIMESTAMP_ATTRIBUTE) {
                    return true;
                }
            }
        }

        return false;
    }

    if (payload instanceof Array) {
        for (let i = 0; i < payload.length; i++) {
            if (!isTimestampedNgsi2Entity(payload[i])) {
                return false;
            }
        }

        return true;
    }
    return isTimestampedNgsi2Entity(payload);
}

/**
 * Executes a request operation using security information if available
 *
 * @param {String} requestOptions   Request options to be sent.
 * @param {String} deviceData       Device data.
 */
function executeWithSecurity(requestOptions, deviceData, callback) {
    logger.debug(context, 'executeWithSecurity');
    config.getGroupRegistry().getType(deviceData.type, function (error, deviceGroup) {
        let typeInformation;
        if (error) {
            logger.debug(context, 'error %j in get group device', error);
        }

        if (deviceGroup) {
            typeInformation = deviceGroup;
        } else {
            typeInformation = config.getConfig().types[deviceData.type];
        }

        if (config.getConfig().authentication && config.getConfig().authentication.enabled) {
            const security = config.getSecurityService();
            if (typeInformation && typeInformation.trust) {
                async.waterfall(
                    [
                        apply(security.auth, typeInformation.trust),
                        apply(ngsiService.updateTrust, deviceGroup, null, typeInformation.trust),
                        apply(security.getToken, typeInformation.trust)
                    ],
                    function (error, token) {
                        if (error) {
                            callback(new errors.SecurityInformationMissing(typeInformation.type));
                        } else {
                            requestOptions.headers[config.getConfig().authentication.header] = token;
                            request(requestOptions, callback);
                        }
                    }
                );
            } else {
                callback(
                    new errors.SecurityInformationMissing(typeInformation ? typeInformation.type : deviceData.type)
                );
            }
        } else {
            request(requestOptions, callback);
        }
    });
}

exports.executeWithSecurity = executeWithSecurity;
exports.checkMandatoryQueryParams = intoTrans(context, checkMandatoryQueryParams);
exports.checkRequestAttributes = intoTrans(context, checkRequestAttributes);
exports.checkBody = intoTrans(context, checkBody);
exports.isTimestamped = isTimestamped;
exports.IsValidTimestamped = IsValidTimestamped;
exports.isTimestampedNgsi2 = isTimestampedNgsi2;
exports.IsValidTimestampedNgsi2 = IsValidTimestampedNgsi2;
<commit_msg>Remove exports<commit_after>/*
 * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of fiware-iotagent-lib
 *
 * fiware-iotagent-lib is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * fiware-iotagent-lib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with fiware-iotagent-lib.
 * If not, see http://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::daniel.moranjimenez@telefonica.com
 *
 * Modified by: Daniel Calvo - ATOS Research & Innovation
 */

/* eslint-disable no-prototype-builtins */

const logger = require('logops');
const errors = require('../../errors');
const constants = require('../../constants');
const intoTrans = require('../common/domain').intoTrans;
const revalidator = require('revalidator');
const moment = require('moment');
const context = {
    op: 'IoTAgentNGSI.RestUtils'
};
const _ = require('underscore');
const request = require('request');
const async = require('async');
const apply = async.apply;
const ngsiService = require('../ngsi/ngsiService');
const config = require('../../commonConfig');

/**
 * Checks all the mandatory attributes in the selected array are present in the presented body object.
 *
 * @param {Array}  mandatoryAttributes      List of the names of the attributes that must be present in the body.
 * @param {Object} body                     Body whose attributes are going to be checked.
 */
function checkMandatoryQueryParams(mandatoryAttributes, body, callback) {
    const missing = [];

    for (const p in mandatoryAttributes) {
        let found = false;

        for (const i in body) {
            if (body.hasOwnProperty(i)) {
                if (i === mandatoryAttributes[p]) {
                    found = true;
                }
            }
        }

        if (!found) {
            missing.push(mandatoryAttributes[p]);
        }
    }

    if (missing.length !== 0) {
        const error = new errors.MissingAttributes('Missing attributes: ' + JSON.stringify(missing));
        error.code = '400';

        callback(error);
    } else {
        callback(null, body);
    }
}

/**
 * Generates a Express middleware that checks for the pressence of all the mandatory attributes of a certain kind
 * (headers, query params, and so), returning a BAD_REQUEST error if any one is not found. The kind of attribute
 * to check can be specified with the 'attribute' parameter.
 *
 * @param {Array} attribute                     Request attribute against which the list will be matched.
 * @param {Array} mandatoryAttributes           List of mandatory headers.
 * @return {Function}                           The generated middleware.
 */
function checkRequestAttributes(attribute, mandatoryAttributes) {
    return function headerChecker(req, res, next) {
        const headerKeys = _.keys(req[attribute]);
        const missing = [];

        for (let i = 0; i < mandatoryAttributes.length; i++) {
            if (headerKeys.indexOf(mandatoryAttributes[i]) < 0) {
                missing.push(mandatoryAttributes[i]);
            }
        }

        if (missing.length !== 0) {
            next(new errors.MissingHeaders(JSON.stringify(missing)));
        } else {
            next();
        }
    };
}

/**
 * Generates a middleware that checks the request body against the given revalidator template.
 *
 * @param {Object} template         Loaded JSON Scheam Template.
 * @return {Function}               Express middleware that checks the validity of the body.
 */
function checkBody(template) {
    return function bodyMiddleware(req, res, next) {
        const errorList = revalidator.validate(req.body, template);

        if (errorList.valid) {
            next();
        } else {
            logger.debug(context, 'Errors found validating request: %j', errorList);
            next(new errors.WrongSyntax('Errors found validating request.'));
        }
    };
}

/**
 * Checks if the timestamp properties of NGSIv2 entities are valid ISO8601 dates.
 *
 * @param {Object} payload         NGSIv2 payload to be analyzed.
 * @return {Boolean}               true if timestamp attributes are valid ISO8601. false if not.
 */
function IsValidTimestampedNgsi2(payload) {
    function isValidTimestampedNgsi2Entity(entity) {
        for (const i in entity) {
            if (entity.hasOwnProperty(i)) {
                if (i === constants.TIMESTAMP_ATTRIBUTE && !moment(entity[i].value, moment.ISO_8601).isValid()) {
                    return false;
                }
            }
        }

        return true;
    }

    if (payload instanceof Array) {
        for (let i = 0; i < payload.length; i++) {
            if (!isValidTimestampedNgsi2Entity(payload[i])) {
                return false;
            }
        }

        return true;
    }
    return isValidTimestampedNgsi2Entity(payload);
}

/**
 * Checks if timestamp attributes are included in NGSIv2 entities.
 *
 * @param {Object} payload         NGSIv2 payload to be analyzed.
 * @return {Boolean}               true if timestamp attributes are included. false if not.
 */
function isTimestampedNgsi2(payload) {
    function isTimestampedNgsi2Entity(entity) {
        for (const i in entity) {
            if (entity.hasOwnProperty(i)) {
                if (i === constants.TIMESTAMP_ATTRIBUTE) {
                    return true;
                }
            }
        }

        return false;
    }

    if (payload instanceof Array) {
        for (let i = 0; i < payload.length; i++) {
            if (!isTimestampedNgsi2Entity(payload[i])) {
                return false;
            }
        }

        return true;
    }
    return isTimestampedNgsi2Entity(payload);
}

/**
 * Executes a request operation using security information if available
 *
 * @param {String} requestOptions   Request options to be sent.
 * @param {String} deviceData       Device data.
 */
function executeWithSecurity(requestOptions, deviceData, callback) {
    logger.debug(context, 'executeWithSecurity');
    config.getGroupRegistry().getType(deviceData.type, function (error, deviceGroup) {
        let typeInformation;
        if (error) {
            logger.debug(context, 'error %j in get group device', error);
        }

        if (deviceGroup) {
            typeInformation = deviceGroup;
        } else {
            typeInformation = config.getConfig().types[deviceData.type];
        }

        if (config.getConfig().authentication && config.getConfig().authentication.enabled) {
            const security = config.getSecurityService();
            if (typeInformation && typeInformation.trust) {
                async.waterfall(
                    [
                        apply(security.auth, typeInformation.trust),
                        apply(ngsiService.updateTrust, deviceGroup, null, typeInformation.trust),
                        apply(security.getToken, typeInformation.trust)
                    ],
                    function (error, token) {
                        if (error) {
                            callback(new errors.SecurityInformationMissing(typeInformation.type));
                        } else {
                            requestOptions.headers[config.getConfig().authentication.header] = token;
                            request(requestOptions, callback);
                        }
                    }
                );
            } else {
                callback(
                    new errors.SecurityInformationMissing(typeInformation ? typeInformation.type : deviceData.type)
                );
            }
        } else {
            request(requestOptions, callback);
        }
    });
}

exports.executeWithSecurity = executeWithSecurity;
exports.checkMandatoryQueryParams = intoTrans(context, checkMandatoryQueryParams);
exports.checkRequestAttributes = intoTrans(context, checkRequestAttributes);
exports.checkBody = intoTrans(context, checkBody);
exports.isTimestampedNgsi2 = isTimestampedNgsi2;
exports.IsValidTimestampedNgsi2 = IsValidTimestampedNgsi2;
<|endoftext|>


<commit_before>// Karma configuration
// Generated on Wed Jul 02 2014 11:05:24 GMT-0300 (BRT)

var dotenv = require('dotenv');
var path = require('path');
var versionify = require("browserify-versionify");
var webpack = require('webpack');

dotenv.load();
var exec = require('child_process').exec
var istanbul = require('browserify-istanbul');
exec('gulp pre-build')

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha', 'chai-sinon', 'jquery-2.1.0'],

    // list of files / patterns to load in the browser
    files: [
      'test/**/*.js',
    ],

    // list of files to exclude
    exclude: [
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
      'test/**/*.js': ['webpack'],
    },

    coverageReporter: {
      reporters: [
        {type: 'lcovonly'},
        {type: 'text-summary'}
      ]
    },
    plugins: [
        require('karma-webpack'),
        require('karma-mocha'),
        'karma-chrome-launcher',
        'karma-chai-sinon',
        'karma-jquery',
        'karma-coverage',
    ],

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress', 'coverage'],

    webpack: {
        plugins: [
          new webpack.DefinePlugin({
            VERSION: JSON.stringify(require('./package.json').version)
          })
        ],
        module: {
            loaders: [
            {
                test: /\.js$/,
                loader: 'babel',
            },
            {
                test: /\.scss$/,
                loaders: ['css', 'sass?includePaths[]='
                    + path.resolve(__dirname, './node_modules/compass-mixins/lib')
                    + '&includePaths[]='
                    + path.resolve(__dirname, './src/base/scss')
                ],
                include: path.resolve(__dirname, 'src'),
            },
            {
                test: /\.(png|woff|eot|ttf|swf)/, loader: 'url-loader?limit=1'
            },
            {
                test: /\.svg/, loader: 'file-loader'
            },
            {
                test: /\.html/, loader: 'html?minimize=false'
            }
            ],
            postLoaders: [ { // << add subject as webpack's postloader
                test: /\.js$/,
                exclude: /(test|node_modules|bower_components)\//,
                loader: 'istanbul-instrumenter'
            } ]
        },
        resolve: {
            root: path.resolve(__dirname, 'src'),
            extensions: ['', '.js'],
        },

    },

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    //browsers: ['Chrome', 'Firefox', 'Safari'],
    browsers: ['Chrome'],
    // to avoid DISCONNECTED messages
    browserDisconnectTimeout : 10000, // default 2000
    browserDisconnectTolerance : 1, // default 0
    browserNoActivityTimeout : 600000, //default 10000

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
  });
};
<commit_msg>karma: fix test configuration file<commit_after>// Karma configuration
// Generated on Wed Jul 02 2014 11:05:24 GMT-0300 (BRT)

var dotenv = require('dotenv');
var path = require('path');
var webpack = require('webpack');

dotenv.load();

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha', 'chai-sinon', 'jquery-2.1.0'],

    // list of files / patterns to load in the browser
    files: [
      'test/**/*.js',
    ],

    // list of files to exclude
    exclude: [
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
      'test/**/*.js': ['webpack'],
    },

    coverageReporter: {
      reporters: [
        {type: 'lcovonly'},
        {type: 'text-summary'}
      ]
    },
    plugins: [
        require('karma-webpack'),
        require('karma-mocha'),
        'karma-chrome-launcher',
        'karma-chai-sinon',
        'karma-jquery',
        'karma-coverage',
    ],

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress', 'coverage'],

    webpack: {
        plugins: [
          new webpack.DefinePlugin({
            VERSION: JSON.stringify(require('./package.json').version)
          })
        ],
        module: {
            loaders: [
            {
                test: /\.js$/,
                loader: 'babel',
            },
            {
                test: /\.scss$/,
                loaders: ['css', 'sass?includePaths[]='
                    + path.resolve(__dirname, './node_modules/compass-mixins/lib')
                    + '&includePaths[]='
                    + path.resolve(__dirname, './src/base/scss')
                ],
                include: path.resolve(__dirname, 'src'),
            },
            {
                test: /\.(png|woff|eot|ttf|swf)/, loader: 'url-loader?limit=1'
            },
            {
                test: /\.svg/, loader: 'file-loader'
            },
            {
                test: /\.html/, loader: 'html?minimize=false'
            }
            ],
            postLoaders: [ { // << add subject as webpack's postloader
                test: /\.js$/,
                exclude: /(test|node_modules|bower_components)\//,
                loader: 'istanbul-instrumenter'
            } ]
        },
        resolve: {
            root: path.resolve(__dirname, 'src'),
            extensions: ['', '.js'],
        },

    },

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    //browsers: ['Chrome', 'Firefox', 'Safari'],
    browsers: ['Chrome'],
    // to avoid DISCONNECTED messages
    browserDisconnectTimeout : 10000, // default 2000
    browserDisconnectTolerance : 1, // default 0
    browserNoActivityTimeout : 600000, //default 10000

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
  });
};
<|endoftext|>


<commit_before>'use strict';

var geojsonhint = require('geojsonhint');

var geoJSONType = /^((Multi)?Point|LineString|Polygon)$/;

function normalizeGeoJsonGeometry(geom) {
  if (!geom || !geom.type || !Array.isArray(geom.coordinates)) {
    return null;
  }

  if (!geom.type.match(geoJSONType)) {
    return null;
  }

  var errors = geojsonhint.hint(geom);
  if (errors.length > 0) {
    return null;
  }

  var result = {
    type: geom.type,
    coordinates: geom.coordinates
  };

  // add crs to result if available
  if (geom.crs) {
    result.crs = geom.crs;
  }

  return result;
}

exports.normalizeGeoJsonGeometry = normalizeGeoJsonGeometry;
<commit_msg>More logging<commit_after>'use strict';

var geojsonhint = require('geojsonhint');

var geoJSONType = /^((Multi)?Point|LineString|Polygon)$/;

function normalizeGeoJsonGeometry(geom) {
  if (!geom || !geom.type || !Array.isArray(geom.coordinates)) {
    return null;
  }

  if (!geom.type.match(geoJSONType)) {
    console.error('Invalid geometry type', geoJSONType);
    return null;
  }

  var errors = geojsonhint.hint(geom);
  if (errors.length > 0) {
    console.error('Found errors in geometry', errors);
    return null;
  }

  var result = {
    type: geom.type,
    coordinates: geom.coordinates
  };

  // add crs to result if available
  if (geom.crs) {
    result.crs = geom.crs;
  }

  return result;
}

exports.normalizeGeoJsonGeometry = normalizeGeoJsonGeometry;
<|endoftext|>


<commit_before>/* jshint esversion:6, node: true */

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const request = require('request');
const app = express();

app.set('port', (process.env.PORT || 5000));

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}));

// parse application/json
app.use(bodyParser.json());

// index
app.get('/', function (req, res) {
	res.send('hello world i am a secret bot');
});

// for facebook verification
app.get('/webhook/', function (req, res) {
	if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') {
		res.send(req.query['hub.challenge']);
	} else {
		res.send('Error, wrong token');
	}
});

// to post data
app.post('/webhook/', function (req, res) {
	var results;

	let messaging_events = req.body.entry[0].messaging;
	for (let i = 0; i < messaging_events.length; i++) {
		let event = req.body.entry[0].messaging[i];
		let sender = event.sender.id;
		//requestSearchType(sender);

		if (event.message && event.message.text) {
			let text = event.message.text;
			if (text == 'Generic') {
				sendGenericMessage(sender);
				continue;
			}
			else if(text == 'hi' || text =='hey'){
				requestSearchType(sender);
				continue;
			}
			else if (text.includes("product: ")){
				var headers = {
				    'Authorization': 'bearer fd887ce5-7b15-4c1e-93a0-dc91ce883ec6'
				};

				//Extract the search key
				var str = text.replace('product: ',' ');
				var url = 'https://www.loblaws.ca/ecommerce/v2/loblaw/products/search?q=';
				url = url.concat(str, '&page=0&pageSize=10&sort=recommended&filters=category:LSL001006000000');

				var options = {
				    url: url,
				    headers: headers
				};
				function callback(error, response, body) {
				    if (!error && response.statusCode == 200) {
				        //console.log(body);
				        var resp = JSON.parse(body);
				        results = resp["searchResults"];
				        console.log(results[0]);
				        console.log(results.products[0].productName);

				        sendProductMessage(sender, results);
				    }
				}

				request(options, callback);

			}
			sendTextMessage(sender, "echo: " + text.substring(0, 200));
		}
		if (event.postback) {
			switch(JSON.stringify(postback.payload)){
				case "searchProduct":
					sendTextMessage(sender, "Search by product", token);
					break;
				case "searchRecipe":
					sendTextMessage(sender, "Search by recipe", token);
					break;
				case "searchBarcode":
					sendTextMessage(sender, "Search by barcode", token);
					break;
			}
			continue;
		}
	}
	res.sendStatus(200);
});

//Request for product, image, or recipe search
function requestSearchType(sender){
	let messageData = {
		"attachment": {
			"type": "template",
			"payload":{
		        "template_type":"button",
		        "text":"What do you want to do next?",
		        "buttons":[
		          {
			        "type":"postback",
			        "title":"By Product",
			        "payload":"searchProduct",
			      },
			      {
			        "type":"postback",
			        "title":"By Recipe",
			        "payload":"searchRecipe",
			      },
			      {
			      	"type":"postback",
			      	"title": "By Barcode",
			      	"payload": "searchBarcode"
			      }
        		]
      		}
		}
	};

	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: sender,
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

// recommended to inject access tokens as environmental variables, e.g.
const token = process.env.PAGE_ACCESS_TOKEN;
//const token = "<PAGE_ACCESS_TOKEN>"

function sendTextMessage(sender, text) {
	let messageData = { text:text };

	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

function sendGenericMessage(sender) {
	let messageData = {
		"attachment": {
			"type": "template",
			"payload": {
				"template_type": "generic",
				"elements": [{
					"title": "First card",
					"subtitle": "Element #1 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/rift.png",
					"buttons": [{
						"type": "web_url",
						"url": "https://www.messenger.com",
						"title": "web url"
					}, {
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for first element in a generic bubble",
					}],
				}, {
					"title": "Second card",
					"subtitle": "Element #2 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/gearvr.png",
					"buttons": [{
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for second element in a generic bubble",
					}],
				}]
			}
		}
	};

	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

/*
	* Results is the api response
	* Makes a carouel displaying tile, description, and image
*/
function sendProductMessage(sender, results) {
	let messageData = {
		"attachment": {
			"type": "template",
			"payload": {
				"template_type": "generic",
				"elements": loadElements(results.products) //Load the products into an object that messenger api can handle
				/*"elements": [{
					"title": results.products[0].productName,
					"subtitle": results.products[0].description,
					"image_url": results.products[0].primaryImage,
					"buttons": [{
						"type": "web_url",
						"url": "https://www.messenger.com",
						"title": "web url"
					}, {
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for first element in a generic bubble",
					}],
				}, {
					"title": "Second card",
					"subtitle": "Element #2 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/gearvr.png",
					"buttons": [{
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for second element in a generic bubble",
					}],
				}] */
			}
		}
	};

	//Send message
	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

/*
	* Load products into an array that can be handled by messenger api
	* Makes carousel as long as number of responses
*/
function loadElements(products){
	var elements = []; //Create blank array

	console.log("Length: " + products.length);

	for (var i = 0; i < products.length; i++){
		var element = {
			"title": products[i].productName, //Sets title
			"subtitle": products[i].description, //Sets description
			"image_url": products[i].primaryImage, //Sets image
		}

		elements.push(element); //Add the element to the array
	}

	return elements;
}

// spin spin sugar
app.listen(app.get('port'), function() {
	console.log('running on port', app.get('port'));
});
<commit_msg>Product search<commit_after>/* jshint esversion:6, node: true */

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const request = require('request');
const app = express();

app.set('port', (process.env.PORT || 5000));

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}));

// parse application/json
app.use(bodyParser.json());

// index
app.get('/', function (req, res) {
	res.send('hello world i am a secret bot');
});

// for facebook verification
app.get('/webhook/', function (req, res) {
	if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') {
		res.send(req.query['hub.challenge']);
	} else {
		res.send('Error, wrong token');
	}
});

// to post data
app.post('/webhook/', function (req, res) {
	var results;

	let messaging_events = req.body.entry[0].messaging;
	for (let i = 0; i < messaging_events.length; i++) {
		let event = req.body.entry[0].messaging[i];
		let sender = event.sender.id;
		//requestSearchType(sender);

		if (event.message && event.message.text) {
			let text = event.message.text;
			if (text == 'Generic') {
				sendGenericMessage(sender);
				continue;
			}
			else if(text == 'hi' || text =='hey'){
				requestSearchType(sender);
				continue;
			}
			else if (text.includes("product: ")){
				var headers = {
				    'Authorization': 'bearer fd887ce5-7b15-4c1e-93a0-dc91ce883ec6'
				};

				//Extract the search key
				var str = text.replace('product: ',' ');
				console.log(str);
				var url = 'https://www.loblaws.ca/ecommerce/v2/loblaw/products/search?q=';
				url = url.concat(str, '&page=0&pageSize=10&sort=recommended&filters=category:LSL001006000000');

				var options = {
				    url: url,
				    headers: headers
				};
				function callback(error, response, body) {
				    if (!error && response.statusCode == 200) {
				        //console.log(body);
				        var resp = JSON.parse(body);
				        results = resp["searchResults"];
				        console.log(results[0]);
				        console.log(results.products[0].productName);

				        sendProductMessage(sender, results);
				    }
				}

				request(options, callback);

			}
			sendTextMessage(sender, "echo: " + text.substring(0, 200));
		}
		if (event.postback) {
			switch(JSON.stringify(postback.payload)){
				case "searchProduct":
					sendTextMessage(sender, "Search by product", token);
					break;
				case "searchRecipe":
					sendTextMessage(sender, "Search by recipe", token);
					break;
				case "searchBarcode":
					sendTextMessage(sender, "Search by barcode", token);
					break;
			}
			continue;
		}
	}
	res.sendStatus(200);
});

//Request for product, image, or recipe search
function requestSearchType(sender){
	let messageData = {
		"attachment": {
			"type": "template",
			"payload":{
		        "template_type":"button",
		        "text":"What do you want to do next?",
		        "buttons":[
		          {
			        "type":"postback",
			        "title":"By Product",
			        "payload":"searchProduct",
			      },
			      {
			        "type":"postback",
			        "title":"By Recipe",
			        "payload":"searchRecipe",
			      },
			      {
			      	"type":"postback",
			      	"title": "By Barcode",
			      	"payload": "searchBarcode"
			      }
        		]
      		}
		}
	};

	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: sender,
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

// recommended to inject access tokens as environmental variables, e.g.
const token = process.env.PAGE_ACCESS_TOKEN;
//const token = "<PAGE_ACCESS_TOKEN>"

function sendTextMessage(sender, text) {
	let messageData = { text:text };

	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

function sendGenericMessage(sender) {
	let messageData = {
		"attachment": {
			"type": "template",
			"payload": {
				"template_type": "generic",
				"elements": [{
					"title": "First card",
					"subtitle": "Element #1 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/rift.png",
					"buttons": [{
						"type": "web_url",
						"url": "https://www.messenger.com",
						"title": "web url"
					}, {
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for first element in a generic bubble",
					}],
				}, {
					"title": "Second card",
					"subtitle": "Element #2 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/gearvr.png",
					"buttons": [{
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for second element in a generic bubble",
					}],
				}]
			}
		}
	};

	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

/*
	* Results is the api response
	* Makes a carouel displaying tile, description, and image
*/
function sendProductMessage(sender, results) {
	let messageData = {
		"attachment": {
			"type": "template",
			"payload": {
				"template_type": "generic",
				"elements": loadElements(results.products) //Load the products into an object that messenger api can handle
				/*"elements": [{
					"title": results.products[0].productName,
					"subtitle": results.products[0].description,
					"image_url": results.products[0].primaryImage,
					"buttons": [{
						"type": "web_url",
						"url": "https://www.messenger.com",
						"title": "web url"
					}, {
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for first element in a generic bubble",
					}],
				}, {
					"title": "Second card",
					"subtitle": "Element #2 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/gearvr.png",
					"buttons": [{
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for second element in a generic bubble",
					}],
				}] */
			}
		}
	};

	//Send message
	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

/*
	* Load products into an array that can be handled by messenger api
	* Makes carousel as long as number of responses
*/
function loadElements(products){
	var elements = []; //Create blank array

	console.log("Length: " + products.length);

	for (var i = 0; i < products.length; i++){
		var element = {
			"title": products[i].productName, //Sets title
			"subtitle": products[i].description, //Sets description
			"image_url": products[i].primaryImage, //Sets image
		}

		elements.push(element); //Add the element to the array
	}

	return elements;
}

// spin spin sugar
app.listen(app.get('port'), function() {
	console.log('running on port', app.get('port'));
});
<|endoftext|>


<commit_before>/**
 * Created by elad.benedict on 8/26/2015.
 */

var liveEntryCtor = require('./entry/LiveEntry');
var flavorDownloaderCtor = require('./entry/FlavorDownloader');
var logger = require('../common/logger').getLogger("Worker");
var fsUtils = require('./utils/fs-utils');
var config = require('./../common/Configuration');
var sessionManager = require('./SessionManager');
var persistenceFormat = require('./../common/PersistenceFormat');
var endListAppenderCtor = require('./manifest/EndListAppender');
var _ = require('underscore');
var Q = require('q');
var qio = require('q-io/fs');
var path = require('path');
var util = require('util');
var adapter = require('./Adapters/AdapterFactory.js').getAdapter();
var http = require("http");
module.exports = (function(){

    function Worker(prefix){
        var self=this;
        this.prefix = prefix;
        this.hostname = config.get('mediaServer').hostname;
        this.port = config.get('mediaServer').port;
        this.applicationName = config.get('mediaServer').applicationName;
        this._intervalObj = null; // Created by setInterval
        this.handledEntries = {};
        this.phantomEntries = {}; // Entries reported to be live even though they're not really live (e.g. due to API bugs)
        this.pollingInterval = config.get('wowzaPollingInterval');
        this.maxChunkCount = config.get('maxChunkCount');
        this.shouldRun = true;
        this.endListAppender = new endListAppenderCtor();
        this.monitorServer = http.createServer(function(request, response) {
            try {
                response.writeHead(200, {"Content-Type": "application/json"});
                response.end(JSON.stringify(self.getStat()));
                response.end();
            }catch(e) {

                logger.info("Exception returning response to monitor server", e, e.stack);
            }
        }).listen(4380+prefix);
    }

    function createLiveEntry(e){
        var that = this;
        var cleanupEntryFolderForNewSession = function(){
            var entryFolderPath = persistenceFormat.getEntryFullPath(e.entryId);
            logger.info("[%s] Cleaning folder %s ", e.entryId,entryFolderPath);
            return fsUtils.cleanFolder(entryFolderPath, e.entryId);
        };

        return sessionManager.isNewSession(e.entryId, logger)
            .then(function(isNewSession) {
                if (isNewSession) {
                     return cleanupEntryFolderForNewSession()
                         .then(function() {
                             return isNewSession;
                         });
                }
                return isNewSession;
            })
            .then(function(isNewSession){
                sessionManager.refreshSessionTimestamp(e.entryId);
                return isNewSession;
            })
            .then(function(isNewSession) {
                    var liveEntry = new liveEntryCtor(e, isNewSession);

                    return liveEntry.start().then(function(){
                        return liveEntry;
                    });
            })
            .then(function(entry) {
                if (that.handledEntries[e.entryId] !== 'cancelPending') {
                    logger.debug("[%s] Adding entry to list of handled entries", e.entryId);
                    that.handledEntries[e.entryId] = entry;
                    //entry.on('stopped', onLiveEntryStopped.bind(that));
                    logger.debug("[%s] Removing entry from endlistAppender", e.entryId);
                    that.endListAppender.removeEntry(e.entryId) // Entry is alive
                        .catch(function(err){
                            logger.error("[%s] Error adding entry to endlist appender processing - %s", e.entryId, err.message);
                        });
                }
                else {
                    logger.info("[%s] Entry creation aborted", e.entryId);
                    entry.stop()
                        .catch(function(err) {
                            logger.error("[%s] Aborting liveEntry object creation - %j", e.entryId, err);
                        })
                        .then(function() {
                            logger.info("[%s] Removing liveEntry object from handled entries list", e.entryId);
                            delete that.handledEntries[e.entryId];
                        });
                }
            });
    }

    function processAlreadyLiveEntries(currentlyLiveEntries) {
        var that = this;
        var alreadyLiveEntries = _.filter(currentlyLiveEntries, function(e) {
            return _.has(that.handledEntries, e.entryId);
        });
        _.forEach(alreadyLiveEntries, function(e) {
            var entry = that.handledEntries[e.entryId];
            if (entry === 'cancelPending') {
                that.handledEntries[e.entryId] = 'pending';
                logger.info("[%s] Entry started playing again after it stopped, changing state back to 'pending'", e.entryId);
            }
            if (entry !== 'pending')
                entry.updateLiveTimestamp();
        });
    }

    function processNewLiveEntries(currentlyLiveEntries){
        var that = this;
        var timeToCoolDown = 60*1000;
        var now = new Date();
        var unhandledLiveEntries = _.filter(currentlyLiveEntries, function(e) {
            return !_.has(that.handledEntries, e.entryId);
        });

        _.forEach(unhandledLiveEntries, function(e) {
            if (!that.handledEntries[e.entryId] && (!that.phantomEntries[e.entryId] || (now - that.phantomEntries[e.entryId] > timeToCoolDown))) {
                // Mark that this entry is already being handled (by the "pending" indicator) so that another
                // entry won't be created for it until the previous one's creation has completed
                that.handledEntries[e.entryId] = 'pending';
                logger.info("[%s] Entry started playing - creating a new object for it", e.entryId);
                createLiveEntry.call(that, e)
                    .then(function() {
                        // If the entry object creation is successful remove it from the list of phantom entries
                        if (that.phantomEntries[e.entryId]) {
                            logger.info("[%s] Successfully created liveEntry object, removing it from phantom list", e.entryId);
                            delete that.phantomEntries[e.entryId];
                        }
                    },
                    function(err) {
                        logger.error("[%s] Error starting entry object: %s, %s", e.entryId, err.message, err.stack);
                        if (that.handledEntries[e.entryId] === 'cancelPending') {
                            // Entry stopped streaming while trying to create object for it. Ignore the error and delete it from list
                            logger.info("[%s] Ignoring error since entry stopped streaming", e.entryId);
                        }
                        else {
                            // If the liveEntry creation was unsuccessful, wait a cool down time of 60 seconds and try again
                            var failTime = new Date();
                            that.phantomEntries[e.entryId] = failTime;
                            logger.debug("[%s] Updating last failure time and adding to phantom list: %s", e.entryId, failTime);
                        }
                        logger.info("[%s] Removing entry from handled entries list", e.entryId);
                        delete that.handledEntries[e.entryId];
                    });
            }
        });
    }

    function processNoLongerLivePhantomEntries(currentlyLiveEntries){
        var that = this;
        var noLongerLivePhantomEntries = _.difference(Object.keys(that.phantomEntries), _.map(currentlyLiveEntries, function(e) {return e.entryId;}));
        _.each(noLongerLivePhantomEntries, function(pe){
            logger.info('Phantom entry ' + pe + ' is no longer "live" - removing it from the phantom entries list');
            delete that.phantomEntries[pe];
        });
    }

    function processNoLongerLiveEntries(currentlyLiveEntries){
        var that = this;
        var noLongerLiveEntries = _.difference(Object.keys(that.handledEntries), _.map(currentlyLiveEntries, function(e) {return e.entryId;}));
        _.forEach(noLongerLiveEntries, function (noLongerLiveEntry) {
            var liveEntry = that.handledEntries[noLongerLiveEntry];
            if (liveEntry === 'pending') {
                logger.info("[%s] Entry status changed to not live before an object could be created for it. Changing state to 'cancelPending'", noLongerLiveEntry);
                that.handledEntries[noLongerLiveEntry] = 'cancelPending';
            }
            else if (liveEntry === 'cancelPending') {
                logger.info("[%s] Stop was requested for entry. Waiting to cancel its creation", noLongerLiveEntry);
            }
            else {
                logger.info("[%s] Entry is no longer live, stopping it", noLongerLiveEntry);
                return liveEntry.stop()
                    .then(function() {
                        logger.debug("[%s] Stopped entry. Removing it from list of handled entries", noLongerLiveEntry);
                        delete that.handledEntries[noLongerLiveEntry];
                        logger.debug("[%s] Adding entry to endlistAppender", noLongerLiveEntry);
                        return that.endListAppender.addEntry(noLongerLiveEntry) // Indicate entry is down and should (later) have end-list appended
                            .catch(function (error) {
                                logger.error("[%s] Error adding entry to endlistAppender processing - %s", noLongerLiveEntry, error.message);
                            })
                            .finally(function () {
                                logger.info("[%s] Removing playlist", noLongerLiveEntry);
                                var playlistPath = path.join(persistenceFormat.getEntryFullPath(noLongerLiveEntry), persistenceFormat.getMasterManifestName());
                                qio.remove(playlistPath);
                            });
                    })
                    .catch(function() {
                        logger.debug("[%s] Waiting %s seconds grace period", noLongerLiveEntry, config.get('flavorDownloaderTeardownInterval')/1000);
                    })
                    .done();
            }
        });
    }

    function handleEntries(){
        var that = this;
        return adapter.getLiveEntries()
            .then(function(liveEntries) {
                var relevantEntries = _.filter(liveEntries, function (e) {
                    return e.entryId.indexOf(that.prefix) === 0;
                });
                logger.debug("Entries received from Wowza: %j", relevantEntries);
                return relevantEntries;
            })
            .then(function(liveEntries){
                if (that.shouldRun) {
                    logger.debug("handled live entries: %j", _.keys(that.handledEntries));
                    var phantomEntryIds = Object.keys(that.phantomEntries);
                    logger.debug("phantom live entries: %j" , phantomEntryIds);
                    logger.debug("Active flavor downloaders: %j" , flavorDownloaderCtor.activeInstances);

                    processAlreadyLiveEntries.call(that, liveEntries);
                    processNewLiveEntries.call(that, liveEntries);
                    processNoLongerLivePhantomEntries.call(that, liveEntries);
                    processNoLongerLiveEntries.call(that, liveEntries);
                }
            });
    }

    Worker.prototype.start = function() {
        var that = this;
        logger.warn("Starting liveProxy pid: %s, wowza: '%s', API server: '%s', prefix='%s'",process.pid,config.get("mediaServer").hostname,config.get('backendClient').serviceUrl,that.prefix);
        logger.warn("Log files %s; %s",config.get('logFileName'),config.get('webServerParams:logFileName'));
        return Q.fcall(function() {
            that._intervalObj = setInterval(handleEntries.bind(that), that.pollingInterval);
            that.endListAppender.init().then(function(){
                return handleEntries.call(that);
            });
        });
    };

    Worker.prototype.stop = function() {
        var that = this;
        if (this._intervalObj) {
            clearInterval(this._intervalObj);
            this._intervalObj = null;
            this.shouldRun = false;
            var stopPromises = _.map(Object.keys(this.handledEntries), function(entryId){
                return that.handledEntries[entryId].stop();
            });
            return Q.all(stopPromises);
        }
    };

    Worker.prototype.getStat = function() {
        return {
            "handledEntries": this.handledEntries,
            "phantomEntries": this.phantomEntries
        }
    }

    return Worker;
})();

<commit_msg>in cases an error was thrown when trying to create the manifest generatot call unregister on the live entry<commit_after>/**
 * Created by elad.benedict on 8/26/2015.
 */

var liveEntryCtor = require('./entry/LiveEntry');
var flavorDownloaderCtor = require('./entry/FlavorDownloader');
var logger = require('../common/logger').getLogger("Worker");
var fsUtils = require('./utils/fs-utils');
var config = require('./../common/Configuration');
var sessionManager = require('./SessionManager');
var persistenceFormat = require('./../common/PersistenceFormat');
var endListAppenderCtor = require('./manifest/EndListAppender');
var _ = require('underscore');
var Q = require('q');
var qio = require('q-io/fs');
var path = require('path');
var util = require('util');
var adapter = require('./Adapters/AdapterFactory.js').getAdapter();
var http = require("http");
module.exports = (function(){

    function Worker(prefix){
        var self=this;
        this.prefix = prefix;
        this.hostname = config.get('mediaServer').hostname;
        this.port = config.get('mediaServer').port;
        this.applicationName = config.get('mediaServer').applicationName;
        this._intervalObj = null; // Created by setInterval
        this.handledEntries = {};
        this.phantomEntries = {}; // Entries reported to be live even though they're not really live (e.g. due to API bugs)
        this.pollingInterval = config.get('wowzaPollingInterval');
        this.maxChunkCount = config.get('maxChunkCount');
        this.shouldRun = true;
        this.endListAppender = new endListAppenderCtor();
        this.monitorServer = http.createServer(function(request, response) {
            try {
                response.writeHead(200, {"Content-Type": "application/json"});
                response.end(JSON.stringify(self.getStat()));
                response.end();
            }catch(e) {

                logger.info("Exception returning response to monitor server", e, e.stack);
            }
        }).listen(4380+prefix);
    }

    function createLiveEntry(e){
        var that = this;
        var cleanupEntryFolderForNewSession = function(){
            var entryFolderPath = persistenceFormat.getEntryFullPath(e.entryId);
            logger.info("[%s] Cleaning folder %s ", e.entryId,entryFolderPath);
            return fsUtils.cleanFolder(entryFolderPath, e.entryId);
        };

        return sessionManager.isNewSession(e.entryId, logger)
            .then(function(isNewSession) {
                if (isNewSession) {
                     return cleanupEntryFolderForNewSession()
                         .then(function() {
                             return isNewSession;
                         });
                }
                return isNewSession;
            })
            .then(function(isNewSession){
                sessionManager.refreshSessionTimestamp(e.entryId);
                return isNewSession;
            })
            .then(function(isNewSession) {
                    var liveEntry = new liveEntryCtor(e, isNewSession);

                    return liveEntry.start()
                        .then(function(){
                            return liveEntry;
                        })
                        .catch(function(err) {
                            err.entry = liveEntry;
                            logger.debug("[%s] Failed to start, assigning entry to handled entry list for cancellation", e.entryId);
                            return Q.reject(err);
                        });
            })
            .then(function(entry) {
                if (that.handledEntries[e.entryId] !== 'cancelPending') {
                    logger.debug("[%s] Adding entry to list of handled entries", e.entryId);
                    that.handledEntries[e.entryId] = entry;
                    //entry.on('stopped', onLiveEntryStopped.bind(that));
                    logger.debug("[%s] Removing entry from endlistAppender", e.entryId);
                    that.endListAppender.removeEntry(e.entryId) // Entry is alive
                        .catch(function(err){
                            logger.error("[%s] Error adding entry to endlist appender processing - %s", e.entryId, err.message);
                        });
                }
                else {
                    logger.info("[%s] Entry creation aborted", e.entryId);
                    entry.stop()
                    .catch(function(err) {
                        logger.error("[%s] Aborting liveEntry object creation - %j", e.entryId, err);
                    })
                    .then(function() {
                        logger.info("[%s] Removing liveEntry object from handled entries list", e.entryId);
                        delete that.handledEntries[e.entryId];
                    });
            }
            });
    }

    function processAlreadyLiveEntries(currentlyLiveEntries) {
        var that = this;
        var alreadyLiveEntries = _.filter(currentlyLiveEntries, function(e) {
            return _.has(that.handledEntries, e.entryId);
        });
        _.forEach(alreadyLiveEntries, function(e) {
            var entry = that.handledEntries[e.entryId];
            if (entry === 'cancelPending') {
                that.handledEntries[e.entryId] = 'pending';
                logger.info("[%s] Entry started playing again after it stopped, changing state back to 'pending'", e.entryId);
            }
            if (entry !== 'pending')
                entry.updateLiveTimestamp();
        });
    }

    function processNewLiveEntries(currentlyLiveEntries){
        var that = this;
        var timeToCoolDown = 60*1000;
        var now = new Date();
        var unhandledLiveEntries = _.filter(currentlyLiveEntries, function(e) {
            return !_.has(that.handledEntries, e.entryId);
        });

        _.forEach(unhandledLiveEntries, function(e) {
            if (!that.handledEntries[e.entryId] && (!that.phantomEntries[e.entryId] || (now - that.phantomEntries[e.entryId] > timeToCoolDown))) {
                // Mark that this entry is already being handled (by the "pending" indicator) so that another
                // entry won't be created for it until the previous one's creation has completed
                that.handledEntries[e.entryId] = 'pending';
                logger.info("[%s] Entry started playing - creating a new object for it", e.entryId);
                createLiveEntry.call(that, e)
                    .then(function() {
                        // If the entry object creation is successful remove it from the list of phantom entries
                        if (that.phantomEntries[e.entryId]) {
                            logger.info("[%s] Successfully created liveEntry object, removing it from phantom list", e.entryId);
                            delete that.phantomEntries[e.entryId];
                        }
                    },
                    function(err) {
                        logger.error("[%s] Error starting entry object: %s, %s", e.entryId, err.message, err.stack);
                        var entry = err.entry;
                        if (that.handledEntries[e.entryId] === 'cancelPending') {
                            // Entry stopped streaming while trying to create object for it. Ignore the error and delete it from list
                            logger.info("[%s] Ignoring error since entry stopped streaming", e.entryId);
                            if (entry) {
                                entry.stop()
                                    .then(function () {
                                        logger.debug("[%s] Stopped entry. Removing it from list of handled entries", e.entryId);
                                        delete that.handledEntries[e.entryId];
                                    });
                            }
                        }
                        else {
                            // If the liveEntry creation was unsuccessful, wait a cool down time of 60 seconds and try again
                            var failTime = new Date();
                            that.phantomEntries[e.entryId] = failTime;
                            logger.debug("[%s] Updating last failure time and adding to phantom list: %s", e.entryId, failTime);
                            logger.info("[%s] Removing entry from handled entries list", e.entryId);
                            delete that.handledEntries[e.entryId];
                        }
                    });
            }
        });
    }

    function processNoLongerLivePhantomEntries(currentlyLiveEntries){
        var that = this;
        var noLongerLivePhantomEntries = _.difference(Object.keys(that.phantomEntries), _.map(currentlyLiveEntries, function(e) {return e.entryId;}));
        _.each(noLongerLivePhantomEntries, function(pe){
            logger.info('Phantom entry ' + pe + ' is no longer "live" - removing it from the phantom entries list');
            delete that.phantomEntries[pe];
        });
    }

    function processNoLongerLiveEntries(currentlyLiveEntries){
        var that = this;
        var noLongerLiveEntries = _.difference(Object.keys(that.handledEntries), _.map(currentlyLiveEntries, function(e) {return e.entryId;}));
        _.forEach(noLongerLiveEntries, function (noLongerLiveEntry) {
            var liveEntry = that.handledEntries[noLongerLiveEntry];
            if (liveEntry === 'pending') {
                logger.info("[%s] Entry status changed to not live before an object could be created for it. Changing state to 'cancelPending'", noLongerLiveEntry);
                that.handledEntries[noLongerLiveEntry] = 'cancelPending';
            }
            else if (liveEntry === 'cancelPending') {
                logger.info("[%s] Stop was requested for entry. Waiting to cancel its creation", noLongerLiveEntry);
            }
            else {
                logger.info("[%s] Entry is no longer live, stopping it", noLongerLiveEntry);
                return liveEntry.stop()
                    .then(function() {
                        logger.debug("[%s] Stopped entry. Removing it from list of handled entries", noLongerLiveEntry);
                        delete that.handledEntries[noLongerLiveEntry];
                        logger.debug("[%s] Adding entry to endlistAppender", noLongerLiveEntry);
                        return that.endListAppender.addEntry(noLongerLiveEntry) // Indicate entry is down and should (later) have end-list appended
                            .catch(function (error) {
                                logger.error("[%s] Error adding entry to endlistAppender processing - %s", noLongerLiveEntry, error.message);
                            })
                            .finally(function () {
                                logger.info("[%s] Removing playlist", noLongerLiveEntry);
                                var playlistPath = path.join(persistenceFormat.getEntryFullPath(noLongerLiveEntry), persistenceFormat.getMasterManifestName());
                                qio.remove(playlistPath);
                            });
                    })
                    .catch(function() {
                        logger.debug("[%s] Waiting %s seconds grace period", noLongerLiveEntry, config.get('flavorDownloaderTeardownInterval')/1000);
                    })
                    .done();
            }
        });
    }

    function handleEntries(){
        var that = this;
        return adapter.getLiveEntries()
            .then(function(liveEntries) {
                var relevantEntries = _.filter(liveEntries, function (e) {
                    return e.entryId.indexOf(that.prefix) === 0;
                });
                logger.debug("Entries received from Wowza: %j", relevantEntries);
                return relevantEntries;
            })
            .then(function(liveEntries){
                if (that.shouldRun) {
                    logger.debug("handled live entries: %j", _.keys(that.handledEntries));
                    var phantomEntryIds = Object.keys(that.phantomEntries);
                    logger.debug("phantom live entries: %j" , phantomEntryIds);
                    logger.debug("Active flavor downloaders: %j" , flavorDownloaderCtor.activeInstances);

                    processAlreadyLiveEntries.call(that, liveEntries);
                    processNewLiveEntries.call(that, liveEntries);
                    processNoLongerLivePhantomEntries.call(that, liveEntries);
                    processNoLongerLiveEntries.call(that, liveEntries);
                }
            });
    }

    Worker.prototype.start = function() {
        var that = this;
        logger.warn("Starting liveProxy pid: %s, wowza: '%s', API server: '%s', prefix='%s'",process.pid,config.get("mediaServer").hostname,config.get('backendClient').serviceUrl,that.prefix);
        logger.warn("Log files %s; %s",config.get('logFileName'),config.get('webServerParams:logFileName'));
        return Q.fcall(function() {
            that._intervalObj = setInterval(handleEntries.bind(that), that.pollingInterval);
            that.endListAppender.init().then(function(){
                return handleEntries.call(that);
            });
        });
    };

    Worker.prototype.stop = function() {
        var that = this;
        if (this._intervalObj) {
            clearInterval(this._intervalObj);
            this._intervalObj = null;
            this.shouldRun = false;
            var stopPromises = _.map(Object.keys(this.handledEntries), function(entryId){
                return that.handledEntries[entryId].stop();
            });
            return Q.all(stopPromises);
        }
    };

    Worker.prototype.getStat = function() {
        return {
            "handledEntries": this.handledEntries,
            "phantomEntries": this.phantomEntries
        }
    }

    return Worker;
})();

<|endoftext|>