<commit_before>/* @flow */

"use strict";

// Perseus module uses React directly and uses $._ directly for
// localization, so we do this as a hack to get it to work
function perseusPrep(katex, KAS, MathJax) {
    $._ = function(x) { return x; };
    window.$_ = function(x) { return x; };
    window.katex = katex;
    window.KAS = KAS;
}

import React from "react";
import Util from "../util";
import APIClient from "../apiclient";
import l10n from "../l10n";
import {getName, getFilename, isPerseusExercise, isKhanExercisesExercise} from "../data/topic-tree-helper";
import component from "omniscient";
import $ from "jquery";
import {isSignedIn} from "../user";
import _ from "underscore";

window.Exercises = {
    cluesEnabled: false
};

const TaskCompleteView = component(({level}) =>

You did it!

        {
            l10n.get("level-indicator", {
                level: level
            })
        }

        { level !== "mastered" &&

To level up more, you need to do mastery challenges.

        }

).jsx;

/**
 * Represents a single exercise, it will load the exercise dynamically.
 * Most of this will be refactored when parent state is brought up.
 */
const ExerciseMixin = {
    refreshUserExerciseInfo: function() {
        var promises = [APIClient.getTaskInfoByExerciseName(getName(this.props.topicTreeNode))];
        if (isSignedIn()) {
            promises.push(APIClient.getUserExercise(getName(this.props.topicTreeNode)));
        }
        return new Promise((resolve, reject) => {
            Promise.all(promises).then((result) => {
                const taskInfo = result[0];
                const exerciseInfo = result[1];
                this.startTime = new Date().getTime();
                Util.log("getTaskInfoByExerciseName: %o", taskInfo);
                Util.log("getUserExercise: %o", exerciseInfo);
                resolve({
                    level: exerciseInfo && exerciseInfo.exercise_progress && exerciseInfo.exercise_progress.level,
                    mastered: exerciseInfo && exerciseInfo.exercise_progress && exerciseInfo.exercise_progress.mastered,
                    practiced: exerciseInfo && exerciseInfo.exercise_progress && exerciseInfo.exercise_progress.practiced,
                    problemNumber: exerciseInfo && (exerciseInfo.total_done + 1) || 1,
                    streak: exerciseInfo && exerciseInfo.streak,
                    taskId: taskInfo.id,
                    taskAttemptHistory: taskInfo.task_attempt_history,
                    longestStreak: exerciseInfo && exerciseInfo.longest_streak
                });
            }).catch((e) => {
                reject();
            });
        });
    },
    refreshRandomAssessment: function() {
        // Pick a random problem type:
        var problemTypes = this.exercise.problem_types;
        var randomProblemTypeGroupIndex = Math.floor(Math.random() * problemTypes.length);
        var randomProblemTypeGroup = this.exercise.problem_types[randomProblemTypeGroupIndex];
        this.problemTypeName = randomProblemTypeGroup.name;

        var randomProblemTypeIndex = Math.floor(Math.random() * randomProblemTypeGroup.items.length);

        this.randomAssessmentSHA1 = randomProblemTypeGroup.items[randomProblemTypeIndex].sha1;
        this.randomAssessmentId = randomProblemTypeGroup.items[randomProblemTypeIndex].id;

        return Promise.all([this.refreshUserExerciseInfo(),
            APIClient.getAssessmentItem(this.randomAssessmentId)]).then((results) => {
                this.attemptNumber = 0;
                var userExerciseInfo = results[0],
                    assessmentItem = results[1];
                var assessment = JSON.parse(assessmentItem.item_data);
                Util.log("Got assessment item: %o: item data: %o userExerciseInfo: %o", assessmentItem, assessment, userExerciseInfo);
                this.props.statics.editExercise((exercise) =>
                    exercise.merge({
                        hintsUsed: 0,
                        perseusItemData: assessment,
                        level: userExerciseInfo.level,
                        mastered: userExerciseInfo.mastered,
                        practiced: userExerciseInfo.practiced,
                        problemNumber: userExerciseInfo.problemNumber,
                        streak: userExerciseInfo.streak,
                        taskId: userExerciseInfo.taskId,
                        taskAttemptHistory: userExerciseInfo.taskAttemptHistory || [],
                        longestStreak: userExerciseInfo.longestStreak
                    }));
            });
    },
    onClickRequestHint: function() {
        this.refs.itemRenderer.showHint();
        this.props.statics.editExercise((exercise) => exercise.merge({
                hintsUsed: (this.props.exerciseStore.get("hintsUsed") || 0) + 1,
            })
        );
    },
    onClickSubmitAnswer: function() {
        var score = this.refs.itemRenderer.scoreInput();
        Util.log("score: %o", score);
        var secondsTaken = (new Date().getTime() - this.startTime) / 1000 | 0;
        var attemptNumber = ++this.attemptNumber;
        var isCorrect = score.correct;
        var hintsUsed = this.props.exerciseStore.get("hintsUsed") || 0;
        APIClient.reportExerciseProgress(getName(this.props.topicTreeNode), this.props.exerciseStore.get("problemNumber"),

            this.randomAssessmentSHA1, this.randomAssessmentId,
            secondsTaken, hintsUsed, isCorrect,
            attemptNumber, this.problemTypeName, this.props.exerciseStore.get("taskId")).then(() => {
                if (isCorrect) {
                    const getNumSuccessful = (exerciseStore) => {
                        var taskAttemptHistory = exerciseStore.get("taskAttemptHistory");
                        if (!taskAttemptHistory) {
                            return 0;
                        }
                        taskAttemptHistory = taskAttemptHistory.slice(-5);
                        return _.reduce(taskAttemptHistory.toJS(), (total, task) => {
                            return total + ((task.correct && !task.seen_hint) ? 1 : 0);
                        }, 0);
                    };
                    // If we have another correct and we already have 4 correct,
                    // then show task complete view.
                    if (this.props.exerciseStore.get("streak") >= 4 &&
                            hintsUsed === 0 && getNumSuccessful(this.props.exerciseStore) === 4) {
                        this.props.statics.editExercise((exercise) =>
                            exercise.merge({
                                taskComplete: true
                            })
                        );
                        return;
                    }
                    this.refreshRandomAssessment();
                } else {
                    // Refresh attempt info so it shows up as wrong
                    this.refreshUserExerciseInfo();
                }
            });
    },
    componentWillMount: function() {
        if (isPerseusExercise(this.props.topicTreeNode)) {
            APIClient.getExerciseByName(getName(this.props.topicTreeNode)).then((result) => {
                this.exercise = result;
                Util.log("got exercise: %o", result);
                this.refreshRandomAssessment();
            });
        }

        // TODO: Make this load async
        window._ = _;
        window.React = React;
        window.$ = $;
        $._ = (x) => x;
        window.jQuery = $;

        var Khan = require("../../khan-exercises/main"),
            MathJax = require("../../bower_components/MathJax/MathJax.js");
        Khan = window.Khan;
        MathJax = window.MathJax;
        window.KhanUtil = Khan.Util;

        const katex = require("katex"),
            KAS = require("../../bower_components/KAS/kas"),
            Perseus = require("../../bower_components/perseus/perseus-3");
        perseusPrep(katex, KAS, MathJax, Khan.Util);
        Perseus.init({}).then(() => {
            Util.log("Perseus init done %o, %o", Perseus);
            this.ItemRenderer = Perseus.ItemRenderer;
            this.forceUpdate();
        });
    },
};

export const ExerciseViewer = component(ExerciseMixin, function({topicTreeNode, exerciseStore}) {
    var content;
    if (exerciseStore.get("error")) {
        content =
Could not load exercise
;
    } else if (isKhanExercisesExercise(topicTreeNode)) {
        var path = `/khan-exercises/exercises/${getFilename(topicTreeNode)}`;
        content = ;
    } else if (exerciseStore.get("taskComplete")) {
        content = <TaskCompleteView level={exerciseStore.get("level")} mastered={exerciseStore.get("mastered")} />;
    } else if (this.ItemRenderer && exerciseStore.get("perseusItemData")) {
        var showHintsButton = exerciseStore.get("perseusItemData").get("hints").length > (exerciseStore.get("hintsUsed") || 0);
        // Always show 5 attempt icons with either pending, correct, hint or wrong
        var attemptIcons = [];
        var taskAttemptHistory = exerciseStore.get("taskAttemptHistory").slice(-5);
        for (var i = 0; i < 5; i++) {
            if (i >= taskAttemptHistory.length) {
                attemptIcons.push();
            } else if (taskAttemptHistory.get(i).get("seen_hint")) {
                attemptIcons.push();
            } else if (!taskAttemptHistory.get(i).get("correct")) {
                attemptIcons.push();
            } else {
                attemptIcons.push();
            }
        }

        var streakText;
        if (exerciseStore.get("streak") > 5) {
            streakText = l10n.get("correct-streak", {
                count: exerciseStore.get("streak"),
            });
        }
        var longestStreakText;
        if (exerciseStore.get("longestStreak") > 5) {
            longestStreakText = l10n.get("longest-correct-streak", {
                count: exerciseStore.get("longestStreak"),
            });
        }

        content =

                          {attemptIcons}

                              {streakText}

                              {longestStreakText}

                      <this.ItemRenderer ref="itemRenderer"
                                         item={exerciseStore.get("perseusItemData").toJS()}
                                         key={exerciseStore.get("problemNumber")}
                                         problemNum={exerciseStore.get("problemNumber")}
                                         initialHintsVisible={0}
                                         enabledFeatures={{
                                             highlight: true,
                                             toolTipFormats: true
                                         }} />

                      <button className="submit-answer-button"
                              data-l10n-id="submit-answer"
                              onClick={this.onClickSubmitAnswer}>Submit Answer</button>
                      { !showHintsButton ? null :
                      <button className="submit-answer-button"
                              data-l10n-id="hint"
                              onClick={this.onClickRequestHint}>Hint</button>
                      }

;
    }

    Util.log("render exercise: :%o", topicTreeNode);
    return

        {content}

;
}).jsx;
<commit_msg>Add some doc<commit_after>/* @flow */

"use strict";

// Perseus module uses React directly and uses $._ directly for
// localization, so we do this as a hack to get it to work
function perseusPrep(katex, KAS, MathJax) {
    $._ = function(x) { return x; };
    window.$_ = function(x) { return x; };
    window.katex = katex;
    window.KAS = KAS;
}

