<commit_before>/*
 chatter happens every second, based on the results of Test,
 events happens when webModel is updated with the listed entry.

 repeatInterval - How often it will repeat this set of things to say
 spacing - How many seconds of nothing said from any entry must have elapsed before saying this entry.
 Test - Must be a function that returns true or false.

 repeat -  under "thingToSay" tells the randomizer whether to
    randomly pick from the entire list on every run,
    or to "mark them off" one by one and only pick from the remaining entries
    on each run until they are all gone and restart.
   If you say false, the "weight" doesn't mean much either, as it basically just indicates which one will be picked first.
 weight - Weight for how likely an entry is to be chosen.

 repeatInterval & spacing are typically 0 for events, as presumably you want it to chatter on every instance of an event, but maybe not always.
 Test only applies to chatter, as events are spoken when the event happens no matter what.

 Currently "chatter" just has the random noises,
 but in theory it could have different sections that only operate,
 under specific circumstances.
 Like: Only say these things while navigating to a waypoint,
 and only these when making a map,
 and only these when plugged in doing nothing.
 Etc.
 The "Test" should make that easy.

 Just remember that point in time event triggered speech need to happen in 'events', not 'chatter'.
 */

// TODO: SOME of these may be better off set within the feature that runs them,
//       in order to clearly only announce an action when it truly happens,
//       not just when it is requested.

// Received even when goal is set via Rviz:
// goalStatus: New Goal Set.
// goalStatus: Goal reached.

const speechModel = {
  chatter: {
    randomChatter: {
      repeatInterval: 600,
      spacing: 300,
      Test: () => {
        return true;
      },
      thingsToSay: {
        repeat: false,
        'CastleInTheSky-Ding': {
          text: '~/.arlobot/sounds/CastleInTheSky-Ding.wav',
          weight: 100,
        },
        'CastleInTheSky-Drip2': {
          text: '~/.arlobot/sounds/CastleInTheSky-Drip2.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep2a': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep2a.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep2b': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep2b.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep2c': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep2c.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep3a': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep3a.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep3b': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep3b.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep3c': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep3c.wav',
          weight: 100,
        },
        'CastleInTheSky-Zap': {
          text: '~/.arlobot/sounds/CastleInTheSky-Zap.wav',
          weight: 100,
        },
      },
    },
  },
  events: {
    goalStatus: {
      repeatInterval: 10,
      spacing: 0,
      newGoalSet: {
        thingsToSay: {
          repeat: true,
          onMyWay: {
            text: 'On my way!',
            weight: 100,
          },
        },
      },
      goalReached: {
        thingsToSay: {
          repeat: true,
          iHaveArrived: {
            text: 'I have arrived.',
            weight: 100,
          },
          destinationReached: {
            text: 'Destination reached..',
            weight: 100,
          },
        },
      },
    },
    Escaping: {
      repeatInterval: 15,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          WallEwhoa: {
            text: '~/.arlobot/sounds/whoa.wav',
            weight: 100,
          },
        },
      },
    },
    ROSstart: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          ROSstart: {
            text: 'Starting ROS.',
            weight: 100,
          },
        },
      },
    },
    ROSisRunning: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          ROSisRunning: {
            text: 'ROS is running.',
            weight: 100,
          },
        },
      },
    },
    makeMap: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          makeMap: {
            text: 'Preparing to make map.',
            weight: 100,
          },
        },
      },
    },
    makeMapRunning: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          makeMapRunning: {
            text: 'Making Map.',
            weight: 100,
          },
        },
      },
    },
    loadMapRequested: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          loadMapRequested: {
            text: 'Loading map.',
            weight: 100,
          },
        },
      },
    },
    mapLoaded: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          mapLoaded: {
            text: 'Map Loaded.',
            weight: 100,
          },
        },
      },
    },
    haltRobot: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          WallEwhoa: {
            text: '~/.arlobot/sounds/whoa.wav',
            weight: 100,
          },
        },
      },
    },
    cameraOn: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          iSeeYou: {
            text: '~/.arlobot/sounds/I-see-you.wav',
            weight: 100,
          },
        },
      },
    },
    pluggedIn: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          thankYou: {
            text: 'Thank you for plugging me in.',
            weight: 100,
          },
        },
      },
      false: {
        thingsToSay: {
          repeat: true,
          depressed: {
            text: 'I suddenly feel very depressed.',
            weight: 10,
          },
          thankYou: {
            text: 'Thank you for unplugging me.',
            weight: 90,
          },
        },
      },
    },
    laptopFullyCharged: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          True: {
            text: 'That was refreshing.',
            weight: 40,
          },
          True2: {
            text: "I'm ready to go!",
            weight: 60,
          },
        },
      },
      false: {
        thingsToSay: {
          repeat: true,
          unDone: {
            text: 'Starting to get tired.',
            weight: 100,
          },
        },
      },
    },
    mostRecentArrival: {
      repeatInterval: 0,
      spacing: 0,
      Here: {
        thingsToSay: {
          repeat: true,
          imHere: {
            text: "I'm here.",
            weight: 10,
          },
          yesMaster: {
            text: 'Yes Master?',
            weight: 90,
          },
        },
      },
      initial: {
        thingsToSay: {
          repeat: true,
          home: {
            text: 'Home sweet home.',
            weight: 60,
          },
          familiar: {
            text: 'This place looks vaguely familiar.',
            weight: 40,
          },
        },
      },
    },
  },
};

// Sting Keys cannot be set on object literals, so setting them here:
speechModel.events.goalStatus['New Goal Set.'] =
  speechModel.events.goalStatus.newGoalSet;
speechModel.events.goalStatus['Goal reached.'] =
  speechModel.events.goalStatus.goalReached;

module.exports = speechModel;
<commit_msg>Clean up speech model to be more generic.<commit_after>/*
 chatter happens every second, based on the results of Test,
 events happens when webModel is updated with the listed entry.

 repeatInterval - How often it will repeat this set of things to say
 spacing - How many seconds of nothing said from any entry must have elapsed before saying this entry.
 Test - Must be a function that returns true or false.

 repeat -  under "thingToSay" tells the randomizer whether to
    randomly pick from the entire list on every run,
    or to "mark them off" one by one and only pick from the remaining entries
    on each run until they are all gone and restart.
   If you say false, the "weight" doesn't mean much either, as it basically just indicates which one will be picked first.
 weight - Weight for how likely an entry is to be chosen.

 repeatInterval & spacing are typically 0 for events, as presumably you want it to chatter on every instance of an event, but maybe not always.
 Test only applies to chatter, as events are spoken when the event happens no matter what.

 Currently "chatter" just has the random noises,
 but in theory it could have different sections that only operate,
 under specific circumstances.
 Like: Only say these things while navigating to a waypoint,
 and only these when making a map,
 and only these when plugged in doing nothing.
 Etc.
 The "Test" should make that easy.

 Just remember that point in time event triggered speech need to happen in 'events', not 'chatter'.
 */

// Received even when goal is set via Rviz:
// goalStatus: New Goal Set.
// goalStatus: Goal reached.

const speechModel = {
  chatter: {
    randomChatter: {
      repeatInterval: 600,
      spacing: 300,
      Test: () => true,
      thingsToSay: {
        repeat: false,
        'CastleInTheSky-Ding': {
          text: '~/.arlobot/sounds/CastleInTheSky-Ding.wav',
          weight: 100,
        },
        'CastleInTheSky-Drip2': {
          text: '~/.arlobot/sounds/CastleInTheSky-Drip2.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep2a': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep2a.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep2b': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep2b.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep2c': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep2c.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep3a': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep3a.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep3b': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep3b.wav',
          weight: 100,
        },
        'CastleInTheSky-RobotBeep3c': {
          text: '~/.arlobot/sounds/CastleInTheSky-RobotBeep3c.wav',
          weight: 100,
        },
        'CastleInTheSky-Zap': {
          text: '~/.arlobot/sounds/CastleInTheSky-Zap.wav',
          weight: 100,
        },
      },
    },
  },
  events: {
    goalStatus: {
      repeatInterval: 10,
      spacing: 0,
      newGoalSet: {
        thingsToSay: {
          repeat: true,
          onMyWay: {
            text: 'On my way!',
            weight: 100,
          },
        },
      },
      goalReached: {
        thingsToSay: {
          repeat: true,
          iHaveArrived: {
            text: 'I have arrived.',
            weight: 100,
          },
          destinationReached: {
            text: 'Destination reached..',
            weight: 100,
          },
        },
      },
    },
    Escaping: {
      repeatInterval: 15,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          WallEwhoa: {
            text: '~/.arlobot/sounds/whoa.wav',
            weight: 100,
          },
        },
      },
    },
    ROSstart: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          ROSstart: {
            text: 'Starting ROS.',
            weight: 100,
          },
        },
      },
    },
    ROSisRunning: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          ROSisRunning: {
            text: 'ROS is running.',
            weight: 100,
          },
        },
      },
    },
    makeMap: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          makeMap: {
            text: 'Preparing to make map.',
            weight: 100,
          },
        },
      },
    },
    makeMapRunning: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          makeMapRunning: {
            text: 'Making Map.',
            weight: 100,
          },
        },
      },
    },
    loadMapRequested: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          loadMapRequested: {
            text: 'Loading map.',
            weight: 100,
          },
        },
      },
    },
    mapLoaded: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          mapLoaded: {
            text: 'Map Loaded.',
            weight: 100,
          },
        },
      },
    },
    haltRobot: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          WallEwhoa: {
            text: '~/.arlobot/sounds/whoa.wav',
            weight: 100,
          },
        },
      },
    },
    cameraOn: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          iSeeYou: {
            text: '~/.arlobot/sounds/I-see-you.wav',
            weight: 100,
          },
        },
      },
    },
    pluggedIn: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          thankYou: {
            text: 'Thank you for plugging me in.',
            weight: 100,
          },
        },
      },
      false: {
        thingsToSay: {
          repeat: true,
          thankYou: {
            text: 'Thank you for unplugging me.',
            weight: 100,
          },
        },
      },
    },
    laptopFullyCharged: {
      repeatInterval: 0,
      spacing: 0,
      true: {
        thingsToSay: {
          repeat: true,
          charged: {
            text: 'Computer battery fully charged!',
            weight: 100,
          },
        },
      },
      false: {
        thingsToSay: {
          repeat: true,
          unDone: {
            text: 'Starting to get tired.',
            weight: 100,
          },
        },
      },
    },
    mostRecentArrival: {
      repeatInterval: 0,
      spacing: 0,
      Here: {
        thingsToSay: {
          repeat: true,
          imHere: {
            text: "I'm here.",
            weight: 10,
          },
          yesMaster: {
            text: 'Yes Master?',
            weight: 90,
          },
        },
      },
      initial: {
        thingsToSay: {
          repeat: true,
          home: {
            text: 'Home sweet home.',
            weight: 60,
          },
          familiar: {
            text: 'This place looks vaguely familiar.',
            weight: 40,
          },
        },
      },
    },
  },
};

