<commit_before>define([
    'jquery',
    '/bower_components/chainpad-crypto/crypto.js',
    '/bower_components/chainpad-listmap/chainpad-listmap.js',
    '/common/toolbar3.js',
    '/bower_components/nthen/index.js',
    '/common/sframe-common.js',
    '/common/common-interface.js',
    '/common/common-hash.js',
    '/todo/todo.js',
    '/customize/messages.js',
    '/bower_components/sortablejs/Sortable.min.js',

    'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
    'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
    'less!/customize/src/less2/main.less',
], function (
    $,
    Crypto,
    Listmap,
    Toolbar,
    nThen,
    SFCommon,
    UI,
    Hash,
    Todo,
    Messages,
    Sortable
    )
{
    var APP = window.APP = {};

    var common;
    var sFrameChan;
    nThen(function (waitFor) {
        $(waitFor(UI.addLoadingScreen));
        SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
    }).nThen(function (waitFor) {
        sFrameChan = common.getSframeChannel();
        sFrameChan.onReady(waitFor());
    }).nThen(function (/*waitFor*/) {
        var $body = $('body');
        var $list = $('#cp-app-todo-taskslist');

        var removeTips = function () {
            UI.clearTooltips();
        };

        var onReady = function () {
            var todo = Todo.init(APP.lm.proxy);

            Sortable.create($list[0], {
                store: {
                    get: function () {
                        return todo.getOrder();
                    },
                    set: function (sortable) {
                        todo.reorder(sortable.toArray());
                    }
                }
            });

            var deleteTask = function(id) {
                todo.remove(id);

                var $els = $list.find('.cp-app-todo-task').filter(function (i, el) {
                    return $(el).data('id') === id;
                });
                $els.fadeOut(null, function () {
                    $els.remove();
                    removeTips();
                });
                //APP.display();
            };

            // TODO make this actually work, and scroll to bottom...
            var scrollTo = function (t) {
                $list.animate({
                    scrollTop: t,
                });
            };
            scrollTo = scrollTo;

            var makeCheckbox = function (id, cb) {
                var entry = APP.lm.proxy.data[id];
                if (!entry || typeof(entry) !== 'object') {
                    return void console.log('entry undefined');
                }

                var checked = entry.state === 1 ?
                    'cp-app-todo-task-checkbox-checked fa-check-square-o':
                    'cp-app-todo-task-checkbox-unchecked fa-square-o';

                var title = entry.state === 1?
                    Messages.todo_markAsIncompleteTitle:
                    Messages.todo_markAsCompleteTitle;
                    title = title;

                removeTips();
                return $('<span>', {
                    'class': 'cp-app-todo-task-checkbox fa ' + checked,
                    //title: title,
                }).on('click', function () {
                    entry.state = (entry.state + 1) % 2;
                    if (typeof(cb) === 'function') {
                        cb(entry.state);
                    }
                });
            };

            var addTaskUI = function (el, animate) {
                if (!el) { return; }
                var $taskDiv = $('<div>', {
                    'class': 'cp-app-todo-task'
                });
                if (animate) {
                    $taskDiv.prependTo($list);
                } else {
                    $taskDiv.appendTo($list);
                }
                $taskDiv.data('id', el);
                $taskDiv.attr('data-id', el);

                makeCheckbox(el, function (/*state*/) {
                    APP.display();
                })
                .appendTo($taskDiv);

                var entry = APP.lm.proxy.data[el];
                if (!entry || typeof(entry) !== 'object') {
                    return void console.log('entry undefined');
                }

                if (entry.state) {
                    $taskDiv.addClass('cp-app-todo-task-complete');
                }

                var $span = $('<span>', { 'class': 'cp-app-todo-task-text' });

                var $input = $('<input>', {
                    type: 'text',
                    'class': 'cp-app-todo-task-input'
                }).val(entry.task).keydown(function (e) {
                    if (e.which === 13) {
                        todo.val(el, 'task', $input.val().trim());
                        $input.hide();
                        $span.text($input.val().trim());
                        $span.show();
                    }
                }).appendTo($taskDiv);

                $span.text(entry.task)
                    .appendTo($taskDiv)
                    .click(function () {
                        $input.show();
                        $span.hide();
                    });
              /*$('<span>', { 'class': 'cp-app-todo-task-date' })
                    .text(new Date(entry.ctime).toLocaleString())
                    .appendTo($taskDiv);*/
                $('<button>', {
                    'class': 'fa fa-times cp-app-todo-task-remove btn btn-danger',
                    title: Messages.todo_removeTaskTitle,
                }).appendTo($taskDiv).on('click', function() {
                    deleteTask(el);
                });

                if (animate) {
                    $taskDiv.hide();
                    window.setTimeout(function () {
                        // ???
                        $taskDiv.fadeIn();
                    }, 0);
                }
                removeTips();
            };
            var display = APP.display = function () {
                $list.empty();
                removeTips();
                APP.lm.proxy.order.forEach(function (el) {
                    addTaskUI(el);
                });
                //scrollTo('300px');
            };

            var addTask = function () {
                var $input = $('#cp-app-todo-newtodo');
                // if the input is empty after removing leading and trailing spaces
                // don't create a new entry
                if (!$input.val().trim()) { return; }

                var obj = {
                    "state": 0,
                    "task": $input.val(),
                    "ctime": +new Date(),
                    "mtime": +new Date()
                };

                var id = Hash.createChannelId();
                todo.add(id, obj);

                $input.val("");
                addTaskUI(id, true);
                //display();
            };

            var $formSubmit = $('.cp-app-todo-create-form button').on('click', addTask);
            $('#cp-app-todo-newtodo').on('keypress', function (e) {
                switch (e.which) {
                    case 13:
                        $formSubmit.click();
                        break;
                    default:
                        //console.log(e.which);
                }
            }).focus();

            var editTask = function () {

            };
            editTask = editTask;

            display();
            UI.removeLoadingScreen();
        };

        var onInit = function () {
            $body.on('dragover', function (e) { e.preventDefault(); });
            $body.on('drop', function (e) { e.preventDefault(); });

            var $bar = $('.cp-toolbar-container');

            var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle'];
            var configTb = {
                displayed: displayed,
                sfCommon: common,
                $container: $bar,
                pageTitle: Messages.todo_title,
                metadataMgr: common.getMetadataMgr(),
            };
            APP.toolbar = Toolbar.create(configTb);
            APP.toolbar.$rightside.hide();
        };
        var createTodo = function() {
            var listmapConfig = {
                data: {},
                common: common,
                userName: 'todo',
                logLevel: 1
            };

            var lm = APP.lm = Listmap.create(listmapConfig);

            lm.proxy.on('create', onInit)
                    .on('ready', onReady);
        };
        createTodo();
    });
});
<commit_msg>Fix cursor position when editing a task in todo<commit_after>define([
    'jquery',
    '/bower_components/chainpad-crypto/crypto.js',
    '/bower_components/chainpad-listmap/chainpad-listmap.js',
    '/common/toolbar3.js',
    '/bower_components/nthen/index.js',
    '/common/sframe-common.js',
    '/common/common-interface.js',
    '/common/common-hash.js',
    '/todo/todo.js',
    '/customize/messages.js',
    '/bower_components/sortablejs/Sortable.min.js',

    'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
    'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
    'less!/customize/src/less2/main.less',
], function (
    $,
    Crypto,
    Listmap,
    Toolbar,
    nThen,
    SFCommon,
    UI,
    Hash,
    Todo,
    Messages,
    Sortable
    )
{
    var APP = window.APP = {};

    var common;
    var sFrameChan;
    nThen(function (waitFor) {
        $(waitFor(UI.addLoadingScreen));
        SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
    }).nThen(function (waitFor) {
        sFrameChan = common.getSframeChannel();
        sFrameChan.onReady(waitFor());
    }).nThen(function (/*waitFor*/) {
        var $body = $('body');
        var $list = $('#cp-app-todo-taskslist');

        var removeTips = function () {
            UI.clearTooltips();
        };

        var onReady = function () {
            var todo = Todo.init(APP.lm.proxy);

            Sortable.create($list[0], {
                store: {
                    get: function () {
                        return todo.getOrder();
                    },
                    set: function (sortable) {
                        todo.reorder(sortable.toArray());
                    }
                }
            });

            var deleteTask = function(id) {
                todo.remove(id);

                var $els = $list.find('.cp-app-todo-task').filter(function (i, el) {
                    return $(el).data('id') === id;
                });
                $els.fadeOut(null, function () {
                    $els.remove();
                    removeTips();
                });
                //APP.display();
            };

            // TODO make this actually work, and scroll to bottom...
            var scrollTo = function (t) {
                $list.animate({
                    scrollTop: t,
                });
            };
            scrollTo = scrollTo;

            var makeCheckbox = function (id, cb) {
                var entry = APP.lm.proxy.data[id];
                if (!entry || typeof(entry) !== 'object') {
                    return void console.log('entry undefined');
                }

                var checked = entry.state === 1 ?
                    'cp-app-todo-task-checkbox-checked fa-check-square-o':
                    'cp-app-todo-task-checkbox-unchecked fa-square-o';

                var title = entry.state === 1?
                    Messages.todo_markAsIncompleteTitle:
                    Messages.todo_markAsCompleteTitle;
                    title = title;

                removeTips();
                return $('<span>', {
                    'class': 'cp-app-todo-task-checkbox fa ' + checked,
                    //title: title,
                }).on('click', function () {
                    entry.state = (entry.state + 1) % 2;
                    if (typeof(cb) === 'function') {
                        cb(entry.state);
                    }
                });
            };

            var addTaskUI = function (el, animate) {
                if (!el) { return; }
                var $taskDiv = $('<div>', {
                    'class': 'cp-app-todo-task'
                });
                if (animate) {
                    $taskDiv.prependTo($list);
                } else {
                    $taskDiv.appendTo($list);
                }
                $taskDiv.data('id', el);
                $taskDiv.attr('data-id', el);

                makeCheckbox(el, function (/*state*/) {
                    APP.display();
                })
                .appendTo($taskDiv);

                var entry = APP.lm.proxy.data[el];
                if (!entry || typeof(entry) !== 'object') {
                    return void console.log('entry undefined');
                }

                if (entry.state) {
                    $taskDiv.addClass('cp-app-todo-task-complete');
                }

                var $span = $('<span>', { 'class': 'cp-app-todo-task-text' });

                var $input = $('<input>', {
                    type: 'text',
                    'class': 'cp-app-todo-task-input'
                }).val(entry.task).keydown(function (e) {
                    if (e.which === 13) {
                        todo.val(el, 'task', $input.val().trim());
                        $input.hide();
                        $span.text($input.val().trim());
                        $span.show();
                    }
                }).on('click mousedown', function (e) {
                    e.stopPropagation();
                }).appendTo($taskDiv);

                $span.text(entry.task)
                    .appendTo($taskDiv)
                    .click(function () {
                        $input.show();
                        $span.hide();
                    });
              /*$('<span>', { 'class': 'cp-app-todo-task-date' })
                    .text(new Date(entry.ctime).toLocaleString())
                    .appendTo($taskDiv);*/
                $('<button>', {
                    'class': 'fa fa-times cp-app-todo-task-remove btn btn-danger',
                    title: Messages.todo_removeTaskTitle,
                }).appendTo($taskDiv).on('click', function() {
                    deleteTask(el);
                });

                if (animate) {
                    $taskDiv.hide();
                    window.setTimeout(function () {
                        // ???
                        $taskDiv.fadeIn();
                    }, 0);
                }
                removeTips();
            };
            var display = APP.display = function () {
                $list.empty();
                removeTips();
                APP.lm.proxy.order.forEach(function (el) {
                    addTaskUI(el);
                });
                //scrollTo('300px');
            };

            var addTask = function () {
                var $input = $('#cp-app-todo-newtodo');
                // if the input is empty after removing leading and trailing spaces
                // don't create a new entry
                if (!$input.val().trim()) { return; }

                var obj = {
                    "state": 0,
                    "task": $input.val(),
                    "ctime": +new Date(),
                    "mtime": +new Date()
                };

                var id = Hash.createChannelId();
                todo.add(id, obj);

                $input.val("");
                addTaskUI(id, true);
                //display();
            };

            var $formSubmit = $('.cp-app-todo-create-form button').on('click', addTask);
            $('#cp-app-todo-newtodo').on('keypress', function (e) {
                switch (e.which) {
                    case 13:
                        $formSubmit.click();
                        break;
                    default:
                        //console.log(e.which);
                }
            }).focus();

            var editTask = function () {

            };
            editTask = editTask;

            display();
            UI.removeLoadingScreen();
        };

        var onInit = function () {
            $body.on('dragover', function (e) { e.preventDefault(); });
            $body.on('drop', function (e) { e.preventDefault(); });

            var $bar = $('.cp-toolbar-container');

            var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle'];
            var configTb = {
                displayed: displayed,
                sfCommon: common,
                $container: $bar,
                pageTitle: Messages.todo_title,
                metadataMgr: common.getMetadataMgr(),
            };
            APP.toolbar = Toolbar.create(configTb);
            APP.toolbar.$rightside.hide();
        };
        var createTodo = function() {
            var listmapConfig = {
                data: {},
                common: common,
                userName: 'todo',
                logLevel: 1
            };

            var lm = APP.lm = Listmap.create(listmapConfig);

            lm.proxy.on('create', onInit)
                    .on('ready', onReady);
        };
        createTodo();
    });
});
<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var async = require('async');
var sundial = require('sundial');
var struct = require('../struct.js')();
var annotate = require('../eventAnnotations');