import React from "react";
import Util from "../util";
import APIClient from "../apiclient";
import l10n from "../l10n";
import {getName, getFilename, isPerseusExercise, isKhanExercisesExercise} from "../data/topic-tree-helper";
import component from "omniscient";
import $ from "jquery";
import {isSignedIn} from "../user";
import _ from "underscore";

window.Exercises = {
    cluesEnabled: false
};

/**
 * Component used to render a completion notice for when a stack of tasks
 * is complete.
 */
const TaskCompleteView = component(({level}) =>

You did it!

        {
            l10n.get("level-indicator", {
                level: level
            })
        }

        { level !== "mastered" &&

To level up more, you need to do mastery challenges.

        }

).jsx;

/**
 * Represents a single exercise, it will load the exercise dynamically.
 * Most of this will be refactored when parent state is brought up.
 */
const ExerciseMixin = {
    refreshUserExerciseInfo: function() {
        var promises = [APIClient.getTaskInfoByExerciseName(getName(this.props.topicTreeNode))];
        if (isSignedIn()) {
            promises.push(APIClient.getUserExercise(getName(this.props.topicTreeNode)));
        }
        return new Promise((resolve, reject) => {
            Promise.all(promises).then((result) => {
                const taskInfo = result[0];
                const exerciseInfo = result[1];
                this.startTime = new Date().getTime();
                Util.log("getTaskInfoByExerciseName: %o", taskInfo);
                Util.log("getUserExercise: %o", exerciseInfo);
                resolve({
                    level: exerciseInfo && exerciseInfo.exercise_progress && exerciseInfo.exercise_progress.level,
                    mastered: exerciseInfo && exerciseInfo.exercise_progress && exerciseInfo.exercise_progress.mastered,
                    practiced: exerciseInfo && exerciseInfo.exercise_progress && exerciseInfo.exercise_progress.practiced,
                    problemNumber: exerciseInfo && (exerciseInfo.total_done + 1) || 1,
                    streak: exerciseInfo && exerciseInfo.streak,
                    taskId: taskInfo.id,
                    taskAttemptHistory: taskInfo.task_attempt_history,
                    longestStreak: exerciseInfo && exerciseInfo.longest_streak
                });
            }).catch((e) => {
                reject();
            });
        });
    },
    refreshRandomAssessment: function() {
        // Pick a random problem type:
        var problemTypes = this.exercise.problem_types;
        var randomProblemTypeGroupIndex = Math.floor(Math.random() * problemTypes.length);
        var randomProblemTypeGroup = this.exercise.problem_types[randomProblemTypeGroupIndex];
        this.problemTypeName = randomProblemTypeGroup.name;

        var randomProblemTypeIndex = Math.floor(Math.random() * randomProblemTypeGroup.items.length);

        this.randomAssessmentSHA1 = randomProblemTypeGroup.items[randomProblemTypeIndex].sha1;
        this.randomAssessmentId = randomProblemTypeGroup.items[randomProblemTypeIndex].id;

        return Promise.all([this.refreshUserExerciseInfo(),
            APIClient.getAssessmentItem(this.randomAssessmentId)]).then((results) => {
                this.attemptNumber = 0;
                var userExerciseInfo = results[0],
                    assessmentItem = results[1];
                var assessment = JSON.parse(assessmentItem.item_data);
                Util.log("Got assessment item: %o: item data: %o userExerciseInfo: %o", assessmentItem, assessment, userExerciseInfo);
                this.props.statics.editExercise((exercise) =>
                    exercise.merge({
                        hintsUsed: 0,
                        perseusItemData: assessment,
                        level: userExerciseInfo.level,
                        mastered: userExerciseInfo.mastered,
                        practiced: userExerciseInfo.practiced,
                        problemNumber: userExerciseInfo.problemNumber,
                        streak: userExerciseInfo.streak,
                        taskId: userExerciseInfo.taskId,
                        taskAttemptHistory: userExerciseInfo.taskAttemptHistory || [],
                        longestStreak: userExerciseInfo.longestStreak
                    }));
            });
    },
    onClickRequestHint: function() {
        this.refs.itemRenderer.showHint();
        this.props.statics.editExercise((exercise) => exercise.merge({
                hintsUsed: (this.props.exerciseStore.get("hintsUsed") || 0) + 1,
            })
        );
    },
    onClickSubmitAnswer: function() {
        var score = this.refs.itemRenderer.scoreInput();
        Util.log("score: %o", score);
        var secondsTaken = (new Date().getTime() - this.startTime) / 1000 | 0;
        var attemptNumber = ++this.attemptNumber;
        var isCorrect = score.correct;
        var hintsUsed = this.props.exerciseStore.get("hintsUsed") || 0;
        APIClient.reportExerciseProgress(getName(this.props.topicTreeNode), this.props.exerciseStore.get("problemNumber"),

            this.randomAssessmentSHA1, this.randomAssessmentId,
            secondsTaken, hintsUsed, isCorrect,
            attemptNumber, this.problemTypeName, this.props.exerciseStore.get("taskId")).then(() => {
                if (isCorrect) {
                    const getNumSuccessful = (exerciseStore) => {
                        var taskAttemptHistory = exerciseStore.get("taskAttemptHistory");
                        if (!taskAttemptHistory) {
                            return 0;
                        }
                        taskAttemptHistory = taskAttemptHistory.slice(-5);
                        return _.reduce(taskAttemptHistory.toJS(), (total, task) => {
                            return total + ((task.correct && !task.seen_hint) ? 1 : 0);
                        }, 0);
                    };
                    // If we have another correct and we already have 4 correct,
                    // then show task complete view.
                    if (this.props.exerciseStore.get("streak") >= 4 &&
                            hintsUsed === 0 && getNumSuccessful(this.props.exerciseStore) === 4) {
                        this.props.statics.editExercise((exercise) =>
                            exercise.merge({
                                taskComplete: true
                            })
                        );
                        return;
                    }
                    this.refreshRandomAssessment();
                } else {
                    // Refresh attempt info so it shows up as wrong
                    this.refreshUserExerciseInfo();
                }
            });
    },
    componentWillMount: function() {
        if (isPerseusExercise(this.props.topicTreeNode)) {
            APIClient.getExerciseByName(getName(this.props.topicTreeNode)).then((result) => {
                this.exercise = result;
                Util.log("got exercise: %o", result);
                this.refreshRandomAssessment();
            });
        }

        // TODO: Make this load async
        window._ = _;
        window.React = React;
        window.$ = $;
        $._ = (x) => x;
        window.jQuery = $;

        var Khan = require("../../khan-exercises/main"),
            MathJax = require("../../bower_components/MathJax/MathJax.js");
        Khan = window.Khan;
        MathJax = window.MathJax;
        window.KhanUtil = Khan.Util;

        const katex = require("katex"),
            KAS = require("../../bower_components/KAS/kas"),
            Perseus = require("../../bower_components/perseus/perseus-3");
        perseusPrep(katex, KAS, MathJax, Khan.Util);
        Perseus.init({}).then(() => {
            Util.log("Perseus init done %o, %o", Perseus);
            this.ItemRenderer = Perseus.ItemRenderer;
            this.forceUpdate();
        });
    },
};

/**
 * Component used to render an exercise.
 */
export const ExerciseViewer = component(ExerciseMixin, function({topicTreeNode, exerciseStore}) {
    var content;
    if (exerciseStore.get("error")) {
        content =
Could not load exercise
;
    } else if (isKhanExercisesExercise(topicTreeNode)) {
        var path = `/khan-exercises/exercises/${getFilename(topicTreeNode)}`;
        content = ;
    } else if (exerciseStore.get("taskComplete")) {
        content = <TaskCompleteView level={exerciseStore.get("level")} mastered={exerciseStore.get("mastered")} />;
    } else if (this.ItemRenderer && exerciseStore.get("perseusItemData")) {
        var showHintsButton = exerciseStore.get("perseusItemData").get("hints").length > (exerciseStore.get("hintsUsed") || 0);
        // Always show 5 attempt icons with either pending, correct, hint or wrong
        var attemptIcons = [];
        var taskAttemptHistory = exerciseStore.get("taskAttemptHistory").slice(-5);
        for (var i = 0; i < 5; i++) {
            if (i >= taskAttemptHistory.length) {
                attemptIcons.push();
            } else if (taskAttemptHistory.get(i).get("seen_hint")) {
                attemptIcons.push();
            } else if (!taskAttemptHistory.get(i).get("correct")) {
                attemptIcons.push();
            } else {
                attemptIcons.push();
            }
        }

        var streakText;
        if (exerciseStore.get("streak") > 5) {
            streakText = l10n.get("correct-streak", {
                count: exerciseStore.get("streak"),
            });
        }
        var longestStreakText;
        if (exerciseStore.get("longestStreak") > 5) {
            longestStreakText = l10n.get("longest-correct-streak", {
                count: exerciseStore.get("longestStreak"),
            });
        }

        content =

                          {attemptIcons}

                              {streakText}

                              {longestStreakText}

                      <this.ItemRenderer ref="itemRenderer"
                                         item={exerciseStore.get("perseusItemData").toJS()}
                                         key={exerciseStore.get("problemNumber")}
                                         problemNum={exerciseStore.get("problemNumber")}
                                         initialHintsVisible={0}
                                         enabledFeatures={{
                                             highlight: true,
                                             toolTipFormats: true
                                         }} />

                      <button className="submit-answer-button"
                              data-l10n-id="submit-answer"
                              onClick={this.onClickSubmitAnswer}>Submit Answer</button>
                      { !showHintsButton ? null :
                      <button className="submit-answer-button"
                              data-l10n-id="hint"
                              onClick={this.onClickRequestHint}>Hint</button>
                      }

;
    }

    Util.log("render exercise: :%o", topicTreeNode);
    return

        {content}

;
}).jsx;
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var codecs = require('./codecs');

/**
 * Expose `Decoder`.
 */

exports = module.exports = Decoder;

/**
 * Initialize a new `Decoder`.
 *
 * The "Decoder" encapsulates message framing and
 * applying of codecs for each message received.
 *
 * @api private
 */

function Decoder(){
  this.buff = new Buffer(4);
  this.i = 0;
  this.state = 'header';
  this.headers = null;
  this.body = null;
}

/**
 * Temporary stub. Calling context should impement this
 * in order to receive the parsed message(s).
 *
 * @param {Mixed} message
 * @api private
 */

Decoder.prototype.onmessage = function(msg){};

/**
 * Frame the given `chunk`.
 *
 * @param {Buffer} chunk
 * @api private
 */

Decoder.prototype.write = function(chunk){
  if ('header' == this.state) this.frameHeader(chunk)
  else this.frameBody(chunk);
};