// Sting Keys cannot be set on object literals, so setting them here:
speechModel.events.goalStatus['New Goal Set.'] =
  speechModel.events.goalStatus.newGoalSet;
speechModel.events.goalStatus['Goal reached.'] =
  speechModel.events.goalStatus.goalReached;

module.exports = speechModel;
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

var Span = require('./span');

module.exports = Agent;

function Agent(options) {
    if (!(this instanceof Agent)) {
        return new Agent(options);
    }
    var self = this;

    options = options || {};

    self.logger = options.logger;

    // If this is set to true in a call to Agent#configure, all incoming
    // requests will have their traceflags forced to 1. It's intended to be
    // set on the 'top level service'.
    self.forceTrace = options.forceTrace || false;

    // 'our' service name that is used as the service name on spans for
    // incoming reuqests
    self.serviceName = options.serviceName || null;

    self.reporter = options.reporter || self.reporter;
}

function compareBufs(buf1, buf2) {
    if (!buf2) return false;
    return (buf1.readUInt32BE(0) === buf2.readUInt32BE(0)) &&
        (buf1.readUInt32BE(4) === buf2.readUInt32BE(4));
}

// ## setupNewSpan
// Sets up a new span for an outgoing rpc
Agent.prototype.setupNewSpan = function setupNewSpan(options) {
    var self = this;

    var hostPortParts = options.hostPort.split(":");
    var host = hostPortParts[0], port = parseInt(hostPortParts[1], 10);

    var empty = new Buffer([0, 0, 0, 0, 0, 0, 0, 0]);
    if (compareBufs(empty, options.parentid)) {
        options.parentid = null;
    }

    if (compareBufs(empty, options.traceid)) {
        options.traceid = null;
    }

    if (compareBufs(empty, options.spanid)) {
        options.spanid = null;
    }

    var span = new Span({
        logger: self.logger,
        endpoint: new Span.Endpoint(
            host,
            port,
            // If a service hasn't been specified on the tracer, use the
            // service on the incoming request. This is to handle the
            // case of the service router, which has a different service name
            // than the one specified in the incoming request.
            self.serviceName || options.serviceName
        ),
        name: options.name,
        id: options.spanid,
        parentid: options.parentid,
        traceid: options.traceid,
        flags: self.forceTrace? 1 : options.flags
    });

    var parentSpan = options.parentSpan;
    if (options.outgoing && !parentSpan && !options.topLevelRequest) {
        if (self.logger) {
            self.logger.warn("TChannel tracer: parent span not specified " +
                "for outgoing request!", options);
        }
    }

    if (parentSpan && (!options.parentid && !options.traceid)) {
        // If there's a parentSpan and the parentid and traceid weren't
        // specified, we need to propagate the ids from the parent span.
        span.propagateIdsFrom(parentSpan);
        span.generateSpanid();
    } else if (!parentSpan && (!options.traceid && !options.spanid)) {
        // No ids were specified and there's no parent span. Generate a new
        // id and use it as the spanid and traceid.
        span.generateIds();
    }

    return span;
};

Agent.prototype.report = function report(span) {
    var self = this;

    if (span.flags === 1) {
        self.reporter(span);
    }
};

// Default reporter, just logs.
Agent.prototype.reporter = function (span) {
    var self = this;

    // TODO: actual reporting
    if (self.logger) {
        self.logger.info('got span: ' + span.toString());
    }
};

<commit_msg>tracing: add use strict<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var Span = require('./span');

module.exports = Agent;

function Agent(options) {
    if (!(this instanceof Agent)) {
        return new Agent(options);
    }
    var self = this;

    options = options || {};

    self.logger = options.logger;

    // If this is set to true in a call to Agent#configure, all incoming
    // requests will have their traceflags forced to 1. It's intended to be
    // set on the 'top level service'.
    self.forceTrace = options.forceTrace || false;

    // 'our' service name that is used as the service name on spans for
    // incoming reuqests
    self.serviceName = options.serviceName || null;

    self.reporter = options.reporter || self.reporter;
}

function compareBufs(buf1, buf2) {
    if (!buf2) return false;
    return (buf1.readUInt32BE(0) === buf2.readUInt32BE(0)) &&
        (buf1.readUInt32BE(4) === buf2.readUInt32BE(4));
}

// ## setupNewSpan
// Sets up a new span for an outgoing rpc
Agent.prototype.setupNewSpan = function setupNewSpan(options) {
    var self = this;

    var hostPortParts = options.hostPort.split(":");
    var host = hostPortParts[0], port = parseInt(hostPortParts[1], 10);

    var empty = new Buffer([0, 0, 0, 0, 0, 0, 0, 0]);
    if (compareBufs(empty, options.parentid)) {
        options.parentid = null;
    }

    if (compareBufs(empty, options.traceid)) {
        options.traceid = null;
    }

    if (compareBufs(empty, options.spanid)) {
        options.spanid = null;
    }

    var span = new Span({
        logger: self.logger,
        endpoint: new Span.Endpoint(
            host,
            port,
            // If a service hasn't been specified on the tracer, use the
            // service on the incoming request. This is to handle the
            // case of the service router, which has a different service name
            // than the one specified in the incoming request.
            self.serviceName || options.serviceName
        ),
        name: options.name,
        id: options.spanid,
        parentid: options.parentid,
        traceid: options.traceid,
        flags: self.forceTrace? 1 : options.flags
    });

    var parentSpan = options.parentSpan;
    if (options.outgoing && !parentSpan && !options.topLevelRequest) {
        if (self.logger) {
            self.logger.warn("TChannel tracer: parent span not specified " +
                "for outgoing request!", options);
        }
    }

    if (parentSpan && (!options.parentid && !options.traceid)) {
        // If there's a parentSpan and the parentid and traceid weren't
        // specified, we need to propagate the ids from the parent span.
        span.propagateIdsFrom(parentSpan);
        span.generateSpanid();
    } else if (!parentSpan && (!options.traceid && !options.spanid)) {
        // No ids were specified and there's no parent span. Generate a new
        // id and use it as the spanid and traceid.
        span.generateIds();
    }

    return span;
};

Agent.prototype.report = function report(span) {
    var self = this;

    if (span.flags === 1) {
        self.reporter(span);
    }
};

// Default reporter, just logs.
Agent.prototype.reporter = function (span) {
    var self = this;

    // TODO: actual reporting
    if (self.logger) {
        self.logger.info('got span: ' + span.toString());
    }
};

<|endoftext|>


<commit_before>var utils  = require('../utils');

/**
 * session
 *
 * Takes a list of files and creates a set of file names that occur in subject
 * directories. Then generates a warning if a given subject is missing any
 * files from the set.
 */
var session = function missingSessionFiles(fileList) {
    var subjects = [];
    var issues = [];
    for (var key in fileList) {
        var file = fileList[key];
        var filename;

        if (!file || (typeof window != 'undefined' && !file.webkitRelativePath)) {
            continue;
        }

        var path = utils.files.relativePath(file);
        if (!utils.type.isBIDS(path)) {
            continue;
        }
        var subject;
        //match the subject identifier up to the '/' in the full path to a file.
        var match = path.match(/sub-(.*?)(?=\/)/);
        if (match === null) {
            continue;
        } else {
            subject = match[0];
        }
        // initialize an empty array if we haven't seen this subject before
        if (typeof(subjects[subject]) === 'undefined') {
            subjects[subject] = [];
        }
        // files are prepended with subject name, the following two commands
        // remove the subject from the file name to allow filenames to be more
        // easily compared
        filename = path.substring(path.match(subject).index + subject.length);
        filename = filename.replace(subject, '<sub>');
        subjects[subject].push(filename);
    }

    var subject_files = [];
    for (var key in subjects) {
        var subject = subjects[key];
        for (var i = 0; i < subject.length; i++) {
            var file = subject[i];
            if (subject_files.indexOf(file) < 0) {
                subject_files.push(file);
            }
        }
    }

    for (var subject in subjects) {
        for (var set_file in subject_files) {
            if (subjects[subject].indexOf(subject_files[set_file]) === -1) {
                filename = subject_files[set_file].replace('<sub>', subject);
                file.relativePath = '/' + subject + filename;
                issues.push(new utils.Issue({
                    file: file,
                    evidence: "Subject: " + subject + "; Missing file: " + filename,
                    code: 38
                }));
            }
        }
    }
    return issues;
};

module.exports = session;
<commit_msg>mocked file object in issue creation to allow file info in validator output<commit_after>var utils  = require('../utils');

/**
 * session
 *
 * Takes a list of files and creates a set of file names that occur in subject
 * directories. Then generates a warning if a given subject is missing any
 * files from the set.
 */