var TZOUtil = require('../TimezoneOffsetUtil');

var debug = require('../bows')('FreeStyleLiteDriver');

module.exports = function (config) {
  var cfg = _.clone(config);
  cfg.deviceData = null;
  var serialDevice = config.deviceComms;

  // With no date & time settings changes available,
  // timezone is applied across-the-board
  cfg.tzoUtil = new TZOUtil(cfg.timezone, new Date().toISOString(), []);

  var STX = 0x02;
  var ETX = 0x03;
  var CR = 0x0D;
  var LF = 0x0A;

  var dateTimePattern = '([A-Za-z ]{4} [0-3][0-9] [0-9]{4} ' +
                        '[012][0-9]:[0-5][0-9](?::[0-6][0-9])?)';
  function parseDateTime(match, index) {
    var fmt = 'MMMM DD YYYY HH:mm:ss';
    var parsed = sundial.parseFromFormat(match[index], fmt);
    var dev = sundial.formatDeviceTime(parsed);
    var utcInfo = cfg.tzoUtil.lookup(parsed);
    return {
      dev: dev,
      utc: utcInfo.time,
      timezoneOffset: utcInfo.timezoneOffset,
      conversionOffset: utcInfo.conversionOffset
    };
  }

  var patterns = {
    serialNumber: {
      pattern: '^(?:[A-Z]-....-.....|[A-Z][A-Z][A-Z]....-.....|[XC][A-Z][GM]........)$',
      handler: function(match, packet) {
        packet.serialNumber = match[0];
      }
    },
    softwareVersion: {
      pattern: '^[0-9.]{4,13}',
      handler: function(match, packet) {
        packet.softwareVersion = match[0];
      }
    },
    deviceTime: {
      pattern: '^' + dateTimePattern + '$',
      handler: function(match, packet) {
        var times = parseDateTime(match, 1);
        packet.deviceTime = times.dev;
        packet.time = times.utc;
      }
    },
    numEntries: {
      pattern: '^(?:[0-9]{3}|Log Empty)$',
      handler: function(match, packet) {
        packet.numEntries = parseInt(match[0], 10);
        if (_.isNaN(packet.numEntries)) {
          packet.numEntries = 0;
        }
      }
    },
    notFound: {
      pattern: '^Log Not Found$',
      handler: function(match, packet) {
        packet.notFound = true;
      }
    },
    end: {
      pattern: '^END$',
      handler: function(match, packet) {
        packet.end = true;
      }
    },
    ok: {
      pattern: '^CMD OK$',
      handler: function(match, packet) {
        packet.ok = true;
      }
    },
    fail: {
      pattern: '^CMD Fail!$',
      handler: function(match, packet) {
        packet.fail = true;
      }
    },
    logEntry: {
      pattern: '^\s*([0-9HL][0-9OI][0-9 ])  ' + dateTimePattern + ' [0-9A-Fa-f][0-9A-Fa-f] (0x[0-9A-Fa-f][0-9A-Fa-f])',
      handler: function(match, packet) {
        var entry = {};
        var reading = match[1];

        var testType = match[3];

        if(testType[3] === '1') {
          debug('Marking as control test');
          entry.control = true;
        }else{
          entry.control = false;
        }

        //this works as a reference, from the standard HI/LO values
        //in the FreeStyle meters
        if (reading == 'HI ') {
            entry.reading = 501;
            entry.annotations = [{
              code: 'bg/out-of-range',
              value: 'high',
              threshold: 500
            }];
        } else if (reading == 'LO ') {
          entry.reading = 19;
          entry.annotations = [{
            code: 'bg/out-of-range',
            value: 'low',
            threshold: 20
          }];
        } else {
          entry.reading = parseInt(reading, 10);
        }

        entry.datetime = parseDateTime(match, 2);
        packet.logEntries.push(entry);
      }
    },
    checksum: {
      pattern: '^(0x[0-9A-F]{4})  END',
      handler: function(match, packet) {
        packet.receivedChecksum = parseInt(match[1], 16);
      }
    }
  };
  var logEntry = 'logEntry';

  var extractPacket = function (bytes) {
    var packet = {
      bytes: bytes,
      valid: false,
      packet_len: 0,
      lcb: 0,
      lines: [],
      logEntries: []
    };

    var startIndex = 0;
    var endIndex = _.indexOf(bytes, ETX, startIndex + 1);
    if (endIndex === -1) {

      return packet;
    }

    function stringFromBytes(bytes, start, end) {
      var s = '';
      for (var i=start; i<end; ++i) {
        s += String.fromCharCode(bytes[i]);
      }
      return s;
    }

    function searchForSequence(bytes, sequence) {
      var found = false;
      for (var ix=0; ix<bytes.length; ++ix) {
        found = true;
        for (var jx=0; jx<sequence.length; ++jx) {
          if (bytes[ix + jx] != sequence[jx]) {
            found = false;
            break;
          }
        }
        if (found) {
          return ix;
        }
      }
      return -1;
    }

    var checksumIndex = searchForSequence(bytes, [0x0D, 0x0A, 0x30, 0x78]);
    checksumIndex += 2;
    if (checksumIndex !== -1) {
      var sum = 0;
      for (var i=startIndex; i<checksumIndex; ++i) {
        sum += bytes[i];
      }
      packet.calculatedChecksum = sum & 0xFFFF;
    }

    packet.packet_len = endIndex;

    var startLine = startIndex;
    while (startLine < endIndex) {
      var endLine = _.indexOf(bytes, CR, startLine);
      if (endLine === -1) {
        endLine = endIndex - 1;
      }
      if (bytes[endLine + 1] != LF) {
        packet.packet_len = endLine;
        return packet;
      }

      var line = stringFromBytes(bytes, startLine, endLine);
      packet.lines.push(line);
      startLine = endLine + 2;
    }

    var patkeys = _.keys(patterns);
    _.remove(patkeys, function(k) { return k === logEntry; });
    patkeys.unshift(logEntry);

    _.forEach(packet.lines, function(line, index) {
      var found = false;
      _.forEach(patkeys, function(key) {
        var m = line.trim().match(patterns[key].pattern);
        if (m) {
          found = true;
          patterns[key].handler(m, packet);
        }
      });
      if (!found && line !== '') {
        debug('No pattern match found for "' + line + '"');
      }
    });

    packet.valid = true;
    if (packet.receivedChecksum && packet.receivedChecksum != packet.calculatedChecksum) {
      debug('Failed checksum! rcv: ', packet.receivedChecksum, ' calc: ', packet.calculatedChecksum);
      packet.valid = false;
    }
    if (packet.numEntries && packet.logEntries.length !== packet.numEntries) {
      debug('Entry count failure! num: ', packet.numEntries, ' found: ', packet.logEntries.length);
      packet.valid = false;
    }
    if (packet.fail) {
      packet.valid = false;
    }
    return packet;
  };

  var freeStyleErrorHandler = function(info) {
    if (info.connectionId && info.error) {
      if (info.error == 'timeout') {
        return [ ETX ];
      }
    }
  };

  var freeStyleMessageHandler = function (buffer) {
    var discardCount = 0;
    while (buffer.len() > discardCount && buffer.get(0) == ETX) {
      ++discardCount;
    }

    if (discardCount) {
      buffer.discard(discardCount);
    }

    if (buffer.len() < 11) {
      return false;
    }

    var packet = extractPacket(buffer.bytes());
    if (packet.packet_len !== 0) {
      buffer.discard(packet.packet_len);
    }

    if (packet.valid) {
      return packet;
    } else {
      return null;
    }
  };

  var listenForPacket = function (timeout, parser, callback) {
    var abortTimer = setTimeout(function () {
      clearInterval(listenTimer);
      debug('TIMEOUT');
      callback('TIMEOUT', null);
    }, timeout);

    var listenTimer = setInterval(function () {
      if (serialDevice.hasAvailablePacket()) {
        var pkt = serialDevice.nextPacket();
        clearTimeout(abortTimer);
        clearInterval(listenTimer);
        pkt.parsed_payload = parser(pkt);
        callback(null, pkt);
      }
    }, 20);
  };

  var freeStyleCommandResponse = function (commandpacket, callback) {
    serialDevice.writeSerial(commandpacket.packet, function (err) {
      if (err) {
        callback(err, null);
      }
      listenForPacket(3000, commandpacket.parser, function(err, result) {
        if (err) {
          callback(err, null);
        } else {
          callback(err, result);
        }
      });
    });
  };

  var xtest = function() {
    return {
      packet: struct.packString('mem'),
      parser: function (result) {
        return result;
      }
    };
  };

  var xGetData = function() {
    return {
      packet: struct.packString('mem'),
      parser: function (result) {
        return result;
      }
    };
  };

  var getSerialNumber = function (obj, cb) {
    var cmd = xtest();
    freeStyleCommandResponse(cmd, function (err, result) {
      if (err) {
        debug('Failure trying to talk to device.');
        debug(err);
        debug(result);
        cb(err, null);
      } else {
        _.assign(obj, _.pick(result, 'serialNumber', 'softwareVersion', 'deviceTime'));
        obj.model = 'AbbottFreeStyleLite';
        obj.id = obj.model + ' ' + obj.serialNumber;
        cb(null, obj);
      }
    });
  };

  var getAllData = function (obj, cb) {
    var cmd = xGetData();
    freeStyleCommandResponse(cmd, function (err, result) {
      if (err) {
        debug('Failure trying to talk to device.');
        debug(err);
        debug(result);
        cb(err, null);
      } else {
        _.assign(obj, result);
        cb(null, obj);
      }
    });
  };

  var prepBGData = function (progress, data) {
    cfg.builder.setDefaults({ deviceId: data.id });
    var dataToPost = [];
    for (var i = 0; i < data.logEntries.length; ++i) {
      var datum = data.logEntries[i];

        if(datum.control === true) {
          debug('Discarding control');
          continue;
        }

        var smbg = cfg.builder.makeSMBG()
          .with_value(datum.reading)
          .with_deviceTime(datum.datetime.dev)
          .with_timezoneOffset(datum.datetime.timezoneOffset)
          .with_conversionOffset(datum.datetime.conversionOffset)
          .with_time(datum.datetime.utc)
          .with_units('mg/dL')
          .set('index', i)
          .done();
        if (datum.annotations) {
          _.each(datum.annotations, function(ann) {
            annotate.annotateEvent(smbg, ann);
          });
        }
        dataToPost.push(smbg);
    }
    return dataToPost;
  };

  var probe = function (cb) {
    var cmd = xtest();
    freeStyleCommandResponse(cmd, function (err, result) {
      if (err) {
        debug('Failure trying to talk to device.');
        debug(err);
        debug(result);
      }
      cb(err, result);
    });
  };

  return {
    // using the default detect for this driver
    // detect: function(cb) {
    // },

    // this function starts the chain, so it has to create but not accept
    // the result (data) object; it's then passed down the rest of the chain
    setup: function (deviceInfo, progress, cb) {
      debug('in setup!');
      progress(100);
      cb(null, {deviceInfo: deviceInfo});
    },

    connect: function (progress, data, cb) {
      debug('in connect!');
      var handlers = {
        packetHandler: freeStyleMessageHandler,
        errorHandler: freeStyleErrorHandler
      };

      cfg.deviceComms.connect(data.deviceInfo, handlers, probe, function(err) {
        if (err) {
          return cb(err);
        }
        getSerialNumber({}, function (err, result) {
          progress(100);
          data.connect = true;
          _.assign(data, result);
          debug(data);
          cb(null, data);
        });
      });
    },

    getConfigInfo: function (progress, data, cb) {
      progress(100);
      cb(null, data);
    },

    fetchData: function (progress, data, cb) {
      debug('in fetchData');
      getAllData({}, function (err, result) {
        progress(100);
        data.connect = true;
        debug(result);
        data.logEntries = result.logEntries;
        data.numEntries = result.numEntries;
        cb(null, data);
      });
    },

    processData: function (progress, data, cb) {
      progress(0);
      data.post_records = _.map(prepBGData(progress, data), function(d) {
        delete d.index;
        return d;
      });
      progress(100);
      data.processData = true;
      debug(data);
      cb(null, data);
    },

    uploadData: function (progress, data, cb) {
      progress(0);
      var sessionInfo = {
        deviceTags: ['bgm'],
        deviceManufacturers: ['Abbott'],
        deviceModel: 'FreeStyle Lite',
        deviceSerialNumber: data.serialNumber,
        deviceId: data.id,
        start: sundial.utcDateString(),
        timeProcessing: cfg.tzoUtil.type,
        tzName : cfg.timezone,
        version: cfg.version
      };

      cfg.api.upload.toPlatform(data.post_records, sessionInfo, progress, cfg.groupId, function (err, result) {
        if (err) {
          debug(err);
          progress(100);
          return cb(err, data);
        } else {
          progress(100);
          return cb(null, data);
        }
      });
    },

    disconnect: function (progress, data, cb) {
      progress(100);
      data.disconnect = true;
      cb(null, data);
    },

    cleanup: function (progress, data, cb) {
      cfg.deviceComms.clearPacketHandler();
      cfg.deviceComms.clearErrorHandler();
      cfg.deviceComms.disconnect(function() {
        progress(100);
        data.cleanup = true;
        cb(null, data);
      });
    },

    testDriver: function(config) {
      var progress = function(v) {
        debug('progress: ', v);
      };
      var data = {};
      this.connect(progress, data, function(err, result) {
        debug('result:', result);
      });
    }
  };

};
<commit_msg>Updated comment regarding HI/LO glucose range<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var async = require('async');
var sundial = require('sundial');
var struct = require('../struct.js')();
var annotate = require('../eventAnnotations');