/**
 * Frame headers.
 *
 * @param {Buffer} chunk
 * @api private
 */

Decoder.prototype.frameHeader = function(chunk) {
  var remaining = 4 - this.i;

  var n = remaining > chunk.length
    ? chunk.length
    : remaining;

  // buffer
  for (var i = 0; i < n; i++) {
    this.buff[this.i++] = chunk[i];
  }

  // complete
  if (4 === this.i) {
    this.state = 'body';
    this.i = 0;
    this.headers = this.parseHeaders(this.buff);
    this.body = new Buffer(this.headers.length);
  }

  // remaining chunks
  if (chunk.length - n) this.write(chunk.slice(n));
};

/**
 * Frame body.
 *
 * @param {Buffer} chunk
 * @api private
 */

Decoder.prototype.frameBody = function(chunk) {
  var remaining = this.headers.length - this.i;

  var n = remaining > chunk.length
    ? chunk.length
    : remaining;

  var multipart = this.headers.meta === 0x00;

  // buffer
  chunk.copy(this.body, this.i, 0, n);
  this.i += n;

  // complete
  if (this.headers.length === this.i) {
    this.onmessage(this.parseBody(this.body, this.headers), multipart);
    this.reset();
  }

  // bytes remaining
  if (chunk.length - n) this.write(chunk.slice(n));
};

/**
 * Parsers out `meta` and `length` header octets.
 *
 * @param {Buffer} buff
 * @return {Object}
 * @api private
 */

Decoder.prototype.parseHeaders = function(buff) {
  var meta = buff[0]
    , len = 0;

  // zero out MSB
  buff[0] = 0x0;
  len = buff.readUInt32BE(0);

  // undo side-effect
  buff[0] = meta;

  return {
    length: len,
    meta: meta
  };
};

/**
 * Parses out multipart messages.
 *
 * @param {Buffer} buff
 * @return {Boolean} multipart
 * @api private
 */

Decoder.prototype.parseBody = function(body, headers) {
  var multipart = headers.meta === 0x00;
  var msgs = []
    , buff
    , header;

  if (!multipart) return this.decode(body, headers.meta);

  for (var i = 0; i < body.length;) {
    header = this.parseHeaders(body.slice(i, i + 4));
    buff = body.slice(i + 4, i + header.length + 4);
    msgs.push(this.decode(buff, header.meta));
    i += header.length + 4;
  }

  return msgs;
};

/**
 * Apply the codec `type` to `msg`.
 *
 * @param {Buffer} msg
 * @param {String} fmt
 * @return {Mixed} decoded message
 * @api private
 */

Decoder.prototype.decode = function(msg, type) {
  var codec = codecs.byId(type);
  return codec.decode(msg);
};

/**
 * Set/resets to the default state.
 */

Decoder.prototype.reset = function() {
  this.state = 'header';
  this.i = 0;
  this.headers = null;
  this.body = null;
};

<commit_msg>decoder: buff -> buf (nitpick)<commit_after>
/**
 * Module dependencies.
 */

var codecs = require('./codecs');

/**
 * Expose `Decoder`.
 */

exports = module.exports = Decoder;

/**
 * Initialize a new `Decoder`.
 *
 * The "Decoder" encapsulates message framing and
 * applying of codecs for each message received.
 *
 * @api private
 */

function Decoder(){
  this.buf = new Buffer(4);
  this.i = 0;
  this.state = 'header';
  this.headers = null;
  this.body = null;
}

/**
 * Temporary stub. Calling context should impement this
 * in order to receive the parsed message(s).
 *
 * @param {Mixed} message
 * @api private
 */

Decoder.prototype.onmessage = function(msg){};

/**
 * Frame the given `chunk`.
 *
 * @param {Buffer} chunk
 * @api private
 */

Decoder.prototype.write = function(chunk){
  if ('header' == this.state) this.frameHeader(chunk)
  else this.frameBody(chunk);
};

/**
 * Frame headers.
 *
 * @param {Buffer} chunk
 * @api private
 */

Decoder.prototype.frameHeader = function(chunk) {
  var remaining = 4 - this.i;

  var n = remaining > chunk.length
    ? chunk.length
    : remaining;

  // bufer
  for (var i = 0; i < n; i++) {
    this.buf[this.i++] = chunk[i];
  }

  // complete
  if (4 === this.i) {
    this.state = 'body';
    this.i = 0;
    this.headers = this.parseHeaders(this.buf);
    this.body = new Buffer(this.headers.length);
  }

  // remaining chunks
  if (chunk.length - n) this.write(chunk.slice(n));
};

/**
 * Frame body.
 *
 * @param {Buffer} chunk
 * @api private
 */

Decoder.prototype.frameBody = function(chunk) {
  var remaining = this.headers.length - this.i;

  var n = remaining > chunk.length
    ? chunk.length
    : remaining;

  var multipart = this.headers.meta === 0x00;

  // bufer
  chunk.copy(this.body, this.i, 0, n);
  this.i += n;

  // complete
  if (this.headers.length === this.i) {
    this.onmessage(this.parseBody(this.body, this.headers), multipart);
    this.reset();
  }

  // bytes remaining
  if (chunk.length - n) this.write(chunk.slice(n));
};

/**
 * Parsers out `meta` and `length` header octets.
 *
 * @param {Buffer} buf
 * @return {Object}
 * @api private
 */

Decoder.prototype.parseHeaders = function(buf) {
  var meta = buf[0]
    , len = 0;

  // zero out MSB
  buf[0] = 0x0;
  len = buf.readUInt32BE(0);

  // undo side-effect
  buf[0] = meta;

  return {
    length: len,
    meta: meta
  };
};

/**
 * Parses out multipart messages.
 *
 * @param {Buffer} buf
 * @return {Boolean} multipart
 * @api private
 */

Decoder.prototype.parseBody = function(body, headers) {
  var multipart = headers.meta === 0x00;
  var msgs = []
    , buf
    , header;

  if (!multipart) return this.decode(body, headers.meta);

  for (var i = 0; i < body.length;) {
    header = this.parseHeaders(body.slice(i, i + 4));
    buf = body.slice(i + 4, i + header.length + 4);
    msgs.push(this.decode(buf, header.meta));
    i += header.length + 4;
  }

  return msgs;
};

/**
 * Apply the codec `type` to `msg`.
 *
 * @param {Buffer} msg
 * @param {String} fmt
 * @return {Mixed} decoded message
 * @api private
 */

Decoder.prototype.decode = function(msg, type) {
  var codec = codecs.byId(type);
  return codec.decode(msg);
};

/**
 * Set/resets to the default state.
 */

Decoder.prototype.reset = function() {
  this.state = 'header';
  this.i = 0;
  this.headers = null;
  this.body = null;
};

<|endoftext|>


<commit_before>/*jshint esversion: 6 */

// initial DB object
var mysql = require('promise-mysql');
var sql_config = require('../sql_config');
var db = mysql.createPool(sql_config.db);
var debug = require('debug')('myBus:cron');
var util = require('./util');

var taichung = require('./fetch_taichung');
const routeToFetch = [
    {route: 160, isReverse: true},
    {route: 160, isReverse: false}
];

db.on('enqueue', function () {
    console.log('Waiting for available connection slot');
});

function updateBusTable (route) {
    clearTimetable(route).then(
        taichung.fetchTimeTable(route).then(function (timetable) {
            console.log(timetable);
            return walkThroughTimetable(timetable.timeList);
        }).then((tableEntry) => {
            console.log(JSON.stringify(tableEntry));
            saveTimeTable(route, tableEntry);
        })
    );
}

function isReverse (direction) {
    if (typeof direction === 'boolean') {
        return direction;
    } else if (direction.toLowerCase() === 'foeard') {
        return false;
    } else if (direction.toLowerCase() === 'reverse') {
        return true;
    } else {
        throw new Exception('Unknown direction');
    }
}

function clearTimetable (route) {
    var query = `DELETE FROM \`Time_table\` WHERE \`route\`=${route} `;
    console.log(query);
    return db.getConnection().then((connection) => {
        connection.query(query).then((rows) => {
            console.log('Delete OK');
        }).catch((err) => {
            console.log(err.code);
        }).finally(() => {
            db.releaseConnection(connection);
        });
    });
}

function walkThroughTimetable (timetable, location, result) {
    if (!location) {location = '';}
    if (!result) {result = [];}

    for (var key in timetable) {
        if (!timetable.hasOwnProperty(key)) {
            continue;       // skip this property
        }
        if (typeof timetable[key] == "object" && timetable[key] !== null && isNaN(key)) {
            //console.log(location + '\t' + key);
            walkThroughTimetable(timetable[key], (location + `.${key}`), result);
        } else {
            //console.log(timetable[key]);
            var entry = `${location}.${timetable[key].hour}.${timetable[key].minute}.${timetable[key].local}`;
            //console.log(`${location}.${timetable[key].hour}.${timetable[key].minute}.${timetable[key].local}`);
            result.push(entry);
            //console.log(entry);
        }
    }

    return result;
}

function saveTimeTable(route, entryArray) {
    console.log(`saveTimeTable(${entryArray.length})`);
    if (entryArray.length > 0) {
        var entry = entryArray.pop();
        var column = entry.split('.');
        //saveTimetableEntry(route, column[1], isReverse(column[2]), column[3], column[4], Number(column[5]));
        //return saveTimeTable(route, entryArray);
        return saveTimetableEntry(
            route, column[1], isReverse(column[2]), column[3], column[4],Number(column[5])
        ).then(
            saveTimeTable(route, entryArray)
        );
        //(route, column[1], isReverse(column[2]), column[3], column[4], Number(column[5]));
    }
}

function saveTimetableEntry (route, weekday, is_reverse, hour, minute, local) {
    var query = `INSERT INTO \`Time_table\` (\`route\`, \`weekday\`, \`hour\`, \`minute\`, \`local\`) VALUES ('${route}', '${weekday}', '${hour}', '${minute}', '${local}')`;
    return db.getConnection().then((connection) => {
        connection.query(query).then((rows) => {
            console.log('\tquery ok');
        }).catch((err) => {
            console.log('\t' + err.code);
        }).finally(() => {
            db.releaseConnection(connection);
        });
    });
}

function updateRealTime(pos) {
    if (pos === undefined) {
        updateRealTime(0);
    } else if (pos < routeToFetch.length) {
        var mergedData;
        taichung.fetchBusStatus(routeToFetch[pos].route, routeToFetch[pos].isReverse, 9)
        .then(mergeWithStopInfo).then((data) => {
            mergedData = data;
        }).then(() => {
            debug(`Update realtime data for route ${mergedData.route} ${mergedData.isReverse?'foward':'reverse'}`);
            saveBusArrival(mergedData);
            saveBusStatus(mergedData);
        });

        updateRealTime(pos+1);
    }
}