var session = function missingSessionFiles(fileList) {
    var subjects = [];
    var issues = [];
    for (var key in fileList) {
        var file = fileList[key];
        var filename;

        if (!file || (typeof window != 'undefined' && !file.webkitRelativePath)) {
            continue;
        }

        var path = utils.files.relativePath(file);
        if (!utils.type.isBIDS(path)) {
            continue;
        }
        var subject;
        //match the subject identifier up to the '/' in the full path to a file.
        var match = path.match(/sub-(.*?)(?=\/)/);
        if (match === null) {
            continue;
        } else {
            subject = match[0];
        }
        // initialize an empty array if we haven't seen this subject before
        if (typeof(subjects[subject]) === 'undefined') {
            subjects[subject] = [];
        }
        // files are prepended with subject name, the following two commands
        // remove the subject from the file name to allow filenames to be more
        // easily compared
        filename = path.substring(path.match(subject).index + subject.length);
        filename = filename.replace(subject, '<sub>');
        subjects[subject].push(filename);
    }

    var subject_files = [];
    for (var key in subjects) {
        var subject = subjects[key];
        for (var i = 0; i < subject.length; i++) {
            var file = subject[i];
            if (subject_files.indexOf(file) < 0) {
                subject_files.push(file);
            }
        }
    }

    for (var subject in subjects) {
        for (var set_file in subject_files) {
            if (subjects[subject].indexOf(subject_files[set_file]) === -1) {
                filename = subject_files[set_file].replace('<sub>', subject);
                file.relativePath = '/' + subject + filename;
                issues.push(new utils.Issue({
                    file: {relativePath: file},
                    evidence: "Subject: " + subject + "; Missing file: " + filename,
                    code: 38
                }));
            }
        }
    }
    return issues;
};

module.exports = session;
<|endoftext|>


<commit_before>/**
 * PUSH Notification server V 0.2
 * (c) Telefonica Digital, 2012 - All rights reserver
 * Fernando Rodríguez Sela <frsela@tid.es>
 * Guillermo Lopez Leal <gll@tid.es>
 */

var mongodb = require("mongodb");
var log = require("./logger.js");
var events = require("events");
var util = require("util");
var ddbbsettings = require("../config.js").ddbbsettings;
var helpers = require("../common/helpers.js");

var DataStore = function() {
  this.init = function() {
    log.info("datastore::starting --> MongoDB data store loading.");
    events.EventEmitter.call(this);

    if (ddbbsettings.replicasetName) {
      //Filling the replicaset data
      var servers = [];
      ddbbsettings.machines.forEach(function(machine) {
        servers.push(new mongodb.Server(machine[0], machine[1], { auto_reconnect: true }));
      });
      var replSet = new mongodb.ReplSetServers(servers, {rs_name:ddbbsettings.replicasetName, read_secondary: true});

      // Connection to MongoDB
      this.db = new mongodb.Db(ddbbsettings.ddbbname, replSet);
    } else {
      this.db = new mongodb.Db(
        ddbbsettings.ddbbname,
        new mongodb.Server(
          ddbbsettings.machines[0][0], //host
          ddbbsettings.machines[0][1] //port
        )
      );
    }

    // Establish connection to db
    this.db.open(function(err, db) {
      if(!err) {
        log.info("datastore::starting --> Connected to MongoDB on " + ddbbsettings.machines + ". Database Name: " + ddbbsettings.ddbbname);
        this.emit('ddbbconnected');
      } else {
        log.error("datastore::starting --> Error connecting to MongoDB ! - " + err);
        // TODO: Cierre del servidor? Modo alternativo?
      }
    }.bind(this));
  },

  this.registerNode = function (token, serverId, data, callback) {
    // Register in MONGO that this server manages this node
    this.db.collection("nodes", function(err, collection) {
      if (!err) {
        collection.save( { _id: token, serverId: serverId, data: data },
                         { safe: true },
                         function(err,d) {
          if(!err && d) {
            log.debug("datastore::registerNode --> Node inserted/update into MongoDB");
            return callback(true);
          }
          else {
            log.debug("datastore::registerNode --> Error inserting/updating node into MongoDB -- " + err);
            return callback(false);
          }
        });
      } else {
        log.error("datastore::registerNode --> There was a problem opening the nodes collection");
        return callback(false);
      }
    });
  };

  /**
   * Unregister a node
   */
   this.unregisterNode = function(token, callback) {
    this.db.collection("nodes", function(err, collection) {
      if (!err) {
        collection.remove( { _id: token },
                         { safe: true },
                         function(err,d) {
          if(!err) {
            log.debug("datastore::unregisterNode --> Node removed from MongoDB");
            return callback(true);
          }
        });
      } else {
        log.error("datastore::unregisterNode --> There was a problem opening the nodes collection");
        return callback(false);
      }
    });
   };

  /**
   * Gets a node - server relationship
   */
  this.getNode = function (token, callbackFunc, callbackParam) {
    // Get from MongoDB
    this.db.collection("nodes", function(err, collection) {
      if (!err) {
        collection.findOne( { _id: token }, function(err,d) {
          if(!err && callbackFunc && d) {
            log.debug('Finding info for node ' + token);
            log.debug("datastore::getNode --> Data found, calling callback with data");
            callbackFunc(d, callbackParam);
          }
          else if (!d && !err) {
            log.debug('Finding info for node ' + token);
            log.debug("datastore::getNode --> No error, but no nodes to notify");
            callbackFunc(null, callbackParam);
          } else {
            log.debug('Finding info for node ' + token);
            log.debug("datastore::getNode --> Error finding node into MongoDB: " + err);
            callbackFunc(null, callbackParam);
          }
        });
      } else {
        log.error("datastore::getNode --> there was a problem opening the nodes collection");
        callbackFunc(null, callbackParam);
      }
    });
  },

  // TODO: Verify that the node exists before add the application
  /**
   * Register a new application
   */
  this.registerApplication = function (waToken, nodeToken, pbkbase64, callback) {
    // Store in MongoDB
    this.db.collection("apps", function(err, collection) {
      if (!err) {
        collection.update( { _id: waToken },
          { $addToSet : { node: nodeToken, pbkbase64: pbkbase64 }},
          {safe: true, upsert: true},
          function(err,d) {
            if(!err) {
              log.debug("datastore::registerApplication --> Application inserted into MongoDB");
              return callback(true);
            } else {
              log.debug("datastore::registerApplication --> Error inserting application into MongoDB: " + err);
              return callback(false);
            }
          });
      } else {
        log.error("datastore::registerApplication --> there was a problem opening the apps collection");
        return callback(false);
      }
    });
  },

  /**
   * Gets an application node list
   */
  this.getApplication = function (token, callbackFunc, callbackParam) {
    // Get from MongoDB
    this.db.collection("apps", function(err, collection) {
      if (!err) {
        collection.findOne( { _id: token }, function(err,d) {
          if(!err && callbackFunc && d) {
            callbackFunc(d, callbackParam);
          } else {
            log.debug("datastore::getApplication -->Error finding application from MongoDB: " + err);
            callbackFunc(null, callbackParam);
          }
        });
      } else {
        log.error("datastore::getApplication --> there was a problem opening the apps collection");
        callbackFunc(null, callbackParam);
      }
    });
  },

  /**
   * Get the Pbk of the WA.
   * @ return the pbk.
   */
  this.getPbkApplication = function(watoken2, callback) {
    var watoken = watoken2.toString();
    log.debug("datastore::getPbkApplication --> Going to find the pbk for the watoken " + watoken);
    this.db.collection("apps", function(err, collection) {
      if (!err) {
        collection.findOne( { _id: watoken }, function(err, d){
          if (!err && d) {
            var pbkbase64 = d.pbkbase64.toString('base64');
            log.debug("datastore::getPbkApplication --> Found the pbk (base64) '" + pbkbase64 + "' for the watoken '" + watoken);
            //WARN: This returns the base64 as saved on the DDBB!!
            return callback(pbkbase64);
          }
          else if (!err && !d){
            log.debug('datastore::getPbkApplication --> There are no pbk for the WAToken' + watoken);
            return callback();
          } else {
            log.debug('datastore::getPbkApplication --> There was a problem finding the pbk for the WAToken');
            return callback();
          }
        });
      } else {
        log.error('datastore::getPbkApplication --> there was a problem opening the apps collection');
        return callback();
      }
    });
  },

  /**
   * Save a new message
   * @return New message as stored on DB
   */
  this.newMessage = function (id, watoken, message) {
    message.messageId = id;
    message.url = helpers.getNotificationURL(watoken);
    var msg = { _id: id, watoken: watoken, payload: message };
    this.db.collection("messages", function(err, collection) {
      if (!err) {
        collection.save(msg, { safe: true }, function(err, d) {
          if(!err && d)
            log.debug("datastore::newMessage --> Message inserted into MongoDB");
          else
            log.debug("datastore::newMessage --> Error inserting message into MongoDB");
        });
      } else {
        log.error("datastore::newMessage --> There was a problem opening the messages collection");
      }
    });
    return msg;
  },

  /**
   * Get a message
   */
  this.getMessage = function (id, callback, callbackParam) {
    log.debug("Looking for message " + id);
    // Get from MongoDB
    this.db.collection("messages", function(err, collection) {
      if (!err) {
        collection.findOne( { 'MsgId': id }, function(err,d) {
          if(!err) {
            if (callback && d) {
              log.debug("datastore::getMessage --> The message has been inserted. Calling callback");
              return callback(d, callbackParam);
            } else {
              log.debug("datastore::getMessage --> The message has been inserted.");
              return d;
            }
          } else {
            log.debug("datastore::getMessage --> Error finding message from MongoDB: " + err);
          }
        });
      } else {
        log.error("datastore::getMessage --> There was a problem opening the messages collection");
      }
    });
  },

  /**
   * Get all messages for a UA
   */
  this.getAllMessages = function (uatoken, callback, callbackParam) {
    log.debug("Looking for messages of " + uatoken);
    // Get from MongoDB
    this.db.collection("messages", function(err, collection) {
      if (!err) {
        collection.find( { _id: uatoken } ).toArray(function(err,d) {
          if(!err && callback && d) {
            log.debug("datastore::getAllMessages --> Messages found, calling callback");
            return callback(d, callbackParam);
          }
          else if (!err && !d) {
            log.debug("datastore::getAllMessages --> No messages found");
          }
        });
      } else {
        log.error("datastore::getAllMessages --> There was a problem opening the messages collection");
      }
    });
  },

  this.removeMessage = function(messageId) {
    log.debug('dataStore::removeMessage --> Going to remove message with _id=' + messageId);
    this.db.collection("messages", function(err, collection) {
      if (!err) {
        collection.remove( { _id: messageId },
                         { safe: true },
                         function(err,d) {
          if(!err) {
            log.debug("datastore::removeMessage --> Message removed from MongoDB");
          }
        });
      } else {
        log.error("datastore::removeMessage --> There was a problem opening the messages collection");
      }
    });
  };
};