var TZOUtil = require('../TimezoneOffsetUtil');

var debug = require('../bows')('FreeStyleLiteDriver');

module.exports = function (config) {
  var cfg = _.clone(config);
  cfg.deviceData = null;
  var serialDevice = config.deviceComms;

  // With no date & time settings changes available,
  // timezone is applied across-the-board
  cfg.tzoUtil = new TZOUtil(cfg.timezone, new Date().toISOString(), []);

  var STX = 0x02;
  var ETX = 0x03;
  var CR = 0x0D;
  var LF = 0x0A;

  var dateTimePattern = '([A-Za-z ]{4} [0-3][0-9] [0-9]{4} ' +
                        '[012][0-9]:[0-5][0-9](?::[0-6][0-9])?)';
  function parseDateTime(match, index) {
    var fmt = 'MMMM DD YYYY HH:mm:ss';
    var parsed = sundial.parseFromFormat(match[index], fmt);
    var dev = sundial.formatDeviceTime(parsed);
    var utcInfo = cfg.tzoUtil.lookup(parsed);
    return {
      dev: dev,
      utc: utcInfo.time,
      timezoneOffset: utcInfo.timezoneOffset,
      conversionOffset: utcInfo.conversionOffset
    };
  }

  var patterns = {
    serialNumber: {
      pattern: '^(?:[A-Z]-....-.....|[A-Z][A-Z][A-Z]....-.....|[XC][A-Z][GM]........)$',
      handler: function(match, packet) {
        packet.serialNumber = match[0];
      }
    },
    softwareVersion: {
      pattern: '^[0-9.]{4,13}',
      handler: function(match, packet) {
        packet.softwareVersion = match[0];
      }
    },
    deviceTime: {
      pattern: '^' + dateTimePattern + '$',
      handler: function(match, packet) {
        var times = parseDateTime(match, 1);
        packet.deviceTime = times.dev;
        packet.time = times.utc;
      }
    },
    numEntries: {
      pattern: '^(?:[0-9]{3}|Log Empty)$',
      handler: function(match, packet) {
        packet.numEntries = parseInt(match[0], 10);
        if (_.isNaN(packet.numEntries)) {
          packet.numEntries = 0;
        }
      }
    },
    notFound: {
      pattern: '^Log Not Found$',
      handler: function(match, packet) {
        packet.notFound = true;
      }
    },
    end: {
      pattern: '^END$',
      handler: function(match, packet) {
        packet.end = true;
      }
    },
    ok: {
      pattern: '^CMD OK$',
      handler: function(match, packet) {
        packet.ok = true;
      }
    },
    fail: {
      pattern: '^CMD Fail!$',
      handler: function(match, packet) {
        packet.fail = true;
      }
    },
    logEntry: {
      pattern: '^\s*([0-9HL][0-9OI][0-9 ])  ' + dateTimePattern + ' [0-9A-Fa-f][0-9A-Fa-f] (0x[0-9A-Fa-f][0-9A-Fa-f])',
      handler: function(match, packet) {
        var entry = {};
        var reading = match[1];

        var testType = match[3];

        if(testType[3] === '1') {
          debug('Marking as control test');
          entry.control = true;
        }else{
          entry.control = false;
        }

        //the HI/LO glucose range for all FreeStyle meters are 20-500 mg/dL according to spec
        if (reading == 'HI ') {
            entry.reading = 501;
            entry.annotations = [{
              code: 'bg/out-of-range',
              value: 'high',
              threshold: 500
            }];
        } else if (reading == 'LO ') {
          entry.reading = 19;
          entry.annotations = [{
            code: 'bg/out-of-range',
            value: 'low',
            threshold: 20
          }];
        } else {
          entry.reading = parseInt(reading, 10);
        }

        entry.datetime = parseDateTime(match, 2);
        packet.logEntries.push(entry);
      }
    },
    checksum: {
      pattern: '^(0x[0-9A-F]{4})  END',
      handler: function(match, packet) {
        packet.receivedChecksum = parseInt(match[1], 16);
      }
    }
  };
  var logEntry = 'logEntry';

  var extractPacket = function (bytes) {
    var packet = {
      bytes: bytes,
      valid: false,
      packet_len: 0,
      lcb: 0,
      lines: [],
      logEntries: []
    };

    var startIndex = 0;
    var endIndex = _.indexOf(bytes, ETX, startIndex + 1);
    if (endIndex === -1) {

      return packet;
    }

    function stringFromBytes(bytes, start, end) {
      var s = '';
      for (var i=start; i<end; ++i) {
        s += String.fromCharCode(bytes[i]);
      }
      return s;
    }

    function searchForSequence(bytes, sequence) {
      var found = false;
      for (var ix=0; ix<bytes.length; ++ix) {
        found = true;
        for (var jx=0; jx<sequence.length; ++jx) {
          if (bytes[ix + jx] != sequence[jx]) {
            found = false;
            break;
          }
        }
        if (found) {
          return ix;
        }
      }
      return -1;
    }

    var checksumIndex = searchForSequence(bytes, [0x0D, 0x0A, 0x30, 0x78]);
    checksumIndex += 2;
    if (checksumIndex !== -1) {
      var sum = 0;
      for (var i=startIndex; i<checksumIndex; ++i) {
        sum += bytes[i];
      }
      packet.calculatedChecksum = sum & 0xFFFF;
    }

    packet.packet_len = endIndex;

    var startLine = startIndex;
    while (startLine < endIndex) {
      var endLine = _.indexOf(bytes, CR, startLine);
      if (endLine === -1) {
        endLine = endIndex - 1;
      }
      if (bytes[endLine + 1] != LF) {
        packet.packet_len = endLine;
        return packet;
      }

      var line = stringFromBytes(bytes, startLine, endLine);
      packet.lines.push(line);
      startLine = endLine + 2;
    }

    var patkeys = _.keys(patterns);
    _.remove(patkeys, function(k) { return k === logEntry; });
    patkeys.unshift(logEntry);

    _.forEach(packet.lines, function(line, index) {
      var found = false;
      _.forEach(patkeys, function(key) {
        var m = line.trim().match(patterns[key].pattern);
        if (m) {
          found = true;
          patterns[key].handler(m, packet);
        }
      });
      if (!found && line !== '') {
        debug('No pattern match found for "' + line + '"');
      }
    });

    packet.valid = true;
    if (packet.receivedChecksum && packet.receivedChecksum != packet.calculatedChecksum) {
      debug('Failed checksum! rcv: ', packet.receivedChecksum, ' calc: ', packet.calculatedChecksum);
      packet.valid = false;
    }
    if (packet.numEntries && packet.logEntries.length !== packet.numEntries) {
      debug('Entry count failure! num: ', packet.numEntries, ' found: ', packet.logEntries.length);
      packet.valid = false;
    }
    if (packet.fail) {
      packet.valid = false;
    }
    return packet;
  };

  var freeStyleErrorHandler = function(info) {
    if (info.connectionId && info.error) {
      if (info.error == 'timeout') {
        return [ ETX ];
      }
    }
  };

  var freeStyleMessageHandler = function (buffer) {
    var discardCount = 0;
    while (buffer.len() > discardCount && buffer.get(0) == ETX) {
      ++discardCount;
    }

    if (discardCount) {
      buffer.discard(discardCount);
    }

    if (buffer.len() < 11) {
      return false;
    }

    var packet = extractPacket(buffer.bytes());
    if (packet.packet_len !== 0) {
      buffer.discard(packet.packet_len);
    }

    if (packet.valid) {
      return packet;
    } else {
      return null;
    }
  };

  var listenForPacket = function (timeout, parser, callback) {
    var abortTimer = setTimeout(function () {
      clearInterval(listenTimer);
      debug('TIMEOUT');
      callback('TIMEOUT', null);
    }, timeout);

    var listenTimer = setInterval(function () {
      if (serialDevice.hasAvailablePacket()) {
        var pkt = serialDevice.nextPacket();
        clearTimeout(abortTimer);
        clearInterval(listenTimer);
        pkt.parsed_payload = parser(pkt);
        callback(null, pkt);
      }
    }, 20);
  };

  var freeStyleCommandResponse = function (commandpacket, callback) {
    serialDevice.writeSerial(commandpacket.packet, function (err) {
      if (err) {
        callback(err, null);
      }
      listenForPacket(3000, commandpacket.parser, function(err, result) {
        if (err) {
          callback(err, null);
        } else {
          callback(err, result);
        }
      });
    });
  };

  var xtest = function() {
    return {
      packet: struct.packString('mem'),
      parser: function (result) {
        return result;
      }
    };
  };

  var xGetData = function() {
    return {
      packet: struct.packString('mem'),
      parser: function (result) {
        return result;
      }
    };
  };

  var getSerialNumber = function (obj, cb) {
    var cmd = xtest();
    freeStyleCommandResponse(cmd, function (err, result) {
      if (err) {
        debug('Failure trying to talk to device.');
        debug(err);
        debug(result);
        cb(err, null);
      } else {
        _.assign(obj, _.pick(result, 'serialNumber', 'softwareVersion', 'deviceTime'));
        obj.model = 'AbbottFreeStyleLite';
        obj.id = obj.model + ' ' + obj.serialNumber;
        cb(null, obj);
      }
    });
  };

  var getAllData = function (obj, cb) {
    var cmd = xGetData();
    freeStyleCommandResponse(cmd, function (err, result) {
      if (err) {
        debug('Failure trying to talk to device.');
        debug(err);
        debug(result);
        cb(err, null);
      } else {
        _.assign(obj, result);
        cb(null, obj);
      }
    });
  };

  var prepBGData = function (progress, data) {
    cfg.builder.setDefaults({ deviceId: data.id });
    var dataToPost = [];
    for (var i = 0; i < data.logEntries.length; ++i) {
      var datum = data.logEntries[i];

        if(datum.control === true) {
          debug('Discarding control');
          continue;
        }

        var smbg = cfg.builder.makeSMBG()
          .with_value(datum.reading)
          .with_deviceTime(datum.datetime.dev)
          .with_timezoneOffset(datum.datetime.timezoneOffset)
          .with_conversionOffset(datum.datetime.conversionOffset)
          .with_time(datum.datetime.utc)
          .with_units('mg/dL')
          .set('index', i)
          .done();
        if (datum.annotations) {
          _.each(datum.annotations, function(ann) {
            annotate.annotateEvent(smbg, ann);
          });
        }
        dataToPost.push(smbg);
    }
    return dataToPost;
  };

  var probe = function (cb) {
    var cmd = xtest();
    freeStyleCommandResponse(cmd, function (err, result) {
      if (err) {
        debug('Failure trying to talk to device.');
        debug(err);
        debug(result);
      }
      cb(err, result);
    });
  };

  return {
    // using the default detect for this driver
    // detect: function(cb) {
    // },

    // this function starts the chain, so it has to create but not accept
    // the result (data) object; it's then passed down the rest of the chain
    setup: function (deviceInfo, progress, cb) {
      debug('in setup!');
      progress(100);
      cb(null, {deviceInfo: deviceInfo});
    },

    connect: function (progress, data, cb) {
      debug('in connect!');
      var handlers = {
        packetHandler: freeStyleMessageHandler,
        errorHandler: freeStyleErrorHandler
      };

      cfg.deviceComms.connect(data.deviceInfo, handlers, probe, function(err) {
        if (err) {
          return cb(err);
        }
        getSerialNumber({}, function (err, result) {
          progress(100);
          data.connect = true;
          _.assign(data, result);
          debug(data);
          cb(null, data);
        });
      });
    },

    getConfigInfo: function (progress, data, cb) {
      progress(100);
      cb(null, data);
    },

    fetchData: function (progress, data, cb) {
      debug('in fetchData');
      getAllData({}, function (err, result) {
        progress(100);
        data.connect = true;
        debug(result);
        data.logEntries = result.logEntries;
        data.numEntries = result.numEntries;
        cb(null, data);
      });
    },

    processData: function (progress, data, cb) {
      progress(0);
      data.post_records = _.map(prepBGData(progress, data), function(d) {
        delete d.index;
        return d;
      });
      progress(100);
      data.processData = true;
      debug(data);
      cb(null, data);
    },

    uploadData: function (progress, data, cb) {
      progress(0);
      var sessionInfo = {
        deviceTags: ['bgm'],
        deviceManufacturers: ['Abbott'],
        deviceModel: 'FreeStyle Lite',
        deviceSerialNumber: data.serialNumber,
        deviceId: data.id,
        start: sundial.utcDateString(),
        timeProcessing: cfg.tzoUtil.type,
        tzName : cfg.timezone,
        version: cfg.version
      };

      cfg.api.upload.toPlatform(data.post_records, sessionInfo, progress, cfg.groupId, function (err, result) {
        if (err) {
          debug(err);
          progress(100);
          return cb(err, data);
        } else {
          progress(100);
          return cb(null, data);
        }
      });
    },

    disconnect: function (progress, data, cb) {
      progress(100);
      data.disconnect = true;
      cb(null, data);
    },

    cleanup: function (progress, data, cb) {
      cfg.deviceComms.clearPacketHandler();
      cfg.deviceComms.clearErrorHandler();
      cfg.deviceComms.disconnect(function() {
        progress(100);
        data.cleanup = true;
        cb(null, data);
      });
    },

    testDriver: function(config) {
      var progress = function(v) {
        debug('progress: ', v);
      };
      var data = {};
      this.connect(progress, data, function(err, result) {
        debug('result:', result);
      });
    }
  };

};
<|endoftext|>