function mergeWithStopInfo(arrivalTime) {
    return db.getConnection().then((connection) => {
        return connection.query(`SELECT * FROM \`Bus_stop\` WHERE \`route\`=${arrivalTime.route} AND \`is_reverse\`=${arrivalTime.isReverse}`)
        .then((rows) => {
            return taichung.mergeBusStatus(arrivalTime, rows);
        }).catch((err) => {
            console.log(err);
        }).finally(() => {
            db.releaseConnection(connection);
        });
    });
}

function saveBusArrival(data, connection) {
    //console.log(`saveBusArrival(${data}, ${connection})`);
    if (data.stopInfo && data.stopInfo.length <= 0) {
        db.releaseConnection(connection);
    } else if (connection === undefined) {
        return db.getConnection().then((connection) => {
            clearBusArrival(data, connection).then(() => {
                saveBusArrival(data, connection);
            });
        });
    } else {
        var stopInfo = data.stopInfo.pop();
        stopInfo.route = data.route;
        stopInfo.isReverse = data.isReverse;
        return saveBusArrivalEntry(stopInfo, connection).then(() => {
            saveBusArrival(data, connection);
        });
    }
}

function clearBusArrival(config, connection) {
    //debug(`clearBusArrival(${config.route}, ${config.isReverse})`);
    return connection.query(`DELETE FROM \`Bus_arrival\` WHERE \`route\`=${config.route} AND \`is_reverse\`=${config.isReverse}`)
    .catch((err) => {
        console.log(err);
    });
}

function saveBusArrivalEntry(data, connection) {
    //debug(`saveBusArrivalEntry({route: ${data.route}, sn: ${data.sn}, isReverse: ${data.isReverse}})`);
    //console.log(`saveBusArrivalEntry({route: ${data.route}, sn: ${data.sn}, isReverse: ${data.isReverse}})`);
    return connection.query(`INSERT INTO \`Bus_arrival\`(\`route\`, \`sn\`, \`is_reverse\`, \`prediction\`) VALUES (${data.route},${data.sn},${data.isReverse},'${util.toSqlTimestamp(data.nextBus.timestamp)}')`)
    .catch((err) => {
        console.log(err);
    });
}

function saveBusStatus(data, connection) {
    //console.log(`saveBusStatus(${data}, ${connection})`);
    if (data.busInfo === null || data.busInfo.length <= 0) {
        if (connection !== undefined) {
            db.releaseConnection(connection);
        }
    } else if (connection === undefined) {
        return db.getConnection().then((connection) => {
            return clearBusOutdated(connection).then(() => {
                saveBusStatus(data, connection);
            });
        });
    } else {
        var busData = data.busInfo.pop();
        busData.route = data.route;
        busData.isReverse = data.isReverse;

        return updateBusStatus(busData, connection).then(() => {
            saveBusStatus(data, connection);
        });
    }
}

function clearBusOutdated(connection) {
    //debug("clear outdated bus");
    return connection.query(`DELETE FROM \`Bus_status\` WHERE ABS(\`last_update\`-CURRENT_TIMESTAMP) > 1800`)
    .catch((err) => {console.log(err);});
}

function updateBusStatus(data, connection) {
    //debug(`updateBusStatus(${data.plate_no})`);
    //console.log(`updateBusStatus(${data.plate_no})`);
    return connection.query(`INSERT INTO \`Bus_status\`(\`plate_number\`, \`route\`, \`closestStop\`, \`nextStop\`, \`longitude\`, \`latitude\`, \`is_reverse\`) \
    VALUES ('${data.plate_no}', ${data.route}, ${data.closestStop}, ${data.nextStop}, ${data.longitude}, ${data.latitude}, ${data.isReverse})`)
    .catch((err) => {
        if (err.code.includes('ER_DUP_ENTRY')) {
            connection.query(`UPDATE \`Bus_status\` SET \`plate_number\`='${data.plate_no}',\`route\`=${data.route},\`closestStop\`=${data.closestStop},\`nextStop\`=${data.nextStop},\`longitude\`=${data.longitude},\`latitude\`=${data.latitude},\`is_reverse\`=${data.isReverse},\`last_update\`=CURRENT_TIMESTAMP \
                            WHERE \`plate_number\`='${data.plate_no}'`);
        } else {
            console.log(err);
        }
    });
}

function updateRouteList() {
    return taichung.fetchRouteList().then((routeList) => {
        return saveRouteList();
    });
}

function saveRouteList(routeList, connection) {
    if (routeList.keys().length > 0 && connection === undefined) {
        return db.getConnection((connection) => {
            saveRouteList(routeNameList, connection);
        });
    } else if (routeList.keys().length > 0) {
        var key = routeList.keys()[0];
        var entry = routeList[key];
        entry.routeNo = key;
        delete routeList[key];
        return deleteRouteEntry(connection).then(() => {
            saveRouteEntry(entry, connection);
        }).then(() => {
            saveRouteList(routeList, connection);
        });
    } else {
        if (connection !== undefined) {
            db.releaseConnection(connection);
        } else {
            return;
        }
    }
}

function saveRouteEntry(entry, connection) {
    return deleteRouteEntry(connection).then(() => {
        connection.query(`INSERT INTO \`Route_info\` (\`route\`, \`name\`, \`start\`, \`end\`, \`map\`) ` +
            `VALUES ('${entry.routeNo}', '${entry.name}', '${entry.from}', '${entry.to}', '${entry.map}');`);
    }).then(() => {
        deleteRouteOperator(entry.routeNo, connection);
    }).then(() => {
        saveRouteOperator(entry.routeNo, entry.operator, connection);
    });
}

function deleteRouteEntry(connection) {
    return connection.query('DELETE FROM `Route_info`');
}

function saveRouteOperator(routeNo, operators, connection) {
    if (operator.length > 0) {
        //return connection.query(`INSERT INTO \`Route_operator\` (\`routeNo\`, \`operator\`) VALUES ('${routeNo}', '${}');`);
        var operatorName = operators.pop();
        return query(`INSERT INTO \`Route_operator\` (\`routeNo\`, \`operator \`) VALUES ('${routeNo}', 'operatorName')`).then(() => {
            saveRouteOperator(routeNo, operators, connection);
        });
    } else {
        return;
    }
}

function deleteRouteOperator(routeNo, connection) {
    return connection.query(`DELETE FROM \`Route_operator\` WHERE \`routeNo\`=${routeNo}`);
}

module.exports.updateBusTable = updateBusTable;
module.exports.updateRealTime = updateRealTime;
module.exports.updateRouteList = updateRouteList;
<commit_msg>add updateStopList()<commit_after>/*jshint esversion: 6 */

// initial DB object
var mysql = require('promise-mysql');
var sql_config = require('../sql_config');
var db = mysql.createPool(sql_config.db);
var debug = require('debug')('myBus:cron');
var util = require('./util');

var taichung = require('./fetch_taichung');
const routeToFetch = [
    {route: 5, isReverse: true},
    {route: 5, isReverse: false},
    {route: 160, isReverse: true},
    {route: 160, isReverse: false}
];

db.on('enqueue', function () {
    console.log('Waiting for available connection slot');
});

function updateBusTable (route) {
    clearTimetable(route).then(
        taichung.fetchTimeTable(route).then(function (timetable) {
            console.log(timetable);
            return walkThroughTimetable(timetable.timeList);
        }).then((tableEntry) => {
            console.log(JSON.stringify(tableEntry));
            saveTimeTable(route, tableEntry);
        })
    );
}

function isReverse (direction) {
    if (typeof direction === 'boolean') {
        return direction;
    } else if (direction.toLowerCase() === 'foeard') {
        return false;
    } else if (direction.toLowerCase() === 'reverse') {
        return true;
    } else {
        throw new Exception('Unknown direction');
    }
}

function clearTimetable (route) {
    var query = `DELETE FROM \`Time_table\` WHERE \`route\`=${route} `;
    console.log(query);
    return db.getConnection().then((connection) => {
        connection.query(query).then((rows) => {
            console.log('Delete OK');
        }).catch((err) => {
            console.log(err.code);
        }).finally(() => {
            db.releaseConnection(connection);
        });
    });
}

function walkThroughTimetable (timetable, location, result) {
    if (!location) {location = '';}
    if (!result) {result = [];}

    for (var key in timetable) {
        if (!timetable.hasOwnProperty(key)) {
            continue;       // skip this property
        }
        if (typeof timetable[key] == "object" && timetable[key] !== null && isNaN(key)) {
            //console.log(location + '\t' + key);
            walkThroughTimetable(timetable[key], (location + `.${key}`), result);
        } else {
            //console.log(timetable[key]);
            var entry = `${location}.${timetable[key].hour}.${timetable[key].minute}.${timetable[key].local}`;
            //console.log(`${location}.${timetable[key].hour}.${timetable[key].minute}.${timetable[key].local}`);
            result.push(entry);
            //console.log(entry);
        }
    }

    return result;
}

function saveTimeTable(route, entryArray) {
    console.log(`saveTimeTable(${entryArray.length})`);
    if (entryArray.length > 0) {
        var entry = entryArray.pop();
        var column = entry.split('.');
        //saveTimetableEntry(route, column[1], isReverse(column[2]), column[3], column[4], Number(column[5]));
        //return saveTimeTable(route, entryArray);
        return saveTimetableEntry(
            route, column[1], isReverse(column[2]), column[3], column[4],Number(column[5])
        ).then(
            saveTimeTable(route, entryArray)
        );
        //(route, column[1], isReverse(column[2]), column[3], column[4], Number(column[5]));
    }
}

function saveTimetableEntry (route, weekday, is_reverse, hour, minute, local) {
    var query = `INSERT INTO \`Time_table\` (\`route\`, \`weekday\`, \`hour\`, \`minute\`, \`local\`) VALUES ('${route}', '${weekday}', '${hour}', '${minute}', '${local}')`;
    return db.getConnection().then((connection) => {
        connection.query(query).then((rows) => {
            console.log('\tquery ok');
        }).catch((err) => {
            console.log('\t' + err.code);
        }).finally(() => {
            db.releaseConnection(connection);
        });
    });
}

/* ========================================================================================================= */