///////////////////////////////////////////
// Singleton
///////////////////////////////////////////
util.inherits(DataStore, events.EventEmitter);
var _ds = new DataStore(); _ds.init();
function getDataStore() {
  return _ds;
}

module.exports = getDataStore();
<commit_msg>Public Key stored in a simple data and not in an array or set<commit_after>/**
 * PUSH Notification server V 0.2
 * (c) Telefonica Digital, 2012 - All rights reserver
 * Fernando Rodríguez Sela <frsela@tid.es>
 * Guillermo Lopez Leal <gll@tid.es>
 */

var mongodb = require("mongodb");
var log = require("./logger.js");
var events = require("events");
var util = require("util");
var ddbbsettings = require("../config.js").ddbbsettings;
var helpers = require("../common/helpers.js");

var DataStore = function() {
  this.init = function() {
    log.info("datastore::starting --> MongoDB data store loading.");
    events.EventEmitter.call(this);

    if (ddbbsettings.replicasetName) {
      //Filling the replicaset data
      var servers = [];
      ddbbsettings.machines.forEach(function(machine) {
        servers.push(new mongodb.Server(machine[0], machine[1], { auto_reconnect: true }));
      });
      var replSet = new mongodb.ReplSetServers(servers, {rs_name:ddbbsettings.replicasetName, read_secondary: true});

      // Connection to MongoDB
      this.db = new mongodb.Db(ddbbsettings.ddbbname, replSet);
    } else {
      this.db = new mongodb.Db(
        ddbbsettings.ddbbname,
        new mongodb.Server(
          ddbbsettings.machines[0][0], //host
          ddbbsettings.machines[0][1] //port
        )
      );
    }

    // Establish connection to db
    this.db.open(function(err, db) {
      if(!err) {
        log.info("datastore::starting --> Connected to MongoDB on " + ddbbsettings.machines + ". Database Name: " + ddbbsettings.ddbbname);
        this.emit('ddbbconnected');
      } else {
        log.error("datastore::starting --> Error connecting to MongoDB ! - " + err);
        // TODO: Cierre del servidor? Modo alternativo?
      }
    }.bind(this));
  },

  this.registerNode = function (token, serverId, data, callback) {
    // Register in MONGO that this server manages this node
    this.db.collection("nodes", function(err, collection) {
      if (!err) {
        collection.save( { _id: token, serverId: serverId, data: data },
                         { safe: true },
                         function(err,d) {
          if(!err && d) {
            log.debug("datastore::registerNode --> Node inserted/update into MongoDB");
            return callback(true);
          }
          else {
            log.debug("datastore::registerNode --> Error inserting/updating node into MongoDB -- " + err);
            return callback(false);
          }
        });
      } else {
        log.error("datastore::registerNode --> There was a problem opening the nodes collection");
        return callback(false);
      }
    });
  };

  /**
   * Unregister a node
   */
   this.unregisterNode = function(token, callback) {
    this.db.collection("nodes", function(err, collection) {
      if (!err) {
        collection.remove( { _id: token },
                         { safe: true },
                         function(err,d) {
          if(!err) {
            log.debug("datastore::unregisterNode --> Node removed from MongoDB");
            return callback(true);
          }
        });
      } else {
        log.error("datastore::unregisterNode --> There was a problem opening the nodes collection");
        return callback(false);
      }
    });
   };

  /**
   * Gets a node - server relationship
   */
  this.getNode = function (token, callbackFunc, callbackParam) {
    // Get from MongoDB
    this.db.collection("nodes", function(err, collection) {
      if (!err) {
        collection.findOne( { _id: token }, function(err,d) {
          if(!err && callbackFunc && d) {
            log.debug('Finding info for node ' + token);
            log.debug("datastore::getNode --> Data found, calling callback with data");
            callbackFunc(d, callbackParam);
          }
          else if (!d && !err) {
            log.debug('Finding info for node ' + token);
            log.debug("datastore::getNode --> No error, but no nodes to notify");
            callbackFunc(null, callbackParam);
          } else {
            log.debug('Finding info for node ' + token);
            log.debug("datastore::getNode --> Error finding node into MongoDB: " + err);
            callbackFunc(null, callbackParam);
          }
        });
      } else {
        log.error("datastore::getNode --> there was a problem opening the nodes collection");
        callbackFunc(null, callbackParam);
      }
    });
  },

  // TODO: Verify that the node exists before add the application
  /**
   * Register a new application
   */
  this.registerApplication = function (waToken, nodeToken, pbkbase64, callback) {
    // Store in MongoDB
    this.db.collection("apps", function(err, collection) {
      if (!err) {
        collection.update( { _id: waToken },
          { $addToSet : { node: nodeToken }, $set : { pbkbase64: pbkbase64 }},
          {safe: true, upsert: true},
          function(err,d) {
            if(!err) {
              log.debug("datastore::registerApplication --> Application inserted into MongoDB");
              return callback(true);
            } else {
              log.debug("datastore::registerApplication --> Error inserting application into MongoDB: " + err);
              return callback(false);
            }
          });
      } else {
        log.error("datastore::registerApplication --> there was a problem opening the apps collection");
        return callback(false);
      }
    });
  },

  /**
   * Gets an application node list
   */
  this.getApplication = function (token, callbackFunc, callbackParam) {
    // Get from MongoDB
    this.db.collection("apps", function(err, collection) {
      if (!err) {
        collection.findOne( { _id: token }, function(err,d) {
          if(!err && callbackFunc && d) {
            callbackFunc(d, callbackParam);
          } else {
            log.debug("datastore::getApplication -->Error finding application from MongoDB: " + err);
            callbackFunc(null, callbackParam);
          }
        });
      } else {
        log.error("datastore::getApplication --> there was a problem opening the apps collection");
        callbackFunc(null, callbackParam);
      }
    });
  },

  /**
   * Get the Pbk of the WA.
   * @ return the pbk.
   */
  this.getPbkApplication = function(watoken2, callback) {
    var watoken = watoken2.toString();
    log.debug("datastore::getPbkApplication --> Going to find the pbk for the watoken " + watoken);
    this.db.collection("apps", function(err, collection) {
      if (!err) {
        collection.findOne( { _id: watoken }, function(err, d){
          if (!err && d) {
            var pbkbase64 = d.pbkbase64.toString('base64');
            log.debug("datastore::getPbkApplication --> Found the pbk (base64) '" + pbkbase64 + "' for the watoken '" + watoken);
            //WARN: This returns the base64 as saved on the DDBB!!
            return callback(pbkbase64);
          }
          else if (!err && !d){
            log.debug('datastore::getPbkApplication --> There are no pbk for the WAToken' + watoken);
            return callback();
          } else {
            log.debug('datastore::getPbkApplication --> There was a problem finding the pbk for the WAToken');
            return callback();
          }
        });
      } else {
        log.error('datastore::getPbkApplication --> there was a problem opening the apps collection');
        return callback();
      }
    });
  },

  /**
   * Save a new message
   * @return New message as stored on DB
   */
  this.newMessage = function (id, watoken, message) {
    message.messageId = id;
    message.url = helpers.getNotificationURL(watoken);
    var msg = { _id: id, watoken: watoken, payload: message };
    this.db.collection("messages", function(err, collection) {
      if (!err) {
        collection.save(msg, { safe: true }, function(err, d) {
          if(!err && d)
            log.debug("datastore::newMessage --> Message inserted into MongoDB");
          else
            log.debug("datastore::newMessage --> Error inserting message into MongoDB");
        });
      } else {
        log.error("datastore::newMessage --> There was a problem opening the messages collection");
      }
    });
    return msg;
  },

  /**
   * Get a message
   */
  this.getMessage = function (id, callback, callbackParam) {
    log.debug("Looking for message " + id);
    // Get from MongoDB
    this.db.collection("messages", function(err, collection) {
      if (!err) {
        collection.findOne( { 'MsgId': id }, function(err,d) {
          if(!err) {
            if (callback && d) {
              log.debug("datastore::getMessage --> The message has been inserted. Calling callback");
              return callback(d, callbackParam);
            } else {
              log.debug("datastore::getMessage --> The message has been inserted.");
              return d;
            }
          } else {
            log.debug("datastore::getMessage --> Error finding message from MongoDB: " + err);
          }
        });
      } else {
        log.error("datastore::getMessage --> There was a problem opening the messages collection");
      }
    });
  },

  /**
   * Get all messages for a UA
   */
  this.getAllMessages = function (uatoken, callback, callbackParam) {
    log.debug("Looking for messages of " + uatoken);
    // Get from MongoDB
    this.db.collection("messages", function(err, collection) {
      if (!err) {
        collection.find( { _id: uatoken } ).toArray(function(err,d) {
          if(!err && callback && d) {
            log.debug("datastore::getAllMessages --> Messages found, calling callback");
            return callback(d, callbackParam);
          }
          else if (!err && !d) {
            log.debug("datastore::getAllMessages --> No messages found");
          }
        });
      } else {
        log.error("datastore::getAllMessages --> There was a problem opening the messages collection");
      }
    });
  },

  this.removeMessage = function(messageId) {
    log.debug('dataStore::removeMessage --> Going to remove message with _id=' + messageId);
    this.db.collection("messages", function(err, collection) {
      if (!err) {
        collection.remove( { _id: messageId },
                         { safe: true },
                         function(err,d) {
          if(!err) {
            log.debug("datastore::removeMessage --> Message removed from MongoDB");
          }
        });
      } else {
        log.error("datastore::removeMessage --> There was a problem opening the messages collection");
      }
    });
  };
};