<commit_before>define(
[
  'underscore'
],
function(_) { "use strict";

/*
* Boolean search engine.
*/
var BooleanSearchEngine = function () {

    var exTree;
    var andExpression = 'and';
    var nonePattern = 'none';
    var patterns = ['tag:', 'url:', 'title:'];
    var bookmarks = {};

    // Trims defined characters from begining and ending of the string. Defaults to whitespace characters.
    var trim = function(input, characters){
        if (!_.isString(input)) return input;

        if (!characters) characters = '\\s';

        return String(input).replace(new RegExp('^' + characters + '+|' + characters + '+$', 'g'), '');
    };

    // Checks if string is blank or not.
    var isBlank = function(str){
        if (_.isNull(str)) str = '';
        return (/^\s*$/).test(str);
    };

    // Check that tag collection contains search.
    var containsTag = function(tags, patternText){

        var tag = _.find(tags, function(item){
            return item.text.indexOf(patternText) != -1;
        });

        return !_.isUndefined(tag);
    };

    // Check that title contains search.
    var containsTitle = function(title, patternText){

        return title.indexOf(trim(patternText)) != -1;
    };

    // Check that url contains search.
    var containsUrl = function(url, patternText){

        return url.indexOf(trim(patternText)) != -1;
    };

    // Generate expression tree by search text.
    this.generateExpressionTree = function(searchText, callback){

        if(isBlank(searchText)) return exTree;

        var searchWords = searchText.split(/(tag:|title:|url:)/);
        if(_.isEmpty(searchWords))
            return exTree;

        exTree = [];
        var node = { pattern: nonePattern, literals:[] };
        var literal = { text: '', expression: nonePattern};

        _.each(searchWords, function(word){
            if(isBlank(word)) return;

            if(_.isEqual(word, andExpression)){

                literal.expression = andExpression;
                return;
            }

            var pattern = _.find(patterns, function(it){ return word.indexOf(it) != -1; });
            if(!_.isUndefined(pattern)){
                // flush node
                if(node.literals.length !== 0) exTree.push(node);

                // create a new node
                node = {
                    pattern: pattern,
                    literals:[]
                };

                return;
            }

            var exps = word.toLowerCase().split(/(and|or)/);
            _.each(exps, function(item){
                if(isBlank(word)) return;

                if(item === andExpression){
                    if(isBlank(literal.text)) return;

                    literal.expression = andExpression;
                    node.literals.push(literal);

                    literal = null;
                }
                else if(item === 'or'){
                    if(isBlank(literal.text)) return;

                    literal.expression = 'or';
                    node.literals.push(literal);

                    literal = null;
                }
                else{
                    literal = {
                        expression: nonePattern,
                        text: trim(item)
                    };
                }
            });

            if(!isBlank(literal.text)) node.literals.push(literal);
        });

        exTree.push(node);

        return exTree;
    };

    // Check that bookmark could be reached by following expression.
    var evaluateExpression = function(bookmark, node){
        if(node.pattern === nonePattern && node.literals.length === 1){
            var literal = node.literals[0];
            var filteredValue = _.find(_.values(bookmark), function(propertyValue){
                return propertyValue.toString().indexOf(trim(literal.text)) != -1;
            });
            return !_.isUndefined(filteredValue);
        }

        var evaluateFunc;
        if(node.pattern === 'tag:'){
            evaluateFunc = function(word){ return containsTag(bookmark.tag, word); };
        }
        else if(node.pattern === 'title:'){
            evaluateFunc = function(word){ return containsTitle(bookmark.title, word); };
        }
        else if(node.pattern === 'url:'){
            evaluateFunc = function(word){ return containsUrl(bookmark.url, word); };
        }

        // var failureWord = _.find(node.literals, function(literal){
        //     return evaluateFunc(literal.text);
        // });
        // return _.isUndefined(failureWord);

        if(node.literals.length === 1){
            return evaluateFunc(node.literals[0].text);
        }

        var result = true;
        var exp = andExpression;
        _.each(node.literals, function(literal){

            var literalResult = evaluateFunc(literal.text);
            if(exp === andExpression)
            {
                result = result && literalResult;
            }
            else if(exp === 'or'){
                result = result || literalResult;
            }
            exp = literal.expression;
        });

        return result;
    };

    // Check that bookmark could be reached by following search text.
    this.filterBookmark = function(bookmark, searchText){

        var search = searchText;
        if(!search) return true;

        var tree = this.generateExpressionTree(search);
        var failureNode = _.find(tree, function(node){
            return !evaluateExpression(bookmark, node);
        });

        return _.isUndefined(failureNode);
    };
};

/*
* Boolean search engine factory method.
*/
var BooleanSearchEngineFactory = function() {
    return new BooleanSearchEngine();
};

return [
  BooleanSearchEngineFactory
];

});<commit_msg>code clean up<commit_after>define(
[
  'underscore'
],
function(_) { "use strict";

/*
* Boolean search engine.
*/
var BooleanSearchEngine = function () {

    var exTree;
    var andExpression = 'and';
    var orExpression = 'or';
    var nonePattern = 'none';
    var patterns = ['tag:', 'url:', 'title:'];
    var bookmarks = {};

    // Trims defined characters from begining and ending of the string. Defaults to whitespace characters.
    var trim = function(input, characters){
        if (!_.isString(input)) return input;

        if (!characters) characters = '\\s';

        return String(input).replace(new RegExp('^' + characters + '+|' + characters + '+$', 'g'), '');
    };

    // Checks if string is blank or not.
    var isBlank = function(str){
        if (_.isNull(str)) str = '';
        return (/^\s*$/).test(str);
    };

    // Check that tag collection contains search.
    var containsTag = function(tags, patternText){

        var tag = _.find(tags, function(item){
            return item.text.indexOf(patternText) != -1;
        });

        return !_.isUndefined(tag);
    };

    // Check that title contains search.
    var containsTitle = function(title, patternText){

        return title.indexOf(trim(patternText)) != -1;
    };

    // Check that url contains search.
    var containsUrl = function(url, patternText){

        return url.indexOf(trim(patternText)) != -1;
    };

    // Generate expression tree by search text.
    this.generateExpressionTree = function(searchText, callback){

        if(isBlank(searchText)) return exTree;

        var searchWords = searchText.split(/(tag:|title:|url:)/);
        if(_.isEmpty(searchWords))
            return exTree;

        exTree = [];
        var node = { pattern: nonePattern, literals:[] };
        var literal = { text: '', expression: nonePattern};

        _.each(searchWords, function(word){
            if(isBlank(word)) return;

            if(_.isEqual(word, andExpression)){

                literal.expression = andExpression;
                return;
            }

            var pattern = _.find(patterns, function(it){ return word.indexOf(it) != -1; });
            if(!_.isUndefined(pattern)){
                // flush node
                if(node.literals.length !== 0) exTree.push(node);

                // create a new node
                node = {
                    pattern: pattern,
                    literals:[]
                };

                return;
            }

            var exps = word.toLowerCase().split(/(and|or)/);
            _.each(exps, function(item){
                if(isBlank(word)) return;

                if(item === andExpression){
                    if(isBlank(literal.text)) return;

                    literal.expression = andExpression;
                    node.literals.push(literal);

                    literal = null;
                }
                else if(item === orExpression){
                    if(isBlank(literal.text)) return;

                    literal.expression = orExpression;
                    node.literals.push(literal);

                    literal = null;
                }
                else{
                    literal = {
                        expression: nonePattern,
                        text: trim(item)
                    };
                }
            });

            if(!isBlank(literal.text)) node.literals.push(literal);
        });

        exTree.push(node);

        return exTree;
    };

    // Check that bookmark could be reached by following expression.
    var evaluateExpression = function(bookmark, node){
        if(node.pattern === nonePattern && node.literals.length === 1){
            var literal = node.literals[0];
            var filteredValue = _.find(_.values(bookmark), function(propertyValue){
                return propertyValue.toString().indexOf(trim(literal.text)) != -1;
            });
            return !_.isUndefined(filteredValue);
        }

        var evaluateFunc;
        if(node.pattern === 'tag:'){
            evaluateFunc = function(word){ return containsTag(bookmark.tag, word); };
        }
        else if(node.pattern === 'title:'){
            evaluateFunc = function(word){ return containsTitle(bookmark.title, word); };
        }
        else if(node.pattern === 'url:'){
            evaluateFunc = function(word){ return containsUrl(bookmark.url, word); };
        }

        if(node.literals.length === 1){
            return evaluateFunc(node.literals[0].text);
        }

        var result = true;
        var exp = andExpression;
        _.each(node.literals, function(literal){

            var literalResult = evaluateFunc(literal.text);
            if(exp === andExpression)
            {
                result = result && literalResult;
            }
            else if(exp === orExpression){
                result = result || literalResult;
            }
            exp = literal.expression;
        });

        return result;
    };

    // Check that bookmark could be reached by following search text.
    this.filterBookmark = function(bookmark, searchText){

        var search = searchText;
        if(!search) return true;

        var tree = this.generateExpressionTree(search);
        var failureNode = _.find(tree, function(node){
            return !evaluateExpression(bookmark, node);
        });

        return _.isUndefined(failureNode);
    };
};

/*
* Boolean search engine factory method.
*/
var BooleanSearchEngineFactory = function() {
    return new BooleanSearchEngine();
};

return [
  BooleanSearchEngineFactory
];

});<|endoftext|>