function updateRealTime(pos) {
    if (pos === undefined) {
        updateRealTime(0);
    } else if (pos < routeToFetch.length) {
        var mergedData;
        taichung.fetchBusStatus(routeToFetch[pos].route, routeToFetch[pos].isReverse, 9)
        .then(mergeWithStopInfo).then((data) => {
            mergedData = data;
        }).then(() => {
            debug(`Update realtime data for route ${mergedData.route} ${mergedData.isReverse?'foward':'reverse'}`);
            saveBusArrival(mergedData);
            saveBusStatus(mergedData);
        });

        updateRealTime(pos+1);
    }
}

function mergeWithStopInfo(arrivalTime) {
    return db.getConnection().then((connection) => {
        return connection.query(`SELECT * FROM \`Bus_stop\` WHERE \`route\`=${arrivalTime.route} AND \`is_reverse\`=${arrivalTime.isReverse}`)
        .then((rows) => {
            return taichung.mergeBusStatus(arrivalTime, rows);
        }).catch((err) => {
            console.log(err);
        }).finally(() => {
            db.releaseConnection(connection);
        });
    });
}

function saveBusArrival(data, connection) {
    //console.log(`saveBusArrival(${data}, ${connection})`);
    if (data.stopInfo && data.stopInfo.length <= 0) {
        db.releaseConnection(connection);
    } else if (connection === undefined) {
        return db.getConnection().then((connection) => {
            clearBusArrival(data, connection).then(() => {
                saveBusArrival(data, connection);
            });
        });
    } else {
        var stopInfo = data.stopInfo.pop();
        stopInfo.route = data.route;
        stopInfo.isReverse = data.isReverse;
        return saveBusArrivalEntry(stopInfo, connection).then(() => {
            saveBusArrival(data, connection);
        });
    }
}

function clearBusArrival(config, connection) {
    //debug(`clearBusArrival(${config.route}, ${config.isReverse})`);
    return connection.query(`DELETE FROM \`Bus_arrival\` WHERE \`route\`=${config.route} AND \`is_reverse\`=${config.isReverse}`)
    .catch((err) => {
        console.log(err);
    });
}

function saveBusArrivalEntry(data, connection) {
    //debug(`saveBusArrivalEntry({route: ${data.route}, sn: ${data.sn}, isReverse: ${data.isReverse}})`);
    //console.log(`saveBusArrivalEntry({route: ${data.route}, sn: ${data.sn}, isReverse: ${data.isReverse}})`);
    return connection.query(`INSERT INTO \`Bus_arrival\`(\`route\`, \`sn\`, \`is_reverse\`, \`prediction\`) VALUES (${data.route},${data.sn},${data.isReverse},'${util.toSqlTimestamp(data.nextBus.timestamp)}')`)
    .catch((err) => {
        console.log(err);
    });
}

function saveBusStatus(data, connection) {
    //console.log(`saveBusStatus(${data}, ${connection})`);
    if (data.busInfo === null || data.busInfo.length <= 0) {
        if (connection !== undefined) {
            db.releaseConnection(connection);
        }
    } else if (connection === undefined) {
        return db.getConnection().then((connection) => {
            return clearBusOutdated(connection).then(() => {
                saveBusStatus(data, connection);
            });
        });
    } else {
        var busData = data.busInfo.pop();
        busData.route = data.route;
        busData.isReverse = data.isReverse;

        return updateBusStatus(busData, connection).then(() => {
            saveBusStatus(data, connection);
        });
    }
}

function clearBusOutdated(connection) {
    //debug("clear outdated bus");
    return connection.query(`DELETE FROM \`Bus_status\` WHERE ABS(\`last_update\`-CURRENT_TIMESTAMP) > 1800`)
    .catch((err) => {console.log(err);});
}

/* ========================================================================================================= */

function updateBusStatus(data, connection) {
    //debug(`updateBusStatus(${data.plate_no})`);
    //console.log(`updateBusStatus(${data.plate_no})`);
    return connection.query(`INSERT INTO \`Bus_status\`(\`plate_number\`, \`route\`, \`closestStop\`, \`nextStop\`, \`longitude\`, \`latitude\`, \`is_reverse\`) \
    VALUES ('${data.plate_no}', ${data.route}, ${data.closestStop}, ${data.nextStop}, ${data.longitude}, ${data.latitude}, ${data.isReverse})`)
    .catch((err) => {
        if (err.code.includes('ER_DUP_ENTRY')) {
            connection.query(`UPDATE \`Bus_status\` SET \`plate_number\`='${data.plate_no}',\`route\`=${data.route},\`closestStop\`=${data.closestStop},\`nextStop\`=${data.nextStop},\`longitude\`=${data.longitude},\`latitude\`=${data.latitude},\`is_reverse\`=${data.isReverse},\`last_update\`=CURRENT_TIMESTAMP \
                            WHERE \`plate_number\`='${data.plate_no}'`);
        } else {
            console.log(err);
        }
    });
}

function updateRouteList() {
    return taichung.fetchRouteList().then((routeList) => {
        return saveRouteList();
    });
}

function saveRouteList(routeList, connection) {
    if (routeList.keys().length > 0 && connection === undefined) {
        return db.getConnection((connection) => {
            saveRouteList(routeNameList, connection);
        });
    } else if (routeList.keys().length > 0) {
        var key = routeList.keys()[0];
        var entry = routeList[key];
        entry.routeNo = key;
        delete routeList[key];
        return deleteRouteEntry(connection).then(() => {
            saveRouteEntry(entry, connection);
        }).then(() => {
            saveRouteList(routeList, connection);
        });
    } else {
        if (connection !== undefined) {
            db.releaseConnection(connection);
        } else {
            return;
        }
    }
}

function saveRouteEntry(entry, connection) {
    return deleteRouteEntry(connection).then(() => {
        connection.query(`INSERT INTO \`Route_info\` (\`route\`, \`name\`, \`start\`, \`end\`, \`map\`) ` +
            `VALUES ('${entry.routeNo}', '${entry.name}', '${entry.from}', '${entry.to}', '${entry.map}');`);
    }).then(() => {
        deleteRouteOperator(entry.routeNo, connection);
    }).then(() => {
        saveRouteOperator(entry.routeNo, entry.operator, connection);
    });
}

function deleteRouteEntry(connection) {
    return connection.query('DELETE FROM `Route_info`');
}

function saveRouteOperator(routeNo, operators, connection) {
    if (operator.length > 0) {
        //return connection.query(`INSERT INTO \`Route_operator\` (\`routeNo\`, \`operator\`) VALUES ('${routeNo}', '${}');`);
        var operatorName = operators.pop();
        return query(`INSERT INTO \`Route_operator\` (\`routeNo\`, \`operator \`) VALUES ('${routeNo}', 'operatorName')`).then(() => {
            saveRouteOperator(routeNo, operators, connection);
        });
    } else {
        return;
    }
}

function deleteRouteOperator(routeNo, connection) {
    return connection.query(`DELETE FROM \`Route_operator\` WHERE \`routeNo\`=${routeNo}`);
}

/* ========================================================================================================= */

function updateStopList(pos) {
    if (pos === undefined) {
        updateStopList(0);
    } else if (pos < routeToFetch.length) {
        debug(`Update stop list for route ${routeToFetch[pos].route} ${routeToFetch[pos].isReverse?'foward':'reverse'}`);
        taichung.fetchStopList({route:routeToFetch[pos].route, isReverse:routeToFetch[pos].isReverse}).then((data) => {
            saveStopList(data);
        });
        updateStopList(pos+1);
    }
}

function saveStopList(data, pos) {
    if (pos === undefined) {
        saveStopList(data, 0);
    } else if (pos < data.stops.length) {
        saveStopEntry(data.route, data.isReverse ? data.stops.length-pos : pos+1, data.isReverse, data.stops[pos].longitude, data.stops[pos].latitude, data.stops[pos].name).then(() => {
            saveStopList(data, pos+1);
        });
    }
}

function saveStopEntry(route, sn, isReverse, longitude, latitude, name) {
    // debug(`saveStopEntry(${route}, ${sn}, ${isReverse}, ${longitude}, ${latitude}, ${name})`)
    return db.query(   `INSERT INTO Bus_stop (route, sn, is_reverse, longitude, latitude, name) ` +
                `VALUES(${route}, ${sn}, ${isReverse}, ${longitude}, ${latitude}, "${name}") ` +
                `ON DUPLICATE KEY UPDATE longitude=${longitude}, latitude=${latitude}, name="${name}"`
    ).catch((err) => {
        console.log(err);
    });
}

module.exports.updateBusTable = updateBusTable;
module.exports.updateRealTime = updateRealTime;
module.exports.updateRouteList = updateRouteList;
module.exports.updateStopList = updateStopList;
<|endoftext|>