///////////////////////////////////////////
// Singleton
///////////////////////////////////////////
util.inherits(DataStore, events.EventEmitter);
var _ds = new DataStore(); _ds.init();
function getDataStore() {
  return _ds;
}

module.exports = getDataStore();
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var EventEmitter = require('./lib/event_emitter');
var stat = require('./stat-tags.js');
var inherits = require('util').inherits;

var errors = require('./errors');
var States = require('./reqres_states');

/*eslint max-statements: [2, 40]*/
function TChannelOutResponse(id, options) {
    options = options || {};
    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.spanEvent = self.defineEvent('span');
    self.finishEvent = self.defineEvent('finish');

    self.channel = options.channel;
    self.inreq = options.inreq;
    self.logger = options.logger;
    self.random = options.random;
    self.timers = options.timers;

    self.start = 0;
    self.end = 0;
    self.state = States.Initial;
    self.id = id || 0;
    self.code = options.code || 0;
    self.tracing = options.tracing || null;
    self.headers = options.headers || {};
    self.checksumType = options.checksumType || 0;
    self.checksum = options.checksum || null;
    self.ok = self.code === 0;
    self.span = options.span || null;
    self.streamed = false;
    self._argstream = null;
    self.arg1 = null;
    self.arg2 = null;
    self.arg3 = null;

    self.codeString = null;
    self.message = null;

    self.error = null;
}

inherits(TChannelOutResponse, EventEmitter);

TChannelOutResponse.prototype.type = 'tchannel.outgoing-response';

TChannelOutResponse.prototype.extendLogInfo = function extendLogInfo(info) {
    var self = this;

    info.responseId = self.id;
    info.responseType = self.type;
    info.responseState = States.describe(self.state);
    info.responseOk = self.ok;
    info.responseCode = self.code;
    info.responseCodeString = self.codeString;
    info.responseErrorMessage = self.message;
    info.responseStart = self.start;
    info.responseEnd = self.end;
    info.responseHasArg3 = self.arg3 !== null && self.arg3 !== undefined;

    if (self.inreq) {
        info = self.inreq.extendLogInfo(info);
    }

    return info;
};

TChannelOutResponse.prototype._sendCallResponse = function _sendCallResponse(args, isLast) {
    var self = this;
    throw errors.UnimplementedMethod({
        className: self.constructor.name,
        methodName: '_sendCallResponse'
    });
};

TChannelOutResponse.prototype._sendCallResponseCont = function _sendCallResponseCont(args, isLast) {
    var self = this;
    throw errors.UnimplementedMethod({
        className: self.constructor.name,
        methodName: '_sendCallResponseCont'
    });
};

TChannelOutResponse.prototype._sendError = function _sendError(codeString, message) {
    var self = this;
    throw errors.UnimplementedMethod({
        className: self.constructor.name,
        methodName: '_sendError'
    });
};

TChannelOutResponse.prototype.sendParts = function sendParts(parts, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            self.sendCallResponseFrame(parts, isLast);
            break;
        case States.Streaming:
            self.sendCallResponseContFrame(parts, isLast);
            break;
        case States.Done:
            self.emitError(errors.ResponseFrameState({
                attempted: 'arg parts',
                state: 'Done'
            }));
            break;
        case States.Error:
            // TODO: log warn
            break;
        default:
            self.channel.logger.error('TChannelOutResponse is in a wrong state', self.extendLogInfo({}));
            break;
    }
};

TChannelOutResponse.prototype.sendCallResponseFrame = function sendCallResponseFrame(args, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            self.start = self.timers.now();
            self._sendCallResponse(args, isLast);
            if (self.span) {
                self.span.annotate('ss');
            }
            if (isLast) self.state = States.Done;
            else self.state = States.Streaming;
            break;
        case States.Streaming:
            self.emitError(errors.ResponseFrameState({
                attempted: 'call response',
                state: 'Streaming'
            }));
            break;
        case States.Done:
        case States.Error:
            var arg2 = args[1] || '';
            var arg3 = args[2] || '';

            self.emitError(errors.ResponseAlreadyDone({
                attempted: 'call response',
                state: self.state,
                method: 'sendCallResponseFrame',
                bufArg2: arg2.slice(0, 50),
                arg2: String(arg2).slice(0, 50),
                bufArg3: arg3.slice(0, 50),
                arg3: String(arg3).slice(0, 50)
            }));
    }
};

TChannelOutResponse.prototype.sendCallResponseContFrame = function sendCallResponseContFrame(args, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            self.emitError(errors.ResponseFrameState({
                attempted: 'call response continuation',
                state: 'Initial'
            }));
            break;
        case States.Streaming:
            self._sendCallResponseCont(args, isLast);
            if (isLast) self.state = States.Done;
            break;
        case States.Done:
        case States.Error:
            self.emitError(errors.ResponseAlreadyDone({
                attempted: 'call response continuation',
                state: self.state,
                method: 'sendCallResponseContFrame'
            }));
    }
};

TChannelOutResponse.prototype.responseAlreadyDone = function responseAlreadyDone() {
    var self = this;

    if (self.state === States.Done || self.state === States.Error) {
        var errorEvent = self.errorEvent || {};
        var listener = !!errorEvent.listener;
        var listeners = 0;
        if (errorEvent.listeners && errorEvent.listeners.length) {
            listeners = errorEvent.listeners.length;
        }

        self.logger.error('responseAlreadyDone detected!!', {
            currentState: self.state,
            codeString: self.codeString,
            errMessage: self.message,
            callerName: self.inreq ? self.inreq.callerName : 'NA',
            serviceName: self.inreq ? self.inreq.serviceName : 'NA',
            listener: listener,
            listeners: listeners
        });
        return true;
    } else {
        return false;
    }
};

TChannelOutResponse.prototype.sendError = function sendError(codeString, message) {
    var self = this;

    if (self.inreq.connection && // because selfpeer/connection
        self.inreq.connection.closing) {
        self.logger.info('ignoring outresponse.sendError on a closed connection', {
            codeString: codeString,
            errorMessage: message
        });
        return;
    }

    if (self.state === States.Done || self.state === States.Error) {
        self.emitError(errors.ResponseAlreadyDone({
            attempted: 'error frame',
            currentState: self.state,
            method: 'sendError',
            codeString: codeString,
            errMessage: message
        }));
    } else {
        if (self.span) {
            self.span.annotate('ss');
        }
        self.state = States.Error;

        self.codeString = codeString;
        self.message = message;
        self.channel.emitFastStat(
            'tchannel.inbound.calls.system-errors',
            'counter',
            1,
            new stat.InboundCallsSystemErrorsTags(
                self.inreq.callerName,
                self.inreq.serviceName,
                String(self.inreq.arg1),
                self.codeString
            )
        );
        self._sendError(codeString, message);
        self.emitFinish();
    }
};

TChannelOutResponse.prototype.emitError = function emitError(err) {
    var self = this;

    if (self.inreq && self.inreq.circuit) {
        self.inreq.circuit.state.onRequestError(err);
    }

    self.error = err;
    self.errorEvent.emit(self, err);
};

TChannelOutResponse.prototype.emitFinish = function emitFinish() {
    var self = this;
    var now = self.timers.now();

    if (self.end) {
        self.logger.warn('out response double emitFinish', self.extendLogInfo({
            now: now
        }));
        return;
    }

    self.end = now;

    var latency = self.end - self.inreq.start;

    self.channel.emitFastStat(
        'tchannel.inbound.calls.latency',
        'timing',
        latency,
        new stat.InboundCallsLatencyTags(
            self.inreq.callerName,
            self.inreq.serviceName,
            self.inreq.endpoint
        )
    );

    if (self.span) {
        self.spanEvent.emit(self, self.span);
    }

    if (self.inreq && self.inreq.circuit) {
        // TODO distingiush res.ok?
        // note that incoming requests do not have responseEvent and clear out
        // their response upon finish.
        if (errors.isUnhealthy(self.codeString)) {
            self.inreq.circuit.state.onRequestUnhealthy();
        } else {
            self.inreq.circuit.state.onRequestHealthy();
        }
    }

    self.finishEvent.emit(self);
};

TChannelOutResponse.prototype.setOk = function setOk(ok) {
    var self = this;
    if (self.state !== States.Initial) {
        self.emitError(errors.ResponseAlreadyStarted({
            state: self.state,
            method: 'setOk',
            ok: ok
        }));
        return false;
    }
    self.ok = ok;
    self.code = ok ? 0 : 1; // TODO: too coupled to v2 specifics?
    return true;
};

TChannelOutResponse.prototype.sendOk = function sendOk(res1, res2) {
    var self = this;
    self.setOk(true);
    self.send(res1, res2);
};

TChannelOutResponse.prototype.sendNotOk = function sendNotOk(res1, res2) {
    var self = this;
    if (self.state === States.Error) {
        self.logger.error('cannot send application error, already sent error frame', self.extendLogInfo({
            res1: res1,
            res2: res2
        }));
    } else {
        self.setOk(false);
        self.send(res1, res2);
    }
};

TChannelOutResponse.prototype.send = function send(res1, res2) {
    var self = this;

    /* send calls after finish() should be swallowed */
    if (self.end) {
        var inreqErrClass = self.inreq &&
                            self.inreq.err &&
                            errors.classify(self.inreq.err);

        switch (inreqErrClass) {
            case 'Timeout':
                self.logger.info('OutResponse.send() after inreq timed out', self.extendLogInfo({}));
                break;
            default:
                self.logger.warn('OutResponse called send() after end', self.extendLogInfo({}));
        }
        return self;
    }

    self.arg2 = res1;
    self.arg3 = res2;

    if (self.ok) {
        self.channel.emitFastStat(
            'tchannel.inbound.calls.success',
            'counter',
            1,
            new stat.InboundCallsSuccessTags(
                self.inreq.callerName,
                self.inreq.serviceName,
                self.inreq.endpoint
            )
        );
    } else {
        // TODO: add outResponse.setErrorType()
        self.channel.emitFastStat(
            'tchannel.inbound.calls.app-errors',
            'counter',
            1,
            new stat.InboundCallsAppErrorsTags(
                self.inreq.callerName,
                self.inreq.serviceName,
                self.inreq.endpoint,
                'unknown'
            )
        );
    }

    // TODO: may be spam, consider dropping
    if (self.inreq.connection && // because selfpeer/connection
        self.inreq.connection.closing) {
        self.logger.info('ignoring outresponse.send on a closed connection', self.extendLogInfo({}));
    } else {
        self.sendCallResponseFrame([self.arg1, res1, res2], true);
    }

    self.emitFinish();

    return self;
};