<commit_before>/*global describe:false, it:false, beforeEach:false, afterEach:false*/

'use strict';

var kraken = require('kraken-js'),
    fs = require('fs'),
    express = require('express'),
    path = require('path'),
    _ = require('lodash'),
    request = require('supertest-as-promised');

// Mock data
var nodeban = require('../../test/data/nodeban.sample.json');
describe('api/board', function () {
    var app, mock, db;
    var board = {
        "boardName": "nodeban",
        "gutters": ["To-Do", "In Progress", "Feature Working", "Integrated", "Complete"]
    };
    beforeEach(function (done) {
        // Pretty random numbers
        var rand = Math.floor(Math.random() * (99999999999 - 0) + 0);
        db = './test/temp/deleteme-api-board' + rand + '.db';
        console.info('Running tests in mock database: ' + db);
        process.env['nedb'] = db;

        app = express();
        app.on('start', done);
        app.use(kraken({
            basedir: path.resolve(__dirname, '../../')
        }));

        mock = app.listen(1337);
    });

    afterEach(function (done) {
        if (fs.existsSync(db)) {
            fs.unlink(db, function(err) {
                console.warn(err);
                mock.close();
                done();
            });
        } else {
            mock.close();
            done();
        }
    });

    it('should recieve 200 when getting root of subresource "board"', function () {
        request(mock)
            .get('/api/board')
            .expect(200);
    });

    it('should get no boards when none have been created', function() {
        request(mock)
            .get('/api/board/list')
            .expect(200)
            .expect([]);
    });

    it('should recieve a 404 when getting a specific board that does not exist', function() {
        request(mock)
            .get('/api/board/invalidBoard')
            .expect(404);
    });

    it('should be able to create a board via POST', function() {
        return request(mock)
            .post('/api/board/create')
            .send(board)
            .expect(200);
    });

    it('should list one board after its creation', function() {
        return request(mock)
            .post('/api/board/create')
            .send(board)
            .expect(200)
            .then(function() {
                return request(mock)
                    .get('/api/board/list')
                    .expect([{
                        name: board.boardName
                    }]);
            }).then(function() {
                // Need this for the createdAt date which is dynamic
                return request(mock)
                    .get('/api/board/nodeban');
            }).then(function(res) {
                nodeban.createdAt = res.body.createdAt;
                return request(mock)
                    .get('/api/board/nodeban')
                    .expect(nodeban);
        });
    });

    it('should validate board creation schema', function() {
        var invalidBoard = _.cloneDeep(board);
        invalidBoard.invalid = true;
        return request(mock)
            .post('/api/board/create')
            .send(invalidBoard)
            .expect({
            "code": 400,
            "error": "UserError",
            "message": "JSON validation error",
            "details": [
                {
                    "message": "\"invalid\" is not allowed",
                    "path": ".invalid",
                    "type": "object.allowUnknown",
                    "context": {
                        "key": "invalid"
                    }
                }
            ]
        });
    });

});
<commit_msg>Revert "changing git users test"<commit_after>/*global describe:false, it:false, beforeEach:false, afterEach:false*/