<commit_before>angular.module('mwUI.Utils')

  .service('mwUrlStorage', function ($rootScope, $location, $route) {
      var storage = {};
      var removeOnUrlChangekeys = [];
      var unbindRouteUpdate = null;
      var unbindLocationChangeStart = null;
      var originalReloadOnSearchValue = null;

      var preventRouteReload = function () {
        if (unbindRouteUpdate) {
          unbindRouteUpdate();
        }
        if (unbindLocationChangeStart) {
          unbindLocationChangeStart();
        }
        if (originalReloadOnSearchValue === null) {
          originalReloadOnSearchValue = $route.current.$$route.reloadOnSearch;
        }

        unbindRouteUpdate = $rootScope.$on('$routeUpdate', function () {
          unbindRouteUpdate();
          unbindRouteUpdate = null;
          var reloadPath = $location.absUrl();
          /*
           * For some reason when setting reloadOnSearch back to its original value a locationChange event is trigger
           * which has to be prevented otherwise the controller will be reinitialised
           */
          unbindLocationChangeStart = $rootScope.$on('$locationChangeStart', function (ev, newUrl) {
            unbindLocationChangeStart();
            if (newUrl === reloadPath) {
              ev.preventDefault();
            }
          });
          $route.current.$$route.reloadOnSearch = originalReloadOnSearchValue;
          originalReloadOnSearchValue = null;
        });

        // reloadOnSearch is set to false to angular will not reinitialise the controller again
        $route.current.$$route.reloadOnSearch = false;
      };

      var cleanUpInvalidQueryParams = function () {
        var removeKeys = _.clone(removeOnUrlChangekeys);
        removeKeys.forEach(function (key, index) {
          storage[key] = null;
          removeOnUrlChangekeys.splice(index, 1);
        });
      };

      var setUrlQueryParams = function () {
        var currentSearchParams = $location.search(),
          newSearchParams;

        newSearchParams = _.extend(currentSearchParams, storage);
        preventRouteReload();
        $location.search(newSearchParams);
        $location.replace();
      };

      $rootScope.$on('$routeChangeStart', function (ev, newRoute, oldRoute) {
        if (newRoute && oldRoute &&
          newRoute.originalPath !== oldRoute.originalPath) {
          if (removeOnUrlChangekeys.length > 0) {
            cleanUpInvalidQueryParams(newRoute.params);
          }
          setUrlQueryParams();
        }
      });

      return {
        /*
         * Return the value that is stored in the url for a key
         *
         * params:
         * key: string
         */
        getItem: function (key) {
          return $location.search()[key];
        },
        /*
         * Calls setItem but allows you to set a whole object instead of a key,value
         *
         * params:
         * obj: object
         * options: {
         *   removeOnUrlChange: boolean
         * }
         */
        setObject: function (obj, options) {
          options = options || {};
          var wasChanged = false;
          if (_.isObject(obj)) {
            for (var key in obj) {
              if (obj.hasOwnProperty(key) && storage[key] !== obj[key]) {
                if (options.removeOnUrlChange && removeOnUrlChangekeys.indexOf(key) === -1) {
                  removeOnUrlChangekeys.push(key);
                }
                storage[key] = obj[key];
                wasChanged = true;
              }
            }
          } else {
            throw new Error('[mwUrlStorage] parameter has to be an object otherwise setItem(key, val) should be called ');
          }

          if (wasChanged) {
            setUrlQueryParams();
          }
        },
        /*
         * Save a key value pair as query param in the url
         * The query will be in the url until you call removeItem('key') also on url change
         * You don't have to worry about the param it will just stay
         *
         * In cases where you don't wan't to keep the query param in the url forever you can set the options param
         * removeOnUrlChange to true (default is false
         *
         * The query won't be stored in the url history so when using the back button you will not go back to the previous
         * query state but to the previous url
         *
         * params:
         * key: string
         * value: string
         * options: {
         *   removeOnUrlChange: boolean
         * }
         */
        setItem: function (key, value, options) {
          options = options || {};
          if (storage[key] !== value) {
            if (options.removeOnUrlChange && removeOnUrlChangekeys.indexOf(key) === -1) {
              removeOnUrlChangekeys.push(key);
            }
            storage[key] = value;
            setUrlQueryParams();
          }
        },
        /*
         * Calls removeItem but allows you to remove a whole object instead of a key
         *
         * params:
         * obj: object
         */
        removeObject: function (obj) {
          var wasChanged = false;
          if (_.isObject(obj)) {
            for (var key in obj) {
              if (obj.hasOwnProperty(key)) {
                if (storage[key] || this.getItem(key)) {
                  storage[key] = null;
                  wasChanged = true;
                }
                var removeOnUrlChangeKeyIndex = removeOnUrlChangekeys.indexOf(key);
                if (removeOnUrlChangeKeyIndex !== -1) {
                  removeOnUrlChangekeys.slice(removeOnUrlChangeKeyIndex, 1);
                }
              }
            }
          } else {
            throw new Error('[mwUrlStorage] parameter has to be an object otherwise deleteItem(key, val) should be called ');
          }

          if (wasChanged) {
            setUrlQueryParams();
          }
        },
        /*
         * Removes a key with its value from the url
         *
         * params:
         * key: string
         */
        removeItem: function (key) {
          var removeOnUrlChangeKeyIndex = removeOnUrlChangekeys.indexOf(key);
          if (removeOnUrlChangeKeyIndex !== -1) {
            removeOnUrlChangekeys.slice(removeOnUrlChangeKeyIndex, 1);
          }
          if (storage[key]) {
            storage[key] = null;
            setUrlQueryParams();
            return true;
          } else {
            return false;
          }
        },
        /*
         * Removes all items that have been set by setItem or setObject from the url
         * Other url query params that have been set e.g. by calling $location.search() won't be affected
         */
        clear: function () {
          storage = {};
          removeOnUrlChangekeys = [];
          setUrlQueryParams();
        }
      };
    }
  )
;<commit_msg>Do nothing in case there is no route definition<commit_after>angular.module('mwUI.Utils')

  .service('mwUrlStorage', function ($rootScope, $location, $route) {
      var storage = {};
      var removeOnUrlChangekeys = [];
      var unbindRouteUpdate = null;
      var unbindLocationChangeStart = null;
      var originalReloadOnSearchValue = null;

      var preventRouteReload = function () {
        // In case the path has not route definition do nothing
        if (!$route.current || !$route.current.$$route) {
          return;
        }
        if (unbindRouteUpdate) {
          unbindRouteUpdate();
        }
        if (unbindLocationChangeStart) {
          unbindLocationChangeStart();
        }
        if (originalReloadOnSearchValue === null) {
          originalReloadOnSearchValue = $route.current.$$route.reloadOnSearch;
        }

        unbindRouteUpdate = $rootScope.$on('$routeUpdate', function () {
          unbindRouteUpdate();
          unbindRouteUpdate = null;
          var reloadPath = $location.absUrl();
          /*
           * For some reason when setting reloadOnSearch back to its original value a locationChange event is trigger
           * which has to be prevented otherwise the controller will be reinitialised
           */
          unbindLocationChangeStart = $rootScope.$on('$locationChangeStart', function (ev, newUrl) {
            unbindLocationChangeStart();
            if (newUrl === reloadPath) {
              ev.preventDefault();
            }
          });
          $route.current.$$route.reloadOnSearch = originalReloadOnSearchValue;
          originalReloadOnSearchValue = null;
        });

        // reloadOnSearch is set to false to angular will not reinitialise the controller again
        $route.current.$$route.reloadOnSearch = false;
      };

      var cleanUpInvalidQueryParams = function () {
        var removeKeys = _.clone(removeOnUrlChangekeys);
        removeKeys.forEach(function (key, index) {
          storage[key] = null;
          removeOnUrlChangekeys.splice(index, 1);
        });
      };

      var setUrlQueryParams = function () {
        var currentSearchParams = $location.search(),
          newSearchParams;

        newSearchParams = _.extend(currentSearchParams, storage);
        preventRouteReload();
        $location.search(newSearchParams);
        $location.replace();
      };

      $rootScope.$on('$routeChangeStart', function (ev, newRoute, oldRoute) {
        if (newRoute && oldRoute &&
          newRoute.originalPath !== oldRoute.originalPath) {
          if (removeOnUrlChangekeys.length > 0) {
            cleanUpInvalidQueryParams(newRoute.params);
          }
          setUrlQueryParams();
        }
      });

      return {
        /*
         * Return the value that is stored in the url for a key
         *
         * params:
         * key: string
         */
        getItem: function (key) {
          return $location.search()[key];
        },
        /*
         * Calls setItem but allows you to set a whole object instead of a key,value
         *
         * params:
         * obj: object
         * options: {
         *   removeOnUrlChange: boolean
         * }
         */
        setObject: function (obj, options) {
          options = options || {};
          var wasChanged = false;
          if (_.isObject(obj)) {
            for (var key in obj) {
              if (obj.hasOwnProperty(key) && storage[key] !== obj[key]) {
                if (options.removeOnUrlChange && removeOnUrlChangekeys.indexOf(key) === -1) {
                  removeOnUrlChangekeys.push(key);
                }
                storage[key] = obj[key];
                wasChanged = true;
              }
            }
          } else {
            throw new Error('[mwUrlStorage] parameter has to be an object otherwise setItem(key, val) should be called ');
          }

          if (wasChanged) {
            setUrlQueryParams();
          }
        },
        /*
         * Save a key value pair as query param in the url
         * The query will be in the url until you call removeItem('key') also on url change
         * You don't have to worry about the param it will just stay
         *
         * In cases where you don't wan't to keep the query param in the url forever you can set the options param
         * removeOnUrlChange to true (default is false
         *
         * The query won't be stored in the url history so when using the back button you will not go back to the previous
         * query state but to the previous url
         *
         * params:
         * key: string
         * value: string
         * options: {
         *   removeOnUrlChange: boolean
         * }
         */
        setItem: function (key, value, options) {
          options = options || {};
          if (storage[key] !== value) {
            if (options.removeOnUrlChange && removeOnUrlChangekeys.indexOf(key) === -1) {
              removeOnUrlChangekeys.push(key);
            }
            storage[key] = value;
            setUrlQueryParams();
          }
        },
        /*
         * Calls removeItem but allows you to remove a whole object instead of a key
         *
         * params:
         * obj: object
         */
        removeObject: function (obj) {
          var wasChanged = false;
          if (_.isObject(obj)) {
            for (var key in obj) {
              if (obj.hasOwnProperty(key)) {
                if (storage[key] || this.getItem(key)) {
                  storage[key] = null;
                  wasChanged = true;
                }
                var removeOnUrlChangeKeyIndex = removeOnUrlChangekeys.indexOf(key);
                if (removeOnUrlChangeKeyIndex !== -1) {
                  removeOnUrlChangekeys.slice(removeOnUrlChangeKeyIndex, 1);
                }
              }
            }
          } else {
            throw new Error('[mwUrlStorage] parameter has to be an object otherwise deleteItem(key, val) should be called ');
          }

          if (wasChanged) {
            setUrlQueryParams();
          }
        },
        /*
         * Removes a key with its value from the url
         *
         * params:
         * key: string
         */
        removeItem: function (key) {
          var removeOnUrlChangeKeyIndex = removeOnUrlChangekeys.indexOf(key);
          if (removeOnUrlChangeKeyIndex !== -1) {
            removeOnUrlChangekeys.slice(removeOnUrlChangeKeyIndex, 1);
          }
          if (storage[key]) {
            storage[key] = null;
            setUrlQueryParams();
            return true;
          } else {
            return false;
          }
        },
        /*
         * Removes all items that have been set by setItem or setObject from the url
         * Other url query params that have been set e.g. by calling $location.search() won't be affected
         */
        clear: function () {
          storage = {};
          removeOnUrlChangekeys = [];
          setUrlQueryParams();
        }
      };
    }
  )
;<|endoftext|>


<commit_before>var site = document.URL;
site = site.substring(site.indexOf("?"));
site = site.substring(site.indexOf("=") + 1);
$("#blockMessage").text(site + " has been Blacklisted.");

var content = $("#countdown");
var i = 15;
var interval = 0;

function updateCountdown() {
  content.text("Unlisting " + site + " in " + --i + " seconds...");
  if (i == 0) {
    clearInterval(interval);
    chrome.tabs.getCurrent(function(tab) {
      chrome.extension.getBackgroundPage().unlistSite(tab.id, site);
    });
    window.location = site;
  }
}