module.exports = TChannelOutResponse;
<commit_msg>linting: [out_response] comply with default-case rule<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var EventEmitter = require('./lib/event_emitter');
var stat = require('./stat-tags.js');
var inherits = require('util').inherits;

var errors = require('./errors');
var States = require('./reqres_states');

/*eslint max-statements: [2, 40]*/
function TChannelOutResponse(id, options) {
    options = options || {};
    var self = this;
    EventEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.spanEvent = self.defineEvent('span');
    self.finishEvent = self.defineEvent('finish');

    self.channel = options.channel;
    self.inreq = options.inreq;
    self.logger = options.logger;
    self.random = options.random;
    self.timers = options.timers;

    self.start = 0;
    self.end = 0;
    self.state = States.Initial;
    self.id = id || 0;
    self.code = options.code || 0;
    self.tracing = options.tracing || null;
    self.headers = options.headers || {};
    self.checksumType = options.checksumType || 0;
    self.checksum = options.checksum || null;
    self.ok = self.code === 0;
    self.span = options.span || null;
    self.streamed = false;
    self._argstream = null;
    self.arg1 = null;
    self.arg2 = null;
    self.arg3 = null;

    self.codeString = null;
    self.message = null;

    self.error = null;
}

inherits(TChannelOutResponse, EventEmitter);

TChannelOutResponse.prototype.type = 'tchannel.outgoing-response';

TChannelOutResponse.prototype.extendLogInfo = function extendLogInfo(info) {
    var self = this;

    info.responseId = self.id;
    info.responseType = self.type;
    info.responseState = States.describe(self.state);
    info.responseOk = self.ok;
    info.responseCode = self.code;
    info.responseCodeString = self.codeString;
    info.responseErrorMessage = self.message;
    info.responseStart = self.start;
    info.responseEnd = self.end;
    info.responseHasArg3 = self.arg3 !== null && self.arg3 !== undefined;

    if (self.inreq) {
        info = self.inreq.extendLogInfo(info);
    }

    return info;
};

TChannelOutResponse.prototype._sendCallResponse = function _sendCallResponse(args, isLast) {
    var self = this;
    throw errors.UnimplementedMethod({
        className: self.constructor.name,
        methodName: '_sendCallResponse'
    });
};

TChannelOutResponse.prototype._sendCallResponseCont = function _sendCallResponseCont(args, isLast) {
    var self = this;
    throw errors.UnimplementedMethod({
        className: self.constructor.name,
        methodName: '_sendCallResponseCont'
    });
};

TChannelOutResponse.prototype._sendError = function _sendError(codeString, message) {
    var self = this;
    throw errors.UnimplementedMethod({
        className: self.constructor.name,
        methodName: '_sendError'
    });
};

TChannelOutResponse.prototype.sendParts = function sendParts(parts, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            self.sendCallResponseFrame(parts, isLast);
            break;
        case States.Streaming:
            self.sendCallResponseContFrame(parts, isLast);
            break;
        case States.Done:
            self.emitError(errors.ResponseFrameState({
                attempted: 'arg parts',
                state: 'Done'
            }));
            break;
        case States.Error:
            // TODO: log warn
            break;
        default:
            self.channel.logger.error('TChannelOutResponse is in a wrong state', self.extendLogInfo({}));
            break;
    }
};

TChannelOutResponse.prototype.sendCallResponseFrame = function sendCallResponseFrame(args, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            self.start = self.timers.now();
            self._sendCallResponse(args, isLast);
            if (self.span) {
                self.span.annotate('ss');
            }
            if (isLast) self.state = States.Done;
            else self.state = States.Streaming;
            break;
        case States.Streaming:
            self.emitError(errors.ResponseFrameState({
                attempted: 'call response',
                state: 'Streaming'
            }));
            break;
        case States.Done:
        case States.Error:
            var arg2 = args[1] || '';
            var arg3 = args[2] || '';

            self.emitError(errors.ResponseAlreadyDone({
                attempted: 'call response',
                state: self.state,
                method: 'sendCallResponseFrame',
                bufArg2: arg2.slice(0, 50),
                arg2: String(arg2).slice(0, 50),
                bufArg3: arg3.slice(0, 50),
                arg3: String(arg3).slice(0, 50)
            }));
            break;
        default:
            // TODO: log warn
            break;
    }
};

TChannelOutResponse.prototype.sendCallResponseContFrame = function sendCallResponseContFrame(args, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            self.emitError(errors.ResponseFrameState({
                attempted: 'call response continuation',
                state: 'Initial'
            }));
            break;
        case States.Streaming:
            self._sendCallResponseCont(args, isLast);
            if (isLast) self.state = States.Done;
            break;
        case States.Done:
        case States.Error:
            self.emitError(errors.ResponseAlreadyDone({
                attempted: 'call response continuation',
                state: self.state,
                method: 'sendCallResponseContFrame'
            }));
            break;
        default:
            // TODO: log warn
            break;
    }
};

TChannelOutResponse.prototype.responseAlreadyDone = function responseAlreadyDone() {
    var self = this;

    if (self.state === States.Done || self.state === States.Error) {
        var errorEvent = self.errorEvent || {};
        var listener = !!errorEvent.listener;
        var listeners = 0;
        if (errorEvent.listeners && errorEvent.listeners.length) {
            listeners = errorEvent.listeners.length;
        }

        self.logger.error('responseAlreadyDone detected!!', {
            currentState: self.state,
            codeString: self.codeString,
            errMessage: self.message,
            callerName: self.inreq ? self.inreq.callerName : 'NA',
            serviceName: self.inreq ? self.inreq.serviceName : 'NA',
            listener: listener,
            listeners: listeners
        });
        return true;
    } else {
        return false;
    }
};

TChannelOutResponse.prototype.sendError = function sendError(codeString, message) {
    var self = this;

    if (self.inreq.connection && // because selfpeer/connection
        self.inreq.connection.closing) {
        self.logger.info('ignoring outresponse.sendError on a closed connection', {
            codeString: codeString,
            errorMessage: message
        });
        return;
    }

    if (self.state === States.Done || self.state === States.Error) {
        self.emitError(errors.ResponseAlreadyDone({
            attempted: 'error frame',
            currentState: self.state,
            method: 'sendError',
            codeString: codeString,
            errMessage: message
        }));
    } else {
        if (self.span) {
            self.span.annotate('ss');
        }
        self.state = States.Error;

        self.codeString = codeString;
        self.message = message;
        self.channel.emitFastStat(
            'tchannel.inbound.calls.system-errors',
            'counter',
            1,
            new stat.InboundCallsSystemErrorsTags(
                self.inreq.callerName,
                self.inreq.serviceName,
                String(self.inreq.arg1),
                self.codeString
            )
        );
        self._sendError(codeString, message);
        self.emitFinish();
    }
};

TChannelOutResponse.prototype.emitError = function emitError(err) {
    var self = this;

    if (self.inreq && self.inreq.circuit) {
        self.inreq.circuit.state.onRequestError(err);
    }

    self.error = err;
    self.errorEvent.emit(self, err);
};

TChannelOutResponse.prototype.emitFinish = function emitFinish() {
    var self = this;
    var now = self.timers.now();

    if (self.end) {
        self.logger.warn('out response double emitFinish', self.extendLogInfo({
            now: now
        }));
        return;
    }

    self.end = now;

    var latency = self.end - self.inreq.start;

    self.channel.emitFastStat(
        'tchannel.inbound.calls.latency',
        'timing',
        latency,
        new stat.InboundCallsLatencyTags(
            self.inreq.callerName,
            self.inreq.serviceName,
            self.inreq.endpoint
        )
    );

    if (self.span) {
        self.spanEvent.emit(self, self.span);
    }

    if (self.inreq && self.inreq.circuit) {
        // TODO distingiush res.ok?
        // note that incoming requests do not have responseEvent and clear out
        // their response upon finish.
        if (errors.isUnhealthy(self.codeString)) {
            self.inreq.circuit.state.onRequestUnhealthy();
        } else {
            self.inreq.circuit.state.onRequestHealthy();
        }
    }

    self.finishEvent.emit(self);
};

TChannelOutResponse.prototype.setOk = function setOk(ok) {
    var self = this;
    if (self.state !== States.Initial) {
        self.emitError(errors.ResponseAlreadyStarted({
            state: self.state,
            method: 'setOk',
            ok: ok
        }));
        return false;
    }
    self.ok = ok;
    self.code = ok ? 0 : 1; // TODO: too coupled to v2 specifics?
    return true;
};

TChannelOutResponse.prototype.sendOk = function sendOk(res1, res2) {
    var self = this;
    self.setOk(true);
    self.send(res1, res2);
};

TChannelOutResponse.prototype.sendNotOk = function sendNotOk(res1, res2) {
    var self = this;
    if (self.state === States.Error) {
        self.logger.error('cannot send application error, already sent error frame', self.extendLogInfo({
            res1: res1,
            res2: res2
        }));
    } else {
        self.setOk(false);
        self.send(res1, res2);
    }
};