'use strict';

var kraken = require('kraken-js'),
    fs = require('fs'),
    express = require('express'),
    path = require('path'),
    _ = require('lodash'),
    request = require('supertest-as-promised');

// Mock data
var nodeban = require('../../test/data/nodeban.sample.json');
describe('api/board', function () {
    var app, mock, db;
    var board = {
        "boardName": "nodeban",
        "gutters": ["To-Do", "In Progress", "Feature Working", "Integrated", "Complete"]
    };
    beforeEach(function (done) {
        // Pretty random numbers
        var rand = Math.floor(Math.random() * (99999999999 - 0) + 0);
        db = './test/temp/deleteme-api-board' + rand + '.db';
        console.info('Running tests in mock database: ' + db);
        process.env['nedb'] = db;
        app = express();
        app.on('start', done);
        app.use(kraken({
            basedir: path.resolve(__dirname, '../../')
        }));

        mock = app.listen(1337);
    });

    afterEach(function (done) {
        if (fs.existsSync(db)) {
            fs.unlink(db, function(err) {
                console.warn(err);
                mock.close();
                done();
            });
        } else {
            mock.close();
            done();
        }
    });

    it('should recieve 200 when getting root of subresource "board"', function () {
        request(mock)
            .get('/api/board')
            .expect(200);
    });

    it('should get no boards when none have been created', function() {
        request(mock)
            .get('/api/board/list')
            .expect(200)
            .expect([]);
    });

    it('should recieve a 404 when getting a specific board that does not exist', function() {
        request(mock)
            .get('/api/board/invalidBoard')
            .expect(404);
    });

    it('should be able to create a board via POST', function() {
        return request(mock)
            .post('/api/board/create')
            .send(board)
            .expect(200);
    });

    it('should list one board after its creation', function() {
        return request(mock)
            .post('/api/board/create')
            .send(board)
            .expect(200)
            .then(function() {
                return request(mock)
                    .get('/api/board/list')
                    .expect([{
                        name: board.boardName
                    }]);
            }).then(function() {
                // Need this for the createdAt date which is dynamic
                return request(mock)
                    .get('/api/board/nodeban');
            }).then(function(res) {
                nodeban.createdAt = res.body.createdAt;
                return request(mock)
                    .get('/api/board/nodeban')
                    .expect(nodeban);
        });
    });

    it('should validate board creation schema', function() {
        var invalidBoard = _.cloneDeep(board);
        invalidBoard.invalid = true;
        return request(mock)
            .post('/api/board/create')
            .send(invalidBoard)
            .expect({
            "code": 400,
            "error": "UserError",
            "message": "JSON validation error",
            "details": [
                {
                    "message": "\"invalid\" is not allowed",
                    "path": ".invalid",
                    "type": "object.allowUnknown",
                    "context": {
                        "key": "invalid"
                    }
                }
            ]
        });
    });

});
<|endoftext|>


<commit_before>/**
 * var connection = new Connection({ tag:             'droonga',
 *                                   defaultDataset:  'example',
 *                                   hostName:        '127.0.0.1',
 *                                   port:            24224,
 *                                   receiveHostName: '127.0.0.1',
 *                                   receivePort:     10030 });
 */

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var fluent = require('fluent-logger');
var Q = require('q');
var microtime = require('microtime');

var FluentReceiver = require('./receiver').FluentReceiver;
var ConsoleLogger = require('../console-logger').ConsoleLogger;

var DEFAULT_FLUENT_TAG =
      Connection.DEFAULT_FLUENT_TAG =
        'droonga';
var DEFAULT_FLUENT_HOST_NAME =
      Connection.DEFAULT_FLUENT_HOST_NAME =
        '127.0.0.1';
var DEFAULT_FLUENT_PORT =
      Connection.DEFAULT_FLUENT_PORT =
        24224;
var DEFAULT_RECEIVE_HOST_NAME =
      Connection.DEFAULT_RECEIVE_HOST_NAME =
        '127.0.0.1';

var ERROR_SERVICE_UNAVAILABLE =
      Connection.ERROR_SERVICE_UNAVAILABLE =
        503;

var ERROR_GATEWAY_TIMEOUT =
      Connection.ERROR_GATEWAY_TIMEOUT =
        504;

var DEFAULT_RESPONSE_TIMEOUT =
      Connection.DEFAULT_RESPONSE_TIMEOUT =
        60 * 1000;