function beginCountdown() {
  i = 15;
  content.text("Unlisting " + site + " in " + i + " seconds...");
  interval = setInterval(function() {updateCountdown(i)}, 1000);
}

function modalHidden() {
  clearInterval(interval);
}

function hideModal() {
  $("#unlistModal").modal("hide");
}

$("#unlistModal").on('hidden', modalHidden);
$("#cancelUnlist").click(hideModal);

chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
  chrome.tabs.getCurrent(function(tab) {
    if (tab.id == message) {
      $("#unlistModal").modal("show");
      beginCountdown();
    }
  });
});<commit_msg>Added metric tracking ajax post<commit_after>var site = document.URL;
site = site.substring(site.indexOf("?"));
site = site.substring(site.indexOf("=") + 1);
$("#blockMessage").text(site + " has been Blacklisted.");

var content = $("#countdown");
var i = 15;
var interval = 0;

$.ajax({
  url: "http://tranquil-lowlands-5473.herokuapp.com/",
  type: "POST",
  data: {
    "page[base_url]": site
  },
}).done(function(data) {
});

function updateCountdown() {
  content.text("Unlisting " + site + " in " + --i + " seconds...");
  if (i == 0) {
    clearInterval(interval);
    chrome.tabs.getCurrent(function(tab) {
      chrome.extension.getBackgroundPage().unlistSite(tab.id, site);
    });
    window.location = site;
  }
}

function beginCountdown() {
  i = 15;
  content.text("Unlisting " + site + " in " + i + " seconds...");
  interval = setInterval(function() {updateCountdown(i)}, 1000);
}

function modalHidden() {
  clearInterval(interval);
}

function hideModal() {
  $("#unlistModal").modal("hide");
}

$("#unlistModal").on('hidden', modalHidden);
$("#cancelUnlist").click(hideModal);

chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
  chrome.tabs.getCurrent(function(tab) {
    if (tab.id == message) {
      $("#unlistModal").modal("show");
      beginCountdown();
    }
  });
});

<|endoftext|>


<commit_before>var Document = Backbone.Model.extend({
  urlRoot: '/api/documents/'
});
var Person = Backbone.Model.extend({
  urlRoot: "/api/people/"
});
var Paragraph = Backbone.Model.extend({});
var Register = Backbone.Model.extend({
  urlRoot: "/api/registers/",
  loadValues: function(info) {
    var that = this;
    _.each(info, function(value, key){
      that.set(key, value, {silent: true});
    });
    return that;
  },
  validate: function(attribs){
    console.log(attribs);
    if(attribs.who.length === 0) {
      return "There must be a who";
    } else if (attribs.when.length === 0) {
      return "There must be a when";
    } else if (attribs.where.length === 0) {
      return "There must be a where";
    } else if (attribs.what === null) {
      return "There must be a what";
    }
  },
  defaults: {
    who    : [],
    where  : [],
    when   : [],
    to_who : [],
    what   : []
  }
});
var ParagraphList = Backbone.Collection.extend({
  model: Paragraph,
  url: function(){
    return '/api/documents/' + this.get("document_id");
  }
});

<commit_msg>addresses are not required anymore<commit_after>var Document = Backbone.Model.extend({
  urlRoot: '/api/documents/'
});
var Person = Backbone.Model.extend({
  urlRoot: "/api/people/"
});
var Paragraph = Backbone.Model.extend({});
var Register = Backbone.Model.extend({
  urlRoot: "/api/registers/",
  loadValues: function(info) {
    var that = this;
    _.each(info, function(value, key){
      that.set(key, value, {silent: true});
    });
    return that;
  },
  validate: function(attribs){
    console.log(attribs);
    if(attribs.who.length === 0) {
      return "There must be a who";
    } else if (attribs.when.length === 0) {
      return "There must be a when";
    } else if (attribs.what === null) {
      return "There must be a what";
    }
  },
  defaults: {
    who    : [],
    where  : [],
    when   : [],
    to_who : [],
    what   : []
  }
});
var ParagraphList = Backbone.Collection.extend({
  model: Paragraph,
  url: function(){
    return '/api/documents/' + this.get("document_id");
  }
});

<|endoftext|>


<commit_before>// =================================================================================
//  Email addresses (@outlook.com)  ||  Heroku app range (ghostip{n}.herokuapp.com)
// =================================================================================
//  ghostip                         ||     41 - 45
//  ghostip1                        ||     36 - 40
//  ghostip2                        ||     21 - 25
//  ghostip3                        ||      1 -  5
//  ghostip4                        ||      6   10
//  ghostip5                        ||     11 - 15
//  ghostip6                        ||     16 - 20
//  ghostip7                        ||     26 - 30
//  ghostip8                        ||     31 - 35
//  ghostip9                        ||     46 - 50
//  ghostip10                       ||     51 - 55
// ==================================================================================
// palingramblog@gmail.com
// Henry4jenkins@gmail.com
// codemuhammed@outlook.com
// palingram@outlook.com
// These are other heroku accounts.

module.exports = () => {
    const request = require('request');
    const curl = require('curlrequest');
    const EventEmitter = require('events').EventEmitter;
    const moduleEvents = new EventEmitter;

    //The proxy to be used to get more proxies
    let proxy = "";

    // @method recursive getIp
	(function getIp() {
        let options = {
            url: 'http://gimmeproxy.com/api/getProxy',
            retries: 1,
            timeout: 10,
            headers: {
                'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36',
            },
            proxy: proxy
        };

        curl.request(options, (err, res) => {
            if(err) {
                console.log(err);
                // @TODO change proxy
                getIp();
            } else {
                 try {
                    const raw = JSON.parse(res);
                    if(raw.curl) {
                        moduleEvents.emit('ip', raw.curl);
                        return getIp();
                    } else {
                        setTimeout(() => {
                            // @TODO change proxy
                            getIp();
                        }, 10000);
                    }
                }
                catch (err) {
                    setTimeout(() => {
                        // @TODO change proxy
                        getIp();
                    }, 10000);
                }
            }
        });
	})();

	return moduleEvents;
};
<commit_msg>Fixed build-pack error<commit_after>// =================================================================================
//  Email addresses (@outlook.com)  ||  Heroku app range (ghostip{n}.herokuapp.com)
// =================================================================================
//  ghostip                         ||     41 - 45
//  ghostip1                        ||     36 - 40
//  ghostip2                        ||     21 - 25
//  ghostip3                        ||      1 -  5
//  ghostip4                        ||      6   10
//  ghostip5                        ||     11 - 15
//  ghostip6                        ||     16 - 20
//  ghostip7                        ||     26 - 30
//  ghostip8                        ||     31 - 35
//  ghostip9                        ||     46 - 50
//  ghostip10                       ||     51 - 55
// ==================================================================================
// palingramblog@gmail.com
// Henry4jenkins@gmail.com
// codemuhammed@outlook.com
// palingram@outlook.com
// These are other heroku accounts.

module.exports = () => {
    const request = require('request');
    const curl = require('curlrequest');
    const EventEmitter = require('events').EventEmitter;
    const moduleEvents = new EventEmitter;

    //The proxy to be used to get more proxies
    let proxy = "";

    // @method recursive getIp
	(function getIp() {
        let options = {
            url: 'http://gimmeproxy.com/api/getProxy',
            retries: 1,
            timeout: 10,
            headers: {
                'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36',
            },
            proxy: proxy
        };

        curl.request(options, (err, res) => {
            if(err) {
                console.log(err);
                // @TODO change proxy
                getIp();
            } else {
                 try {
                    const raw = JSON.parse(res);
                    if(raw.curl) {
                        moduleEvents.emit('ip', raw.curl);
                        return getIp();
                    } else {
                        setTimeout(() => {
                            console.log('here no ip 1');
                            // @TODO change proxy
                            getIp();
                        }, 10000);
                    }
                }
                catch (err) {
                    setTimeout(() => {
                        // @TODO change proxy
                        console.log('here no ip 2');
                        getIp();
                    }, 10000);
                }
            }
        });
	})();

	return moduleEvents;
};
<|endoftext|>