TChannelOutResponse.prototype.send = function send(res1, res2) {
    var self = this;

    /* send calls after finish() should be swallowed */
    if (self.end) {
        var inreqErrClass = self.inreq &&
                            self.inreq.err &&
                            errors.classify(self.inreq.err);

        switch (inreqErrClass) {
            case 'Timeout':
                self.logger.info('OutResponse.send() after inreq timed out', self.extendLogInfo({}));
                break;
            default:
                self.logger.warn('OutResponse called send() after end', self.extendLogInfo({}));
        }
        return self;
    }

    self.arg2 = res1;
    self.arg3 = res2;

    if (self.ok) {
        self.channel.emitFastStat(
            'tchannel.inbound.calls.success',
            'counter',
            1,
            new stat.InboundCallsSuccessTags(
                self.inreq.callerName,
                self.inreq.serviceName,
                self.inreq.endpoint
            )
        );
    } else {
        // TODO: add outResponse.setErrorType()
        self.channel.emitFastStat(
            'tchannel.inbound.calls.app-errors',
            'counter',
            1,
            new stat.InboundCallsAppErrorsTags(
                self.inreq.callerName,
                self.inreq.serviceName,
                self.inreq.endpoint,
                'unknown'
            )
        );
    }

    // TODO: may be spam, consider dropping
    if (self.inreq.connection && // because selfpeer/connection
        self.inreq.connection.closing) {
        self.logger.info('ignoring outresponse.send on a closed connection', self.extendLogInfo({}));
    } else {
        self.sendCallResponseFrame([self.arg1, res1, res2], true);
    }

    self.emitFinish();

    return self;
};

module.exports = TChannelOutResponse;
<|endoftext|>


<commit_before>/* jshint node:true, strict:false */

var
  requirejs = require('requirejs'),

  jasmine = (function () {
    var Jasmine = require('jasmine');
    return new Jasmine({ projectBaseDir: require('path').resolve() });
  }()),

  $ = (function () {
    var
      doc = require('jsdom').jsdom(),
      window = doc.parentWindow;
    return require('jquery')(window);
  }()),

  Backbone = require('backbone'),

  specs = [
    'spec/bbctrl-button.spec.js',
    'spec/bbctrl-switch.spec.js',
    'spec/bbctrl-text-field.spec.js'
  ];

Backbone.$ = $;

requirejs.config({
  nodeRequire: require,
  baseUrl: 'lib',
  paths: {}
});

requirejs(specs, function () {
  jasmine.configureDefaultReporter({});
  jasmine.env.execute();
});
<commit_msg>Make cli spec runner work regardless of cur. dir<commit_after>/* jshint node:true, strict:false */

var
  path = require('path'),

  requirejs = require('requirejs'),

  jasmine = (function () {
    var Jasmine = require('jasmine');
    return new Jasmine({ projectBaseDir: path.resolve() });
  }()),

  $ = (function () {
    var
      doc = require('jsdom').jsdom(),
      window = doc.parentWindow;
    return require('jquery')(window);
  }()),

  Backbone = require('backbone'),

  specs = [
    '../spec/bbctrl-button.spec',
    '../spec/bbctrl-switch.spec',
    '../spec/bbctrl-text-field.spec'
  ];

Backbone.$ = $;

requirejs.config({
  nodeRequire: require,
  baseUrl: path.resolve(__dirname, '../lib'),
  paths: {}
});

requirejs(specs, function () {
  jasmine.configureDefaultReporter({});
  jasmine.env.execute();
});
<|endoftext|>


<commit_before>//    The MIT License (MIT)
//
//    Copyright (c) 2014 YIOM
//
//    Permission is hereby granted, free of charge, to any person obtaining a copy
//    of this software and associated documentation files (the "Software"), to deal
//    in the Software without restriction, including without limitation the rights
//    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//    copies of the Software, and to permit persons to whom the Software is
//    furnished to do so, subject to the following conditions:
//
//    The above copyright notice and this permission notice shall be included in
//    all copies or substantial portions of the Software.
//
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//    THE SOFTWARE.

function Sketchpad(config) {
  // Enforces the context for all functions
  for (var key in this.constructor.prototype) {
    this[key] = this[key].bind(this);
  }

  // Warn the user if no DOM element was selected
  if (!config.hasOwnProperty('element')) {
    console.error('SKETCHPAD ERROR: No element selected');
    return;
  }

  this.element = config.element;

  // Width can be defined on the HTML or programatically
  this._width = config.width || $(this.element).attr('data-width') || 0;
  this._height = config.height || $(this.element).attr('data-height') || 0;

  // Pen attributes
  this.color = config.color || $(this.element).attr('data-color') || '#000000';
  this.penSize = config.penSize || $(this.element).attr('data-penSize') || 5;

  // ReadOnly sketchpads may not be modified
  this.readOnly = config.readOnly ||
                  $(this.element).attr('data-readOnly') ||
                  false;
  if (!this.readOnly) {
      $(this.element).css({cursor: 'crosshair'});
  }

  // Stroke control variables
  this.strokes = config.strokes || [];
  this._currentStroke = {
    color: null,
    size: null,
    lines: [],
  };

  // Undo History
  this.undoHistory = config.undoHistory || [];

  // Animation function calls
  this.animateIds = [];

  // Set sketching state
  this._sketching = false;

  // Setup canvas sketching listeners
  this.reset();
}

//
// Private API
//

Sketchpad.prototype._cursorPosition = function(event) {
  return {
    x: event.pageX - this.canvas.offsetLeft,
    y: event.pageY - this.canvas.offsetTop,
  };
};

Sketchpad.prototype._draw = function(start, end, color, size) {
  this._stroke(start, end, color, size, 'source-over');
};

Sketchpad.prototype._erase = function(start, end, color, size) {
  this._stroke(start, end, color, size, 'destination-out');
};

Sketchpad.prototype._stroke = function(start, end, color, size, compositeOperation) {
  this.context.save();
  this.context.lineJoin = 'round';
  this.context.lineCap = 'round';
  this.context.strokeStyle = color;
  this.context.lineWidth = size;
  this.context.globalCompositeOperation = compositeOperation;
  this.context.beginPath();
  this.context.moveTo(start.x, start.y);
  this.context.lineTo(end.x, end.y);
  this.context.closePath();
  this.context.stroke();

  this.context.restore();
};

//
// Callback Handlers
//

Sketchpad.prototype._mouseDown = function(event) {
  this._lastPosition = this._cursorPosition(event);
  this._currentStroke.color = this.color;
  this._currentStroke.size = this.penSize;
  this._currentStroke.lines = [];
  this._sketching = true;
  this.canvas.addEventListener('mousemove', this._mouseMove);
};

Sketchpad.prototype._mouseUp = function(event) {
  if (this._sketching) {
    this.strokes.push($.extend(true, {}, this._currentStroke));
    this._sketching = false;
  }
  this.canvas.removeEventListener('mousemove', this._mouseMove);
};

Sketchpad.prototype._mouseMove = function(event) {
  var currentPosition = this._cursorPosition(event);

  this._draw(this._lastPosition, currentPosition, this.color, this.penSize);
  this._currentStroke.lines.push({
    start: $.extend(true, {}, this._lastPosition),
    end: $.extend(true, {}, currentPosition),
  });

  this._lastPosition = currentPosition;
};

Sketchpad.prototype._touchStart = function(event) {
  event.preventDefault();
  if (this._sketching) {
    return;
  }
  this._lastPosition = this._cursorPosition(event.changedTouches[0]);
  this._currentStroke.color = this.color;
  this._currentStroke.size = this.penSize;
  this._currentStroke.lines = [];
  this._sketching = true;
  this.canvas.addEventListener('touchmove', this._touchMove, false);
};

Sketchpad.prototype._touchEnd = function(event) {
  event.preventDefault();
  if (this._sketching) {
    this.strokes.push($.extend(true, {}, this._currentStroke));
    this._sketching = false;
  }
  this.canvas.removeEventListener('touchmove', this._touchMove);
};

Sketchpad.prototype._touchCancel = function(event) {
  event.preventDefault();
  if (this._sketching) {
    this.strokes.push($.extend(true, {}, this._currentStroke));
    this._sketching = false;
  }
  this.canvas.removeEventListener('touchmove', this._touchMove);
};

Sketchpad.prototype._touchLeave = function(event) {
  event.preventDefault();
  if (this._sketching) {
    this.strokes.push($.extend(true, {}, this._currentStroke));
    this._sketching = false;
  }
  this.canvas.removeEventListener('touchmove', this._touchMove);
};

Sketchpad.prototype._touchMove = function(event) {
  event.preventDefault();
  var currentPosition = this._cursorPosition(event.changedTouches[0]);

  this._draw(this._lastPosition, currentPosition, this.color, this.penSize);
  this._currentStroke.lines.push({
    start: $.extend(true, {}, this._lastPosition),
    end: $.extend(true, {}, currentPosition),
  });

  this._lastPosition = currentPosition;
};

//
// Public API
//

Sketchpad.prototype.reset = function() {
  // Set attributes
  this.canvas = $(this.element)[0];
  this.canvas.width = this._width;
  this.canvas.height = this._height;
  this.context = this.canvas.getContext('2d');

  // Setup event listeners
  this.redraw(this.strokes);

  if (this.readOnly) {
    return;
  }

  // Mouse
  this.canvas.addEventListener('mousedown', this._mouseDown);
  this.canvas.addEventListener('mouseout', this._mouseUp);
  this.canvas.addEventListener('mouseup', this._mouseUp);

  // Touch
  this.canvas.addEventListener('touchstart', this._touchStart);
  this.canvas.addEventListener('touchend', this._touchEnd);
  this.canvas.addEventListener('touchcancel', this._touchCancel);
  this.canvas.addEventListener('touchleave', this._touchLeave);
};

Sketchpad.prototype.drawStroke = function(stroke) {
  for (var j = 0; j < stroke.lines.length; j++) {
    var line = stroke.lines[j];
    this._draw(line.start, line.end, stroke.color, stroke.size);
  }
};

Sketchpad.prototype.redraw = function(strokes) {
  for (var i = 0; i < strokes.length; i++) {
    this.drawStroke(strokes[i]);
  }
};

Sketchpad.prototype.toObject = function() {
  return {
    width: this.canvas.width,
    height: this.canvas.height,
    strokes: this.strokes,
    undoHistory: this.undoHistory,
  };
};