function Connection(params) {
  EventEmitter.call(this);
  this._params = params || {};
  this._logger = this._params.logger || new ConsoleLogger();
  this._init();
}

util.inherits(Connection, EventEmitter);

Connection.prototype._init = function() {
  this.closed = false;
  this._callbacks = {};
  this._pendingMessages = [];
  this._id = Date.now();
  this.tag = process.env.DROONGA_ENGINE_TAG ||
               this._params.tag ||
                 DEFAULT_FLUENT_TAG;
  this.defaultDataset = process.env.DROONGA_ENGINE_DEFAULT_DATASET ||
                          this._params.defaultDataset ||
                            '';
  this.hostName = this._params.hostName ||
                          process.env.DROONGA_ENGINE_HOST ||
                            DEFAULT_FLUENT_HOST_NAME;
  this.port = process.env.DROONGA_ENGINE_PORT ||
                          this._params.port ||
                            DEFAULT_FLUENT_PORT;
  this.hostAndPort = this.hostName + ':' + this.port;
  this._initSender();
  this._initReceiver();
};

Connection.prototype._initSender = function(wait) {
  this._sendingMessages = {};
  var options = { host: this.hostName,
                  port: this.port };
  var sender = fluent.createFluentSender(this.tag, options);
  this._sender = sender;
  this._sender.on('error', (function(error) {
    var errorMessage =
      'An error is occurred in protocol adapter: ' +
      '[' + error.name + '] ' + error.message;
    var ids = Object.keys(this._sendingMessages);
    if (ids.length == 0) {
      this._logger.error(errorMessage, error);
    } else {
      ids.forEach(function(id) {
        var sendingMessage = this._sendingMessages[id];
        var message = {
          inReplyTo: id,
          statusCode: 500,
          type: sendingMessage.type + '.result',
          body: {
            name: 'ProtocolAdapterError',
            message: errorMessage,
            detail: error
          }
        }
        this.emit('reply:' + id, message.statusCode, message);
      }.bind(this));
      this._sendingMessages = {};
    }
  }).bind(this));
};

Connection.prototype._initReceiver = function() {
  this._receiveCount = 0;
  this._sendCount = 0;
  if (!this._receiver) {
    this.receiveHostName = process.env.DROONGA_ENGINE_RECEIVE_HOST ||
                             this._params.receiveHostName ||
                               DEFAULT_RECEIVE_HOST_NAME;
    this.receivePort = process.env.DROONGA_ENGINE_RECEIVE_PORT ||
                         this._params.receivePort;
  } else {
    this._receiver.close();
  }

  var receiver = new FluentReceiver(this.receivePort,
                                    { logger: this._logger });
  receiver.listen((function() {
    this.receivePort = receiver.port;
    this._sendPendingMessages();
  }).bind(this));

  var tag = this.tag + '.message';
  this._logger.debug('Connection._initReceiver %d (%s): %d %d:',
                     this._id, this.hostAndPort,
                     receiver._id, this.receivePort, tag);
  this._receiver = receiver;
  this._receiver.on(tag,
                    this._handleMessage.bind(this));
};

function isSuccess(code) {
  return Math.floor(code / 100) == 2;
}

Connection.prototype._handleMessage = function(envelope) {
  var inReplyTo = envelope.inReplyTo;
  if (inReplyTo) {
    delete this._sendingMessages[inReplyTo];
    this._logger.debug('Connection._handleMessage.reply %d (%s):',
                       this._id, this.hostAndPort, envelope);
    var errorCode = envelope.statusCode;
    if (!errorCode || isSuccess(errorCode))
      errorCode = null;
    this.emit('reply:' + inReplyTo, errorCode, envelope);
  } else {
    this._logger.debug('Connection._handleMessage.message %d (%s):',
                       this._id, this.hostAndPort, envelope);
    this.emit(envelope.type, envelope);
  }
};

Connection.prototype.getRouteToSelf = function(options) {
  options = options || {};

  var from = this.receiveHostName + ':' + this.receivePort + '/' +
               this.tag + '?connection_id=' + this._id;
  if (options.sessionId)
    from += '&client_session_id=' + options.sessionId;

  return from;
}

var count = 0;
function createId() {
  return Date.now() + ':' + (count++);
}

function getCurrentTime() {
  // The method toISOString() returns a GMT (ex. 2013-01-10T08:34:41.252Z)
  // on node. However, a local time (ex. 2013-01-10T17:34:41.252+09:00) is
  // more helpful for debugging. We should use it in the feature...

  var nowStruct = microtime.nowStruct();
  var seconds = nowStruct[0];
  var microseconds = nowStruct[1];

  var date = new Date((seconds * 1000) + (microseconds / 1000));
  var isoString = date.toISOString();
  var subsecondsPart = ('000000' + microseconds).substr(-6);
  var isoStringWithMicroseconds = isoString.replace(/\.\d+Z$/, '.' + subsecondsPart + 'Z');
  return isoStringWithMicroseconds;
}

function toPositiveInteger(number) {
  if (!number)
    return 0;

  var integer = parseInt(number);
  if (isNaN(integer))
    return 0;

  return Math.max(integer, 0);
}

Connection.prototype.emitMessage = function(type, body, callback, options) {
  if (!this._sender)
    throw new Error('emitMessage is unexpectedly called after the connection is closed!');

  options = options || {};

  // support emitMessage(type, body, options)
  if (callback && typeof callback != 'function') {
    options = callback;
    callback = null;
  }

  if (!this.receivePort) {
    this._logger.debug('Connection.emitMessage %d (%s): ' +
                         'Receiver is not initialized yet. ' +
                         'Given message will be sent later.',
                       this._id, this.hostAndPort);
    this._pendingMessages.push([type, body, callback, options]);
    return null;
  }

  var id = createId();
  this._logger.debug('Connection.emitMessage %d (%s):',
                     this._id, this.hostAndPort);
  var from = this.getRouteToSelf(options);
  var envelope = {
    id:         id,
    from:       from,
    date:       getCurrentTime(),
    dataset:    options.dataset || this.defaultDataset,
    type:       type,
    body:       body
  };
  if (callback) {
    envelope.replyTo = from;

    var event = 'reply:' + id;
    var timeoutId;
    this.once(event, (function(errorCode, response) {
      this._logger.debug('Connection.emitMessage.reply %d (%s):',
                         this._id, this.hostAndPort, errorCode);
      clearTimeout(timeoutId);
      callback(errorCode, response);
    }).bind(this));
    options.timeout = toPositiveInteger(options.timeout) ||
                        DEFAULT_RESPONSE_TIMEOUT;
    timeoutId = setTimeout((function() {
      this.removeAllListeners(event);
      callback(ERROR_GATEWAY_TIMEOUT, null);
    }).bind(this), options.timeout);
  }
  this._sendingMessages[id] = {
    type: type,
    callback: callback
  };
  this._sender.emit('message', envelope, options.emittedCallback);
  return envelope;
};

Connection.prototype.thenableEmitMessage = function(type, body, options) {
  return Q.Promise((function(resolve, reject, notify) {
    this.emitMessage(type, body, function(errorCode, response) {
      resolve({ errorCode: errorCode, response: response });
    }, options);
  }).bind(this));
};

Connection.prototype._sendPendingMessages = function() {
  this._logger.debug('Connection._sendPendingMessages %d (%s): ' +
                       'Send %d pending message(s).',
                     this._id, this.hostAndPort,
                     this._pendingMessages.length);
  if (this._pendingMessages.length) {
    this._pendingMessages.forEach(function(args) {
      this.emitMessage.apply(this, args);
    }, this);
    this._pendingMessages = [];
  }
};

Connection.prototype.close = function() {
  this.removeAllListeners();

  Object.keys(this._sendingMessages).forEach(function(id) {
    var message = this._sendingMessages[id];
    var callback = message.callback;
    if (typeof callback == 'function')
      callback(ERROR_SERVICE_UNAVAILABLE, null);
  }, this);
  this._sendingMessages = {};

  if (this._sender && typeof this._sender.end == 'function') {
    try {
      this._sender.end();
      this._sender.removeAllListeners();
    }
    catch(error) {
    }
    delete this._sender;
  }

  if (this._receiver && typeof this._receiver.close == 'function') {
    try {
      this._receiver.close();
      this._receiver.removeAllListeners();
    }
    catch(error) {
    }
    delete this._receiver;
  }
  this.closed = true;
};

Connection.prototype.getStatus = function() {
  var status = {};
  status.pendingMessages = this._pendingMessages.map(function(pendingMessage) {
    return {
      type:    pendingMessage[0],
      body:    pendingMessage[1],
      options: pendingMessage[3]
    };
  });
  status.sendingMessages = {};
  Object.keys(this._sendingMessages).forEach(function(id) {
    status.sendingMessages[id] = this._sendingMessages[id];
  }, this);
  return status;
};

exports.Connection = Connection;
<commit_msg>Remove unused property<commit_after>/**
 * var connection = new Connection({ tag:             'droonga',
 *                                   defaultDataset:  'example',
 *                                   hostName:        '127.0.0.1',
 *                                   port:            24224,
 *                                   receiveHostName: '127.0.0.1',
 *                                   receivePort:     10030 });
 */

var util = require('util');
var EventEmitter = require('events').EventEmitter;
var fluent = require('fluent-logger');
var Q = require('q');
var microtime = require('microtime');

var FluentReceiver = require('./receiver').FluentReceiver;
var ConsoleLogger = require('../console-logger').ConsoleLogger;

var DEFAULT_FLUENT_TAG =
      Connection.DEFAULT_FLUENT_TAG =
        'droonga';