<commit_before>define([
    'streamhub-sdk/modal',
    'streamhub-sdk/content/views/content-list-view',
    'streamhub-hot-collections/streams/collection-to-heat-metric',
    'text!streamhub-map/css/style.css',
    'streamhub-sdk/jquery',
    'streamhub-map/leaflet',
    'inherits'
], function (
    ModalView,
    ContentListView,
    CollectionToHeatMetric,
    MapViewCss,
    $,
    L,
    inherits
) {
    'use strict';

    var STYLE_EL;
    var SVG_TEMPLATES_EL;

    /**
     * A view to visualize StreamHub content on a map
     * @constructor
     * @param [opts] {Object} Configuration options for the MapView
     * @param [opts.leafletMapOptions] {Object} An object representing the options passed into the creation of a Leaflet Map (L.Map)
     * @param [opts.cloudmadeStyleId] {Number} The style id of the CloudMade map tile theme
     */
    var MapView = function (opts) {
        opts = opts || {};

        this._id = new Date().getTime();
        this._cloudmadeStyleId = opts.cloudmadeStyleId || 998;
        this._leafletMapOptions = opts.leafletMapOptions || {};

        this._overlayViews = [];
        this._dataPoints = [];
        this.elId = this.elClass+'-'+this._id;

        ContentListView.call(this, opts);

        if (!STYLE_EL) {
            $('<style id="'+this.elId+'-style"></style>')
                .text('.'+this.elId+MapViewCss)
                .prependTo('head');
        }

        this._drawMap();
    };
    inherits(MapView, ContentListView);

    MapView.prototype.mapClassName = 'hub-map';
    MapView.prototype.mapOverlayLayerClassName = 'hub-map-overlays';
    MapView.prototype.mapLayerClassName = 'hub-map-layer';
    MapView.prototype.mapLandClassName = 'hub-map-land';
    MapView.prototype.mapWaterClassName = 'hub-map-water';
    MapView.prototype.mapGraticuleClassName = 'hub-map-graticule';
    MapView.prototype.mapSvgTemplatesClassName = 'hub-map-svg-templates';
    MapView.prototype.elClass = 'hub-map-view';

    MapView.prototype.setElement = function (el) {
        ContentListView.prototype.setElement.call(this, el);
        this.$el.addClass(this.elId);

        var self = this;

        this.$el.on('imageError.hub', function (e) {
            var dataPoint;
            for (var i=0; i < self._dataPoints.length; i++) {
                if ($(e.target).attr('src') == self._dataPoints[i].getContent().attachments[0].thumbnail_url) {
                    dataPoint = self._dataPoints[i];
                }
            }
            self._removeDataPoint(dataPoint);
        });

        this.$el.on('addDataPoint.hub', function (e, dataPoint) {
            self._drawMarker(dataPoint);
        });
    };

    MapView.prototype._drawMarker = function (dataPoint) {
        var marker = this._createMarker(dataPoint);
        this._addMarkerToMap(marker);
        return marker;
    };

    MapView.prototype._createMarker = function (dataPoint) {
        return new L.Marker(this._getLatLngFromPoint(dataPoint));
    };

    MapView.prototype._addMarkerToMap = function (marker) {
        marker.addTo(this._map);
    };

    MapView.prototype._getLatLngFromPoint = function (point) {
        return new L.LatLng(point.lat, point.lon);
    };

    MapView.prototype._getMapDimensions = function () {
        this._width = this.$el.width();
        this._height = this.$el.height();
        return { width: this._width, height: this._height };
    };

    /**
     * Add a layer that may contain any number of views
     * @param name {String} The name of the layer. Adds a className on the
     *                      associated svg:g element.
     * @returns {SVGGElement} The svg:g element representing the layer
     */
    MapView.prototype.addLayer = function (name) {
        return this._mapContext.svg.append('svg:g')
            .attr('class', this.mapLayerClassName)
            .attr('class', name);
    };

    MapView.prototype._addDataPoint = function (dataPoint) {
        this._dataPoints.push(dataPoint);
        this.$el.trigger('addDataPoint.hub', dataPoint);
    };

    MapView.prototype._removeDataPoint = function (dataPoint) {
        var index = this._dataPoints.indexOf(dataPoint);
        if (index >= 0) {
            this._dataPoints.splice(index, 1);
            this.$el.trigger('removeDataPoint.hub');
        }
    };

    MapView.prototype._createOverlayView = (function () {
        var maxMetricValue = 0;

        return function (dataPoint) {
            var overlayView;

            // Check if it is cluster set
            if (dataPoint.length) {
                if (dataPoint.length > 1) {
                    overlayView = this._overlayViewFactory.createOverlayView(dataPoint);
                } else {
                    overlayView = this._overlayViewFactory.createOverlayView(dataPoint[0]);
                }
                return overlayView;
            }

            // Check if it is not a CollectionPoint
            if (! this.isCollectionPoint(dataPoint)) {
                overlayView = this._overlayViewFactory.createOverlayView(dataPoint);
                return overlayView;
            }

            // Otherwise it's a CollectionPoint
            var collection = dataPoint.getCollection();
            var metric = CollectionToHeatMetric.transform(collection);
            var metricValue = metric.getValue();
            if (metricValue > maxMetricValue) {
                maxMetricValue = metricValue;
            }
            overlayView = new SymbolView(dataPoint, {
                mapContext: this._mapContext,
                maxMetricValue: function () { return maxMetricValue; },
                notifyStream: dataPoint.getCollection()
            });

            return overlayView;
        };
    }());

    /**
     * A helper function to check whether a Point instance is of type CollectionPoint
     * @param dataPoint {Point} The point instance to be checked
     */
    MapView.prototype.isCollectionPoint = function (dataPoint) {
        return dataPoint._collection !== undefined;
    };

    MapView.prototype._drawMap = function () {
        this._map = new L.Map(this.el, this._leafletMapOptions).setView(
            this._leafletMapOptions.center || [0,0],
            this._leafletMapOptions.zoom || 2
        );

        new L.TileLayer("http://{s}.tile.cloudmade.com/9f4a9cd9d242456794a775abb4e765e1/"+this._cloudmadeStyleId+"/256/{z}/{x}/{y}.png").addTo(this._map);
    };

    /**
     * Add a Point instance to be visualized on the map
     * @param dataPoint {Point} The point to be visualized on the map
     */
    MapView.prototype.add = function (point) {
        this._addDataPoint(point);
    };

    return MapView;
});
<commit_msg>Only add marker to map if #createMarker returns a marker instance<commit_after>define([
    'streamhub-sdk/modal',
    'streamhub-sdk/content/views/content-list-view',
    'streamhub-hot-collections/streams/collection-to-heat-metric',
    'text!streamhub-map/css/style.css',
    'streamhub-sdk/jquery',
    'streamhub-map/leaflet',
    'inherits'
], function (
    ModalView,
    ContentListView,
    CollectionToHeatMetric,
    MapViewCss,
    $,
    L,
    inherits
) {
    'use strict';

    var STYLE_EL;
    var SVG_TEMPLATES_EL;

    /**
     * A view to visualize StreamHub content on a map
     * @constructor
     * @param [opts] {Object} Configuration options for the MapView
     * @param [opts.leafletMapOptions] {Object} An object representing the options passed into the creation of a Leaflet Map (L.Map)
     * @param [opts.cloudmadeStyleId] {Number} The style id of the CloudMade map tile theme
     */
    var MapView = function (opts) {
        opts = opts || {};

        this._id = new Date().getTime();
        this._cloudmadeStyleId = opts.cloudmadeStyleId || 998;
        this._leafletMapOptions = opts.leafletMapOptions || {};

        this._overlayViews = [];
        this._dataPoints = [];
        this.elId = this.elClass+'-'+this._id;

        ContentListView.call(this, opts);

        if (!STYLE_EL) {
            $('<style id="'+this.elId+'-style"></style>')
                .text('.'+this.elId+MapViewCss)
                .prependTo('head');
        }

        this._drawMap();
    };
    inherits(MapView, ContentListView);

    MapView.prototype.mapClassName = 'hub-map';
    MapView.prototype.mapOverlayLayerClassName = 'hub-map-overlays';
    MapView.prototype.mapLayerClassName = 'hub-map-layer';
    MapView.prototype.mapLandClassName = 'hub-map-land';
    MapView.prototype.mapWaterClassName = 'hub-map-water';
    MapView.prototype.mapGraticuleClassName = 'hub-map-graticule';
    MapView.prototype.mapSvgTemplatesClassName = 'hub-map-svg-templates';
    MapView.prototype.elClass = 'hub-map-view';

    MapView.prototype.setElement = function (el) {
        ContentListView.prototype.setElement.call(this, el);
        this.$el.addClass(this.elId);

        var self = this;

        this.$el.on('imageError.hub', function (e) {
            var dataPoint;
            for (var i=0; i < self._dataPoints.length; i++) {
                if ($(e.target).attr('src') == self._dataPoints[i].getContent().attachments[0].thumbnail_url) {
                    dataPoint = self._dataPoints[i];
                }
            }
            self._removeDataPoint(dataPoint);
        });

        this.$el.on('addDataPoint.hub', function (e, dataPoint) {
            self._drawMarker(dataPoint);
        });
    };

    MapView.prototype._drawMarker = function (dataPoint) {
        var marker = this._createMarker(dataPoint);
        if (marker) {
            this._addMarkerToMap(marker);
        }
        return marker;
    };

    MapView.prototype._createMarker = function (dataPoint) {
        return new L.Marker(this._getLatLngFromPoint(dataPoint));
    };

    MapView.prototype._addMarkerToMap = function (marker) {
        marker.addTo(this._map);
    };

    MapView.prototype._getLatLngFromPoint = function (point) {
        return new L.LatLng(point.lat, point.lon);
    };

    MapView.prototype._getMapDimensions = function () {
        this._width = this.$el.width();
        this._height = this.$el.height();
        return { width: this._width, height: this._height };
    };

    /**
     * Add a layer that may contain any number of views
     * @param name {String} The name of the layer. Adds a className on the
     *                      associated svg:g element.
     * @returns {SVGGElement} The svg:g element representing the layer
     */
    MapView.prototype.addLayer = function (name) {
        return this._mapContext.svg.append('svg:g')
            .attr('class', this.mapLayerClassName)
            .attr('class', name);
    };

    MapView.prototype._addDataPoint = function (dataPoint) {
        this._dataPoints.push(dataPoint);
        this.$el.trigger('addDataPoint.hub', dataPoint);
    };

    MapView.prototype._removeDataPoint = function (dataPoint) {
        var index = this._dataPoints.indexOf(dataPoint);
        if (index >= 0) {
            this._dataPoints.splice(index, 1);
            this.$el.trigger('removeDataPoint.hub');
        }
    };

    MapView.prototype._createOverlayView = (function () {
        var maxMetricValue = 0;

        return function (dataPoint) {
            var overlayView;

            // Check if it is cluster set
            if (dataPoint.length) {
                if (dataPoint.length > 1) {
                    overlayView = this._overlayViewFactory.createOverlayView(dataPoint);
                } else {
                    overlayView = this._overlayViewFactory.createOverlayView(dataPoint[0]);
                }
                return overlayView;
            }

            // Check if it is not a CollectionPoint
            if (! this.isCollectionPoint(dataPoint)) {
                overlayView = this._overlayViewFactory.createOverlayView(dataPoint);
                return overlayView;
            }

            // Otherwise it's a CollectionPoint
            var collection = dataPoint.getCollection();
            var metric = CollectionToHeatMetric.transform(collection);
            var metricValue = metric.getValue();
            if (metricValue > maxMetricValue) {
                maxMetricValue = metricValue;
            }
            overlayView = new SymbolView(dataPoint, {
                mapContext: this._mapContext,
                maxMetricValue: function () { return maxMetricValue; },
                notifyStream: dataPoint.getCollection()
            });

            return overlayView;
        };
    }());

    /**
     * A helper function to check whether a Point instance is of type CollectionPoint
     * @param dataPoint {Point} The point instance to be checked
     */
    MapView.prototype.isCollectionPoint = function (dataPoint) {
        return dataPoint._collection !== undefined;
    };

    MapView.prototype._drawMap = function () {
        this._map = new L.Map(this.el, this._leafletMapOptions).setView(
            this._leafletMapOptions.center || [0,0],
            this._leafletMapOptions.zoom || 2
        );

        new L.TileLayer("http://{s}.tile.cloudmade.com/9f4a9cd9d242456794a775abb4e765e1/"+this._cloudmadeStyleId+"/256/{z}/{x}/{y}.png").addTo(this._map);
    };

    /**
     * Add a Point instance to be visualized on the map
     * @param dataPoint {Point} The point to be visualized on the map
     */
    MapView.prototype.add = function (point) {
        this._addDataPoint(point);
    };

    return MapView;
});
<|endoftext|>