Sketchpad.prototype.toJSON = function() {
  return JSON.stringify(this.toObject());
};

Sketchpad.prototype.animate = function(ms, loop, loopDelay) {
  this.clear();
  var delay = ms;
  var callback = null;
  for (var i = 0; i < this.strokes.length; i++) {
    var stroke = this.strokes[i];
    for (var j = 0; j < stroke.lines.length; j++) {
      var line = stroke.lines[j];
      callback = this._draw.bind(this, line.start, line.end,
                                 stroke.color, stroke.size);
      this.animateIds.push(setTimeout(callback, delay));
      delay += ms;
    }
  }
  if (loop) {
    loopDelay = loopDelay || 0;
    callback = this.animate.bind(this, ms, loop, loopDelay);
    this.animateIds.push(setTimeout(callback, delay + loopDelay));
  }
};

Sketchpad.prototype.cancelAnimation = function() {
  for (var i = 0; i < this.animateIds.length; i++) {
    clearTimeout(this.animateIds[i]);
  }
};

Sketchpad.prototype.clear = function() {
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
};

Sketchpad.prototype.undo = function() {
  this.clear();
  var stroke = this.strokes.pop();
  if (stroke) {
    this.undoHistory.push(stroke);
    this.redraw(this.strokes);
  }
};

Sketchpad.prototype.redo = function() {
  var stroke = this.undoHistory.pop();
  if (stroke) {
    this.strokes.push(stroke);
    this.drawStroke(stroke);
  }
};
<commit_msg>Use offset relative to document on cursor positioning<commit_after>//    The MIT License (MIT)
//
//    Copyright (c) 2014 YIOM
//
//    Permission is hereby granted, free of charge, to any person obtaining a copy
//    of this software and associated documentation files (the "Software"), to deal
//    in the Software without restriction, including without limitation the rights
//    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//    copies of the Software, and to permit persons to whom the Software is
//    furnished to do so, subject to the following conditions:
//
//    The above copyright notice and this permission notice shall be included in
//    all copies or substantial portions of the Software.
//
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//    THE SOFTWARE.

function Sketchpad(config) {
  // Enforces the context for all functions
  for (var key in this.constructor.prototype) {
    this[key] = this[key].bind(this);
  }

  // Warn the user if no DOM element was selected
  if (!config.hasOwnProperty('element')) {
    console.error('SKETCHPAD ERROR: No element selected');
    return;
  }

  this.element = config.element;

  // Width can be defined on the HTML or programatically
  this._width = config.width || $(this.element).attr('data-width') || 0;
  this._height = config.height || $(this.element).attr('data-height') || 0;

  // Pen attributes
  this.color = config.color || $(this.element).attr('data-color') || '#000000';
  this.penSize = config.penSize || $(this.element).attr('data-penSize') || 5;

  // ReadOnly sketchpads may not be modified
  this.readOnly = config.readOnly ||
                  $(this.element).attr('data-readOnly') ||
                  false;
  if (!this.readOnly) {
      $(this.element).css({cursor: 'crosshair'});
  }

  // Stroke control variables
  this.strokes = config.strokes || [];
  this._currentStroke = {
    color: null,
    size: null,
    lines: [],
  };

  // Undo History
  this.undoHistory = config.undoHistory || [];

  // Animation function calls
  this.animateIds = [];

  // Set sketching state
  this._sketching = false;

  // Setup canvas sketching listeners
  this.reset();
}

//
// Private API
//

Sketchpad.prototype._cursorPosition = function(event) {
  return {
    x: event.pageX - $(this.canvas).offset().left,
    y: event.pageY - $(this.canvas).offset().top,
  };
};

Sketchpad.prototype._draw = function(start, end, color, size) {
  this._stroke(start, end, color, size, 'source-over');
};

Sketchpad.prototype._erase = function(start, end, color, size) {
  this._stroke(start, end, color, size, 'destination-out');
};

Sketchpad.prototype._stroke = function(start, end, color, size, compositeOperation) {
  this.context.save();
  this.context.lineJoin = 'round';
  this.context.lineCap = 'round';
  this.context.strokeStyle = color;
  this.context.lineWidth = size;
  this.context.globalCompositeOperation = compositeOperation;
  this.context.beginPath();
  this.context.moveTo(start.x, start.y);
  this.context.lineTo(end.x, end.y);
  this.context.closePath();
  this.context.stroke();

  this.context.restore();
};

//
// Callback Handlers
//

Sketchpad.prototype._mouseDown = function(event) {
  this._lastPosition = this._cursorPosition(event);
  this._currentStroke.color = this.color;
  this._currentStroke.size = this.penSize;
  this._currentStroke.lines = [];
  this._sketching = true;
  this.canvas.addEventListener('mousemove', this._mouseMove);
};

Sketchpad.prototype._mouseUp = function(event) {
  if (this._sketching) {
    this.strokes.push($.extend(true, {}, this._currentStroke));
    this._sketching = false;
  }
  this.canvas.removeEventListener('mousemove', this._mouseMove);
};

Sketchpad.prototype._mouseMove = function(event) {
  var currentPosition = this._cursorPosition(event);

  this._draw(this._lastPosition, currentPosition, this.color, this.penSize);
  this._currentStroke.lines.push({
    start: $.extend(true, {}, this._lastPosition),
    end: $.extend(true, {}, currentPosition),
  });

  this._lastPosition = currentPosition;
};

Sketchpad.prototype._touchStart = function(event) {
  event.preventDefault();
  if (this._sketching) {
    return;
  }
  this._lastPosition = this._cursorPosition(event.changedTouches[0]);
  this._currentStroke.color = this.color;
  this._currentStroke.size = this.penSize;
  this._currentStroke.lines = [];
  this._sketching = true;
  this.canvas.addEventListener('touchmove', this._touchMove, false);
};

Sketchpad.prototype._touchEnd = function(event) {
  event.preventDefault();
  if (this._sketching) {
    this.strokes.push($.extend(true, {}, this._currentStroke));
    this._sketching = false;
  }
  this.canvas.removeEventListener('touchmove', this._touchMove);
};

Sketchpad.prototype._touchCancel = function(event) {
  event.preventDefault();
  if (this._sketching) {
    this.strokes.push($.extend(true, {}, this._currentStroke));
    this._sketching = false;
  }
  this.canvas.removeEventListener('touchmove', this._touchMove);
};

Sketchpad.prototype._touchLeave = function(event) {
  event.preventDefault();
  if (this._sketching) {
    this.strokes.push($.extend(true, {}, this._currentStroke));
    this._sketching = false;
  }
  this.canvas.removeEventListener('touchmove', this._touchMove);
};

Sketchpad.prototype._touchMove = function(event) {
  event.preventDefault();
  var currentPosition = this._cursorPosition(event.changedTouches[0]);

  this._draw(this._lastPosition, currentPosition, this.color, this.penSize);
  this._currentStroke.lines.push({
    start: $.extend(true, {}, this._lastPosition),
    end: $.extend(true, {}, currentPosition),
  });

  this._lastPosition = currentPosition;
};

//
// Public API
//

Sketchpad.prototype.reset = function() {
  // Set attributes
  this.canvas = $(this.element)[0];
  this.canvas.width = this._width;
  this.canvas.height = this._height;
  this.context = this.canvas.getContext('2d');

  // Setup event listeners
  this.redraw(this.strokes);

  if (this.readOnly) {
    return;
  }

  // Mouse
  this.canvas.addEventListener('mousedown', this._mouseDown);
  this.canvas.addEventListener('mouseout', this._mouseUp);
  this.canvas.addEventListener('mouseup', this._mouseUp);

  // Touch
  this.canvas.addEventListener('touchstart', this._touchStart);
  this.canvas.addEventListener('touchend', this._touchEnd);
  this.canvas.addEventListener('touchcancel', this._touchCancel);
  this.canvas.addEventListener('touchleave', this._touchLeave);
};

Sketchpad.prototype.drawStroke = function(stroke) {
  for (var j = 0; j < stroke.lines.length; j++) {
    var line = stroke.lines[j];
    this._draw(line.start, line.end, stroke.color, stroke.size);
  }
};

Sketchpad.prototype.redraw = function(strokes) {
  for (var i = 0; i < strokes.length; i++) {
    this.drawStroke(strokes[i]);
  }
};

Sketchpad.prototype.toObject = function() {
  return {
    width: this.canvas.width,
    height: this.canvas.height,
    strokes: this.strokes,
    undoHistory: this.undoHistory,
  };
};

Sketchpad.prototype.toJSON = function() {
  return JSON.stringify(this.toObject());
};

Sketchpad.prototype.animate = function(ms, loop, loopDelay) {
  this.clear();
  var delay = ms;
  var callback = null;
  for (var i = 0; i < this.strokes.length; i++) {
    var stroke = this.strokes[i];
    for (var j = 0; j < stroke.lines.length; j++) {
      var line = stroke.lines[j];
      callback = this._draw.bind(this, line.start, line.end,
                                 stroke.color, stroke.size);
      this.animateIds.push(setTimeout(callback, delay));
      delay += ms;
    }
  }
  if (loop) {
    loopDelay = loopDelay || 0;
    callback = this.animate.bind(this, ms, loop, loopDelay);
    this.animateIds.push(setTimeout(callback, delay + loopDelay));
  }
};

Sketchpad.prototype.cancelAnimation = function() {
  for (var i = 0; i < this.animateIds.length; i++) {
    clearTimeout(this.animateIds[i]);
  }
};

Sketchpad.prototype.clear = function() {
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
};

Sketchpad.prototype.undo = function() {
  this.clear();
  var stroke = this.strokes.pop();
  if (stroke) {
    this.undoHistory.push(stroke);
    this.redraw(this.strokes);
  }
};

Sketchpad.prototype.redo = function() {
  var stroke = this.undoHistory.pop();
  if (stroke) {
    this.strokes.push(stroke);
    this.drawStroke(stroke);
  }
};
<|endoftext|>