var DEFAULT_FLUENT_HOST_NAME =
      Connection.DEFAULT_FLUENT_HOST_NAME =
        '127.0.0.1';
var DEFAULT_FLUENT_PORT =
      Connection.DEFAULT_FLUENT_PORT =
        24224;
var DEFAULT_RECEIVE_HOST_NAME =
      Connection.DEFAULT_RECEIVE_HOST_NAME =
        '127.0.0.1';

var ERROR_SERVICE_UNAVAILABLE =
      Connection.ERROR_SERVICE_UNAVAILABLE =
        503;

var ERROR_GATEWAY_TIMEOUT =
      Connection.ERROR_GATEWAY_TIMEOUT =
        504;

var DEFAULT_RESPONSE_TIMEOUT =
      Connection.DEFAULT_RESPONSE_TIMEOUT =
        60 * 1000;

function Connection(params) {
  EventEmitter.call(this);
  this._params = params || {};
  this._logger = this._params.logger || new ConsoleLogger();
  this._init();
}

util.inherits(Connection, EventEmitter);

Connection.prototype._init = function() {
  this.closed = false;
  this._pendingMessages = [];
  this._id = Date.now();
  this.tag = process.env.DROONGA_ENGINE_TAG ||
               this._params.tag ||
                 DEFAULT_FLUENT_TAG;
  this.defaultDataset = process.env.DROONGA_ENGINE_DEFAULT_DATASET ||
                          this._params.defaultDataset ||
                            '';
  this.hostName = this._params.hostName ||
                          process.env.DROONGA_ENGINE_HOST ||
                            DEFAULT_FLUENT_HOST_NAME;
  this.port = process.env.DROONGA_ENGINE_PORT ||
                          this._params.port ||
                            DEFAULT_FLUENT_PORT;
  this.hostAndPort = this.hostName + ':' + this.port;
  this._initSender();
  this._initReceiver();
};

Connection.prototype._initSender = function(wait) {
  this._sendingMessages = {};
  var options = { host: this.hostName,
                  port: this.port };
  var sender = fluent.createFluentSender(this.tag, options);
  this._sender = sender;
  this._sender.on('error', (function(error) {
    var errorMessage =
      'An error is occurred in protocol adapter: ' +
      '[' + error.name + '] ' + error.message;
    var ids = Object.keys(this._sendingMessages);
    if (ids.length == 0) {
      this._logger.error(errorMessage, error);
    } else {
      ids.forEach(function(id) {
        var sendingMessage = this._sendingMessages[id];
        var message = {
          inReplyTo: id,
          statusCode: 500,
          type: sendingMessage.type + '.result',
          body: {
            name: 'ProtocolAdapterError',
            message: errorMessage,
            detail: error
          }
        }
        this.emit('reply:' + id, message.statusCode, message);
      }.bind(this));
      this._sendingMessages = {};
    }
  }).bind(this));
};

Connection.prototype._initReceiver = function() {
  this._receiveCount = 0;
  this._sendCount = 0;
  if (!this._receiver) {
    this.receiveHostName = process.env.DROONGA_ENGINE_RECEIVE_HOST ||
                             this._params.receiveHostName ||
                               DEFAULT_RECEIVE_HOST_NAME;
    this.receivePort = process.env.DROONGA_ENGINE_RECEIVE_PORT ||
                         this._params.receivePort;
  } else {
    this._receiver.close();
  }

  var receiver = new FluentReceiver(this.receivePort,
                                    { logger: this._logger });
  receiver.listen((function() {
    this.receivePort = receiver.port;
    this._sendPendingMessages();
  }).bind(this));

  var tag = this.tag + '.message';
  this._logger.debug('Connection._initReceiver %d (%s): %d %d:',
                     this._id, this.hostAndPort,
                     receiver._id, this.receivePort, tag);
  this._receiver = receiver;
  this._receiver.on(tag,
                    this._handleMessage.bind(this));
};

function isSuccess(code) {
  return Math.floor(code / 100) == 2;
}

Connection.prototype._handleMessage = function(envelope) {
  var inReplyTo = envelope.inReplyTo;
  if (inReplyTo) {
    delete this._sendingMessages[inReplyTo];
    this._logger.debug('Connection._handleMessage.reply %d (%s):',
                       this._id, this.hostAndPort, envelope);
    var errorCode = envelope.statusCode;
    if (!errorCode || isSuccess(errorCode))
      errorCode = null;
    this.emit('reply:' + inReplyTo, errorCode, envelope);
  } else {
    this._logger.debug('Connection._handleMessage.message %d (%s):',
                       this._id, this.hostAndPort, envelope);
    this.emit(envelope.type, envelope);
  }
};

Connection.prototype.getRouteToSelf = function(options) {
  options = options || {};

  var from = this.receiveHostName + ':' + this.receivePort + '/' +
               this.tag + '?connection_id=' + this._id;
  if (options.sessionId)
    from += '&client_session_id=' + options.sessionId;

  return from;
}

var count = 0;
function createId() {
  return Date.now() + ':' + (count++);
}

function getCurrentTime() {
  // The method toISOString() returns a GMT (ex. 2013-01-10T08:34:41.252Z)
  // on node. However, a local time (ex. 2013-01-10T17:34:41.252+09:00) is
  // more helpful for debugging. We should use it in the feature...

  var nowStruct = microtime.nowStruct();
  var seconds = nowStruct[0];
  var microseconds = nowStruct[1];

  var date = new Date((seconds * 1000) + (microseconds / 1000));
  var isoString = date.toISOString();
  var subsecondsPart = ('000000' + microseconds).substr(-6);
  var isoStringWithMicroseconds = isoString.replace(/\.\d+Z$/, '.' + subsecondsPart + 'Z');
  return isoStringWithMicroseconds;
}

function toPositiveInteger(number) {
  if (!number)
    return 0;

  var integer = parseInt(number);
  if (isNaN(integer))
    return 0;

  return Math.max(integer, 0);
}

Connection.prototype.emitMessage = function(type, body, callback, options) {
  if (!this._sender)
    throw new Error('emitMessage is unexpectedly called after the connection is closed!');

  options = options || {};

  // support emitMessage(type, body, options)
  if (callback && typeof callback != 'function') {
    options = callback;
    callback = null;
  }

  if (!this.receivePort) {
    this._logger.debug('Connection.emitMessage %d (%s): ' +
                         'Receiver is not initialized yet. ' +
                         'Given message will be sent later.',
                       this._id, this.hostAndPort);
    this._pendingMessages.push([type, body, callback, options]);
    return null;
  }

  var id = createId();
  this._logger.debug('Connection.emitMessage %d (%s):',
                     this._id, this.hostAndPort);
  var from = this.getRouteToSelf(options);
  var envelope = {
    id:         id,
    from:       from,
    date:       getCurrentTime(),
    dataset:    options.dataset || this.defaultDataset,
    type:       type,
    body:       body
  };
  if (callback) {
    envelope.replyTo = from;

    var event = 'reply:' + id;
    var timeoutId;
    this.once(event, (function(errorCode, response) {
      this._logger.debug('Connection.emitMessage.reply %d (%s):',
                         this._id, this.hostAndPort, errorCode);
      clearTimeout(timeoutId);
      callback(errorCode, response);
    }).bind(this));
    options.timeout = toPositiveInteger(options.timeout) ||
                        DEFAULT_RESPONSE_TIMEOUT;
    timeoutId = setTimeout((function() {
      this.removeAllListeners(event);
      callback(ERROR_GATEWAY_TIMEOUT, null);
    }).bind(this), options.timeout);
  }
  this._sendingMessages[id] = {
    type: type,
    callback: callback
  };
  this._sender.emit('message', envelope, options.emittedCallback);
  return envelope;
};

Connection.prototype.thenableEmitMessage = function(type, body, options) {
  return Q.Promise((function(resolve, reject, notify) {
    this.emitMessage(type, body, function(errorCode, response) {
      resolve({ errorCode: errorCode, response: response });
    }, options);
  }).bind(this));
};

Connection.prototype._sendPendingMessages = function() {
  this._logger.debug('Connection._sendPendingMessages %d (%s): ' +
                       'Send %d pending message(s).',
                     this._id, this.hostAndPort,
                     this._pendingMessages.length);
  if (this._pendingMessages.length) {
    this._pendingMessages.forEach(function(args) {
      this.emitMessage.apply(this, args);
    }, this);
    this._pendingMessages = [];
  }
};

Connection.prototype.close = function() {
  this.removeAllListeners();

  Object.keys(this._sendingMessages).forEach(function(id) {
    var message = this._sendingMessages[id];
    var callback = message.callback;
    if (typeof callback == 'function')
      callback(ERROR_SERVICE_UNAVAILABLE, null);
  }, this);
  this._sendingMessages = {};

  if (this._sender && typeof this._sender.end == 'function') {
    try {
      this._sender.end();
      this._sender.removeAllListeners();
    }
    catch(error) {
    }
    delete this._sender;
  }

  if (this._receiver && typeof this._receiver.close == 'function') {
    try {
      this._receiver.close();
      this._receiver.removeAllListeners();
    }
    catch(error) {
    }
    delete this._receiver;
  }
  this.closed = true;
};

Connection.prototype.getStatus = function() {
  var status = {};
  status.pendingMessages = this._pendingMessages.map(function(pendingMessage) {
    return {
      type:    pendingMessage[0],
      body:    pendingMessage[1],
      options: pendingMessage[3]
    };
  });
  status.sendingMessages = {};
  Object.keys(this._sendingMessages).forEach(function(id) {
    status.sendingMessages[id] = this._sendingMessages[id];
  }, this);
  return status;
};

exports.Connection = Connection;
<|endoftext|>