<commit_before>'use strict';
var chumblRequest=require("./chumbl-request.js");
var memoryCache=require("memory-cache");
var _memoryCacheTimeout=60000;

var lookup = {
    getLookups: function (options, callback) {
        var cacheId="lookups_" + options.chumblr + (options.query ? "_" + options.query : "");
        var cacheItem=memoryCache.get(cacheId);
        if (cacheItem){
            return callback(null,cacheItem,{cache:true,status:200});
        }

        options.verb = "get";
        var url = "/{chumblr}/lookups";
        if (options.query) {
            url += "?" + options.query;
        }
        options.url = url;
        options.errorIfNoResult=true;

        function requested(err,result,additionalInfo,optLocation){
            if(!err){
                memoryCache.put(cacheId,result,_memoryCacheTimeout);
            }

            return callback(err,result,additionalInfo,optLocation);
        }
        chumblRequest(options, requested);
    },
    getLookup: function (options, callback) {
        var cacheId="lookups_" + options.chumblr + "_" + (options.lookupName?options.lookupName:options.lookupId);
        var cacheItem=memoryCache.get(cacheId);
        if (cacheItem){
            return callback(null,cacheItem,{cache:true,status:200});
        }

        if (options.lookupName) {
            options.url = "/{chumblr}/lookups/{lookupName}";
        } else {
            options.url = "/{chumblr}/lookups/{lookupId}";
        }
        options.verb = "get";
        options.errorIfNoResult=true;

        function requested(err,result,additionalInfo,optLocation){
            if(!err){
                memoryCache.put(cacheId,result,_memoryCacheTimeout);
            }

            return callback(err,result,additionalInfo,optLocation);
        }

        chumblRequest(options, requested);
    },
    getLookupValues: function (options, callback) {
        var cacheId="lookupvalues_" + options.chumblr + "_" +  options.lookupId;
        var cacheItem=memoryCache.get(cacheId);
        if (cacheItem){
            return callback(null,cacheItem,{cache:true,status:200});
        }

        options.verb = "get";
        options.url = "/{chumblr}/lookups/{lookupId}/values";
        options.errorIfNoResult=true;

        function requested(err,result,additionalInfo,optLocation){
            if(!err){
                memoryCache.put(cacheId,result,_memoryCacheTimeout);
            }

            return callback(err,result,additionalInfo,optLocation);
        }

        chumblRequest(options, requested);

    },
    getLookupSchema: function (options, callback) {
        options.verb = "get";
        options.url = "/{chumblr}/lookups/{lookupId}/schema";
        chumblRequest(options, callback);
    },
    vLookup: function (options, callback) {
        var lookupId = null;
        if (options.lookupName) {
            lookupId = options.lookupName;
        } else {
            lookupId = options.lookupId;
        }

        function lookupRetrieved(err, lookupTable) {
            if (err) {
                return callback(err);
            }
            if (!lookupTable) {
                return callback("lookup table unavailable for vlookup:" + lookupId);
            }

            function lookupValuesRetrieved(err, lookupValues) {
                if (err) {
                    return callback(err);
                }
                if (!lookupValues) {
                    return callback("lookup table unavailable for vlookup:" + lookupId);
                }
                var foundValue = null;
                for (var i = 0; i < lookupValues.length; i++) {
                    if (lookupValues[i][options.lookupColumn] === options.value) {
                        if (lookupValues[i][options.returnColumn] || lookupValues[i][options.returnColumn] === 0) {
                            foundValue = lookupValues[i][options.returnColumn];
                        }
                        break;
                    }
                }
                return callback(null, foundValue);
            }

            options.lookupId = lookupTable._id.toString();
            lookup.getLookupValues(options, lookupValuesRetrieved);
        }

        lookup.getLookup(options, lookupRetrieved);
    },

    addLookup: function (options, callback) {
        options.verb = "post";
        options.url = "/{chumblr}/lookups";
        options.data = options.lookup;
        options.errorIfNoResult=true;
        chumblRequest(options, callback);
    },

    updateLookup: function (options, callback) {
        options.verb = "put";
        options.url = "/{chumblr}/lookups/{lookupId}";
        options.data = options.lookup;
        chumblRequest(options, callback);
    },

    setLookupValues: function (options, callback) {
        options.verb = "put";
        options.url = "/{chumblr}/lookups/{lookupId}/values?noCache=true";
        options.data = options.lookupValues;
        chumblRequest(options, callback);
    }
};

module.exports = lookup;<commit_msg>formatting page before making any changes.<commit_after>'use strict';
var chumblRequest = require("./chumbl-request.js");
var memoryCache = require("memory-cache");
var _memoryCacheTimeout = 60000;

var lookup = {
    getLookups: function (options, callback) {
        var cacheId = "lookups_" + options.chumblr + (options.query ? "_" + options.query : "");
        var cacheItem = memoryCache.get(cacheId);
        if (cacheItem) {
            return callback(null, cacheItem, {cache: true, status: 200});
        }

        options.verb = "get";
        var url = "/{chumblr}/lookups";
        if (options.query) {
            url += "?" + options.query;
        }
        options.url = url;
        options.errorIfNoResult = true;

        function requested(err, result, additionalInfo, optLocation) {
            if (!err) {
                memoryCache.put(cacheId, result, _memoryCacheTimeout);
            }

            return callback(err, result, additionalInfo, optLocation);
        }

        chumblRequest(options, requested);
    },
    getLookup: function (options, callback) {
        var cacheId = "lookups_" + options.chumblr + "_" + (options.lookupName ? options.lookupName : options.lookupId);
        var cacheItem = memoryCache.get(cacheId);
        if (cacheItem) {
            return callback(null, cacheItem, {cache: true, status: 200});
        }

        if (options.lookupName) {
            options.url = "/{chumblr}/lookups/{lookupName}";
        } else {
            options.url = "/{chumblr}/lookups/{lookupId}";
        }
        options.verb = "get";
        options.errorIfNoResult = true;

        function requested(err, result, additionalInfo, optLocation) {
            if (!err) {
                memoryCache.put(cacheId, result, _memoryCacheTimeout);
            }

            return callback(err, result, additionalInfo, optLocation);
        }

        chumblRequest(options, requested);
    },
    getLookupValues: function (options, callback) {
        var cacheId = "lookupvalues_" + options.chumblr + "_" + options.lookupId;
        var cacheItem = memoryCache.get(cacheId);
        if (cacheItem) {
            return callback(null, cacheItem, {cache: true, status: 200});
        }

        options.verb = "get";
        options.url = "/{chumblr}/lookups/{lookupId}/values";
        options.errorIfNoResult = true;

        function requested(err, result, additionalInfo, optLocation) {
            if (!err) {
                memoryCache.put(cacheId, result, _memoryCacheTimeout);
            }

            return callback(err, result, additionalInfo, optLocation);
        }

        chumblRequest(options, requested);

    },
    getLookupSchema: function (options, callback) {
        options.verb = "get";
        options.url = "/{chumblr}/lookups/{lookupId}/schema";
        chumblRequest(options, callback);
    },
    vLookup: function (options, callback) {
        var lookupId = null;
        if (options.lookupName) {
            lookupId = options.lookupName;
        } else {
            lookupId = options.lookupId;
        }

        function lookupRetrieved(err, lookupTable) {
            if (err) {
                return callback(err);
            }
            if (!lookupTable) {
                return callback("lookup table unavailable for vlookup:" + lookupId);
            }

            function lookupValuesRetrieved(err, lookupValues) {
                if (err) {
                    return callback(err);
                }
                if (!lookupValues) {
                    return callback("lookup table unavailable for vlookup:" + lookupId);
                }
                var foundValue = null;
                for (var i = 0; i < lookupValues.length; i++) {
                    if (lookupValues[i][options.lookupColumn] === options.value) {
                        if (lookupValues[i][options.returnColumn] || lookupValues[i][options.returnColumn] === 0) {
                            foundValue = lookupValues[i][options.returnColumn];
                        }
                        break;
                    }
                }
                return callback(null, foundValue);
            }

            options.lookupId = lookupTable._id.toString();
            lookup.getLookupValues(options, lookupValuesRetrieved);
        }

        lookup.getLookup(options, lookupRetrieved);
    },

    addLookup: function (options, callback) {
        options.verb = "post";
        options.url = "/{chumblr}/lookups";
        options.data = options.lookup;
        options.errorIfNoResult = true;
        chumblRequest(options, callback);
    },

    updateLookup: function (options, callback) {
        options.verb = "put";
        options.url = "/{chumblr}/lookups/{lookupId}";
        options.data = options.lookup;
        chumblRequest(options, callback);
    },

    setLookupValues: function (options, callback) {
        options.verb = "put";
        options.url = "/{chumblr}/lookups/{lookupId}/values?noCache=true";
        options.data = options.lookupValues;
        chumblRequest(options, callback);
    }
};

module.exports = lookup;<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Start core database.  Note that, unlike most of the
 * rest of the files in the core/ directory, this is hand-written.
 *
 * It assumed this file will be used when starting a full database
 * dump; for that, a file in database/, generated by dump, will
 * restart listeners intead.
 */

// Configuration.
$.servers.http.subdomains = false;
$.servers.http.protocol = 'http:';
$.servers.http.host = 'localhost:8080';

// Set up.
$.system.onStartup();

// Tidy up.
$.tutorial.moveTo($.startRoom);
$.tutorial.reset();

$.pot.moveTo($.startRoom);
$.pot.stage = 0;
$.seed.moveTo($.startRoom);
$.thrower.moveTo($.startRoom);

$.cage.moveTo($.physicals['Genetics Lab']);

$.assistant.moveTo($.startRoom);

<commit_msg>Make sure $.clock is in $.startRoom.contents_<commit_after>/**
 * @license
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Start core database.  Note that, unlike most of the
 * rest of the files in the core/ directory, this is hand-written.
 *
 * It assumed this file will be used when starting a full database
 * dump; for that, a file in database/, generated by dump, will
 * restart listeners intead.
 */

// Configuration.
$.servers.http.subdomains = false;
$.servers.http.protocol = 'http:';
$.servers.http.host = 'localhost:8080';

// Set up.
$.system.onStartup();

// Tidy up.
$.clock.movable = true;
$.clock.moveTo($.startRoom);
$.clock.movable = false;

$.tutorial.moveTo($.startRoom);
$.tutorial.reset();

$.pot.moveTo($.startRoom);
$.pot.stage = 0;
$.seed.moveTo($.startRoom);
$.thrower.moveTo($.startRoom);

$.cage.moveTo($.physicals['Genetics Lab']);

$.assistant.moveTo($.startRoom);

<|endoftext|>


<commit_before>var _ = require('underscore'),
    combine = require('./jsCombine'),
    config = require('./config'),
    fs = require('fs'),
    fu = require('./fileUtil'),
    plugin = require('./plugin'),
    watch = require('./watcher.js'),
    growl = require('growl');

function logError(err) {
    // TODO : Move this to the executable
    console.error(err.stack || err.message);
    growl.notify(err.message, { title: 'Lumbar error' });
}

var configCache;
function loadConfig(path) {
  try {
    configCache = {};
    config.load(path);
  } catch (err) {
    // Do not die a horrible death if this is just a syntax error
    logError(err);
  }
}

exports.plugin = plugin.plugin;
exports.init = function(configFile, options) {
    var outdir = options.outdir,
        minimize = options.minimize,
        plugins = plugin.create(options);

    function initPlatforms() {
        config.platformList().forEach(function(platform) {
            if (!platform) {
                return;
            }
            try {
                console.log("mkdir:\t\033[90mmaking directory\033[0m " + outdir + '/' + platform);
                fs.mkdirSync(outdir + '/' + platform, 0755);
            } catch (err) {
                if (err.code !== 'EEXIST') {
                    throw err;
                }
            }
        });
    }

    function buildPlatform(context, callback) {
      context.combined = config.combineModules(context.package);

      var modules = config.moduleList(context.package);
      modules.forEach(function(module) {
        context = _.clone(context);
        context.module = module;
        buildModule(context, callback)
      });

      if (context.combined) {
        combineResources(context, context.package, function(err, data) {
          if (data) {
            data.package = context.package;
          }
          callback(err, data);
        });
      }
    }
    function buildModule(context, callback) {
      context.platformPath = (context.platform ? context.platform + '/' : '');
      context.module = config.module(context.module);
      context.options = options;
      context.config = config;
      context.configCache = configCache;
      context.fileCache = context.fileCache || {};
      context.moduleCache = {};

      var fileList = config.fileList(plugins, context);

      var resources = fileList.map(function(resource) {
        context.resource = resource;
        return plugins.resource(context);
      }).filter(function(resource) { return resource; });

      context.moduleResources = resources;
      plugins.module(context);

      if (!context.combined) {
        context.resources = context.moduleResources;
        combineResources(context, context.module.name, function(err, data) {
          if (data) {
            data.package = context.package;
            data.module = context.module.name;
          }
          callback(err, data);
        });
      } else {
        context.resources = context.resources || [];
        context.resources.push.apply(context.resources, context.moduleResources);
      }
    }
    function combineResources(context, name, callback) {
      var fileName = outdir + '/' + context.platformPath + name + '.js';

      context.fileName = fileName;
      plugins.file(context);

      combine.combine(context.resources, fileName, options.minimize, function(err, data) {
        if (err) {
          fs.unlink(fileName);
        }

        context.fileCache = undefined;

        if (data) {
          data.platform = context.platform;
        }
        callback(err, data)
      });
    }

    loadConfig(configFile);
    initPlatforms();

    return {
        use: function(plugin) {
          plugins.use(plugin);
        },
        build: function(packageName, callback) {
            var packages = packageName ? [packageName] : config.packageList();
            packages.forEach(function(package) {
              var platforms = config.platformList(package);
              platforms.forEach(function(platform) {
                buildPlatform({package: package, platform: platform}, callback);
              });
            });
        },
        watch: function(packageName, callback) {
            var packages = packageName ? [packageName] : config.packageList(),
                self = this;

            // Watch for changes in the config file
            // WARN : This isn't really safe for multiple executions, this is a concious design decision
            watch.watchFile(configFile, [], function() {
                loadConfig(configFile);

                // Cleanup what we can, breaking things along the way
                watch.unwatchAll();
                fu.resetCache();

                // Give it a chance to cleanup before start recreating. Attempting to avoid the too many files error.
                setTimeout(function() {
                  initPlatforms();
                  self.watch(packageName, callback);
                }, 1000);
            });

            // Watch the individual components
            packages.forEach(function(name) {
                self.build(name, function(err, status) {
                    if (err) {
                        logError(err);
                        return;
                    }

                    callback(err, status);

                    var context = {package: status.package, platform: status.platform, module: status.module};
                    watch.watchFile("foo" + status.fileName + "foo", status.input, function() {
                      if (context.module) {
                        buildModule(_.clone(context), callback);
                      } else {
                        buildPlatform(_.clone(context), callback);
                      }
                    });
                });
            });
        }
    };
};
<commit_msg>lumbar.unwatch<commit_after>var _ = require('underscore'),
    combine = require('./jsCombine'),
    config = require('./config'),
    fs = require('fs'),
    fu = require('./fileUtil'),
    plugin = require('./plugin'),
    watch = require('./watcher.js'),
    growl = require('growl');

function logError(err) {
    // TODO : Move this to the executable
    console.error(err.stack || err.message);
    growl.notify(err.message, { title: 'Lumbar error' });
}

var configCache;
function loadConfig(path) {
  try {
    configCache = {};
    config.load(path);
  } catch (err) {
    // Do not die a horrible death if this is just a syntax error
    logError(err);
  }
}

exports.plugin = plugin.plugin;
exports.init = function(configFile, options) {
    var outdir = options.outdir,
        minimize = options.minimize,
        plugins = plugin.create(options);

    function initPlatforms() {
        config.platformList().forEach(function(platform) {
            if (!platform) {
                return;
            }
            try {
                console.log("mkdir:\t\033[90mmaking directory\033[0m " + outdir + '/' + platform);
                fs.mkdirSync(outdir + '/' + platform, 0755);
            } catch (err) {
                if (err.code !== 'EEXIST') {
                    throw err;
                }
            }
        });
    }

    function buildPlatform(context, callback) {
      context.combined = config.combineModules(context.package);

      var modules = config.moduleList(context.package);
      modules.forEach(function(module) {
        context = _.clone(context);
        context.module = module;
        buildModule(context, callback)
      });

      if (context.combined) {
        combineResources(context, context.package, function(err, data) {
          if (data) {
            data.package = context.package;
          }
          callback(err, data);
        });
      }
    }
    function buildModule(context, callback) {
      context.platformPath = (context.platform ? context.platform + '/' : '');
      context.module = config.module(context.module);
      context.options = options;
      context.config = config;
      context.configCache = configCache;
      context.fileCache = context.fileCache || {};
      context.moduleCache = {};

      var fileList = config.fileList(plugins, context);

      var resources = fileList.map(function(resource) {
        context.resource = resource;
        return plugins.resource(context);
      }).filter(function(resource) { return resource; });

      context.moduleResources = resources;
      plugins.module(context);

      if (!context.combined) {
        context.resources = context.moduleResources;
        combineResources(context, context.module.name, function(err, data) {
          if (data) {
            data.package = context.package;
            data.module = context.module.name;
          }
          callback(err, data);
        });
      } else {
        context.resources = context.resources || [];
        context.resources.push.apply(context.resources, context.moduleResources);
      }
    }
    function combineResources(context, name, callback) {
      var fileName = outdir + '/' + context.platformPath + name + '.js';

      context.fileName = fileName;
      plugins.file(context);

      combine.combine(context.resources, fileName, options.minimize, function(err, data) {
        if (err) {
          fs.unlink(fileName);
        }

        context.fileCache = undefined;

        if (data) {
          data.platform = context.platform;
        }
        callback(err, data)
      });
    }

    loadConfig(configFile);
    initPlatforms();

    return {
        use: function(plugin) {
          plugins.use(plugin);
        },
        build: function(packageName, callback) {
            var packages = packageName ? [packageName] : config.packageList();
            packages.forEach(function(package) {
              var platforms = config.platformList(package);
              platforms.forEach(function(platform) {
                buildPlatform({package: package, platform: platform}, callback);
              });
            });
        },
        watch: function(packageName, callback) {
            var packages = packageName ? [packageName] : config.packageList(),
                self = this;

            // Watch for changes in the config file
            // WARN : This isn't really safe for multiple executions, this is a concious design decision
            watch.watchFile(configFile, [], function() {
                loadConfig(configFile);

                // Cleanup what we can, breaking things along the way
                watch.unwatchAll();
                fu.resetCache();

                // Give it a chance to cleanup before start recreating. Attempting to avoid the too many files error.
                setTimeout(function() {
                  initPlatforms();
                  self.watch(packageName, callback);
                }, 1000);
            });

            // Watch the individual components
            packages.forEach(function(name) {
                self.build(name, function(err, status) {
                    if (err) {
                        logError(err);
                        return;
                    }

                    callback(err, status);

                    var context = {package: status.package, platform: status.platform, module: status.module};
                    watch.watchFile("foo" + status.fileName + "foo", status.input, function() {
                      if (context.module) {
                        buildModule(_.clone(context), callback);
                      } else {
                        buildPlatform(_.clone(context), callback);
                      }
                    });
                });
            });
        },
        unwatch: function() {
          watch.unwatchAll();
        }
    };
};
<|endoftext|>


<commit_before>'use strict';

var LanguageDetect = require('languagedetect');
var MailParser = require('mailparser').MailParser;
var _ = require('lodash');
var async = require('async');
var cheerio = require('cheerio');
var crypto = require('crypto');
var events = require('events');
var fs = require('fs');
var path = require('path');
var request = require('request');
var shell = require('shelljs');
var simplesmtp = require('simplesmtp');
var util = require('util');

var logger = require('./logger');
var mailUtilities = require('./mailUtilities');

var Mailin = function () {
    events.EventEmitter.call(this);

    /* Set up the default options. */
    this.options = {
        port: 2500,
        tmp: '.tmp',
        webhook: 'http://localhost:3000/webhook',
        disableWebhook: false,
        logFile: null,
        disableDkim: false,
        disableSpf: false,
        disableSpamScore: false,
        verbose: false
    };
};
util.inherits(Mailin, events.EventEmitter);

Mailin.prototype.start = function (options, callback) {
    var _this = this;

    options = options || {};
    if (_.isFunction(options)) {
        callback = options;
        options = {};
    }

    this.options = _.defaults(options, this.options);

    callback = callback || function () {};

    /* Create tmp dir if necessary. */
    if (!fs.existsSync(this.options.tmp)) {
        shell.mkdir('-p', this.options.tmp);
    }

    /* Log to a file if necessary. */
    if (this.options.logFile) {
        logger.setLogFile(this.options.logFile);
    }

    /* Set log level if necessary. */
    if (this.options.verbose) {
        logger.setLevel('verbose');
    }

    /* Check the webhook validity. */
    if (!this.options.disableWebhook) {
        request.head(this.options.webhook, function (err, resp) {
            if (err || resp.statusCode !== 200) {
                logger.warn('Webhook ' + _this.options.webhook +
                    ' seems invalid or down. You may want to double check the webhook url.');
            } else {
                logger.info('Webhook ' + _this.options.webhook + ' is valid, up and running.');
            }
        });
    }

    var smtp = simplesmtp.createServer({
        SMTPBanner: 'Mailin Smtp Server',
        // debug: true
    });

    smtp.on('startData', function (connection) {
        logger.info('Receiving message from ' + connection.from);

        /* Create a write stream to a tmp file to which the incoming mail
         * will be streamed. */
        var mailPath = path.join(_this.options.tmp, _this._makeId());
        var mailWriteStream = fs.createWriteStream(mailPath);

        connection.mailPath = mailPath;
        connection.mailWriteStream = mailWriteStream;

        _this.emit('startMessage', {
            from: connection.from,
            to: connection.to
        });
    });

    smtp.on('data', function (connection, chunk) {
        connection.mailWriteStream.write(chunk);
    });

    smtp.on('dataReady', function (connection, cbConnection) {
        logger.info('Processing message from ' + connection.from);

        async.auto({
                retrieveRawEmail: function (cbAuto) {
                    fs.readFile(connection.mailPath, function (err, data) {
                        if (err) return cbAuto(err);
                        cbAuto(null, data.toString());
                    });
                },

                validateDkim: ['retrieveRawEmail',
                    function (cbAuto, results) {
                        if (_this.options.disableDkim) return cbAuto(null, false);

                        logger.verbose('Validating dkim.');
                        var rawEmail = results.retrieveRawEmail;
                        mailUtilities.validateDkim(rawEmail, cbAuto);
                    }
                ],

                validateSpf: function (cbAuto) {
                    if (_this.options.disableSpf) return cbAuto(null, false);

                    logger.verbose('Validating spf.');
                    /* Get ip and host. */
                    mailUtilities.validateSpf(connection.remoteAddress,
                        connection.from, connection.host, cbAuto);
                },

                computeSpamScore: ['retrieveRawEmail',
                    function (cbAuto, results) {
                        if (_this.options.disableSpamScore) return cbAuto(null, 0.0);

                        logger.verbose('Computing spam score.');
                        var rawEmail = results.retrieveRawEmail;
                        mailUtilities.computeSpamScore(rawEmail, cbAuto);
                    }
                ],

                parseEmail: function (cbAuto) {
                    logger.verbose('Parsing email.');
                    /* Prepare the mail parser. */
                    var mailParser = new MailParser();
                    mailParser.on('end', function (mail) {
                        logger.verbose(util.inspect(mail, {
                            depth: 5
                        }));

                        /* Make sure that both text and html versions of the
                         * body are available. */
                        if (!mail.text && !mail.html) {
                            mail.text = '';
                            mail.html = '

';
                        } else if (!mail.html) {
                            mail.html = _this._convertTextToHtml(mail.text);
                        } else if (!mail.text) {
                            mail.text = _this._convertHtmlToText(mail.html);
                        }

                        cbAuto(null, mail);
                    });

                    /* Stream the written email to the parser. */
                    var mailReadStream = fs.createReadStream(connection.mailPath);
                    mailReadStream.pipe(mailParser);
                },

                detectLanguage: ['parseEmail',
                    function (cbAuto, results) {
                        logger.verbose('Detecting language.');
                        var text = results.parseEmail.text;

                        var language = '';
                        var languageDetector = new LanguageDetect();
                        var potentialLanguages = languageDetector.detect(text, 2);
                        if (potentialLanguages.length !== 0) {
                            logger.verbose('Potential languages: ' + util.inspect(potentialLanguages, {
                                depth: 5
                            }));

                            /* Use the first detected language.
                             * potentialLanguages = [['english', 0.5969], ['hungarian', 0.40563]] */
                            language = potentialLanguages[0][0];
                        } else {
                            logger.info('Unable to detect language for the current message.');
                        }

                        cbAuto(null, language);
                    }
                ],

                finalizeMessage: ['validateDkim', 'validateSpf', 'computeSpamScore',
                    'parseEmail', 'detectLanguage',
                    function (cbAuto, results) {
                        var isDkimValid = results.validateDkim;
                        var isSpfValid = results.validateSpf;
                        var spamScore = results.computeSpamScore;
                        var parsedEmail = results.parseEmail;
                        var language = results.detectLanguage;

                        /* Finalize the parsed email object. */
                        parsedEmail.dkim = isDkimValid ? 'pass' : 'failed';
                        parsedEmail.spf = isSpfValid ? 'pass' : 'failed';
                        parsedEmail.spamScore = spamScore;
                        parsedEmail.language = language;

                        /* Make fields exist, even if empty. That will make
                         * json easier to use on the webhook receiver side. */
                        parsedEmail.cc = parsedEmail.cc || [];
                        parsedEmail.attachments = parsedEmail.attachments || [];

                        _this.emit('message', parsedEmail);

                        cbAuto(null, parsedEmail);
                    }
                ],

                postToWebhook: ['finalizeMessage',
                    function (cbAuto, results) {
                        if (_this.options.disableWebhook) return cbAuto(null);

                        logger.info('Sending request to webhook ' + _this.options.webhook);
                        var finalizedMessage = results.finalizeMessage;

                        /* Remove the attachments contents from the message,
                         * they will be posted as multipart of a form as key
                         * values pairs (attachmentName, attachmentContent). */
                        var attachmentNamesAndContent = {};
                        finalizedMessage.attachments.forEach(function (attachment) {
                            attachmentNamesAndContent[attachment.generatedFileName] = attachment.content;
                            delete attachment.content;
                        });

                        logger.verbose(finalizedMessage);

                        /* Send the request to the webhook. */
                        var r = request.post({
                            url: _this.options.webhook,
                            timeout: 30000
                        }, function (err, resp, body) {
                            if (err || resp.statusCode !== 200) {
                                logger.warn('Error in posting to webhook ' + _this.options.webhook);
                                logger.warn('Response status code: ' + resp.statusCode);
                                return cbAuto(err);
                            }

                            logger.info('Succesfully posted to webhook ' + _this.options.webhook);
                            logger.verbose(body);
                            return cbAuto(null);
                        });

                        var form = r.form();
                        form.append('mailinMsg', JSON.stringify(finalizedMessage));
                        for (var attachmentName in attachmentNamesAndContent) {
                            if (attachmentNamesAndContent.hasOwnProperty(attachmentName)) {
                                form.append(attachmentName, attachmentNamesAndContent[attachmentName]);
                            }
                        }
                    }
                ]
            },
            function (err) {
                if (err) return logger.error(err);

                /* Don't forget to unlink the tmp file. */
                fs.unlink(connection.mailPath, function (err) {
                    if (err) return logger.error(err);
                });

                logger.info('End processing message.');
            });

        /* Close the connection. */
        cbConnection(null, 'ABC1');
    });

    smtp.listen(_this.options.port, function (err) {
        if (!err) {
            logger.info('Mailin Smtp server listening on port ' + _this.options.port);
        } else {
            logger.error('Could not start server on port ' + _this.options.port + '.');
            if (_this.options.port < 1000) {
                logger.error('Ports under 1000 require root privileges.');
            }

            logger.error(err.message);
        }

        callback(err);
    });
};

Mailin.prototype._makeId = function () {
    return crypto.randomBytes(20).toString('hex');
};

Mailin.prototype._makeShortId = function () {
    return this._makeId().substr(0, 6);
};

Mailin.prototype._convertTextToHtml = function (text) {
    /* Replace newlines by
. */
    text = text.replace(/(\n\r)|(\n)/g, '
');
    /* Remove
 at the begining. */
    text = text.replace(/^\s*(
)*\s*/, '');
    /* Remove
 at the end. */
    text = text.replace(/\s*(
)*\s*$/, '');

    return text;
};

Mailin.prototype._convertHtmlToText = function (html) {
    var $ = cheerio.load(html);
    return $.root().text();
};

module.exports = new Mailin();
<commit_msg>Fix bug when handling failed webhook<commit_after>'use strict';

var LanguageDetect = require('languagedetect');
var MailParser = require('mailparser').MailParser;
var _ = require('lodash');
var async = require('async');
var cheerio = require('cheerio');
var crypto = require('crypto');
var events = require('events');
var fs = require('fs');
var path = require('path');
var request = require('request');
var shell = require('shelljs');
var simplesmtp = require('simplesmtp');
var util = require('util');

var logger = require('./logger');
var mailUtilities = require('./mailUtilities');

var Mailin = function () {
    events.EventEmitter.call(this);

    /* Set up the default options. */
    this.options = {
        port: 2500,
        tmp: '.tmp',
        webhook: 'http://localhost:3000/webhook',
        disableWebhook: false,
        logFile: null,
        disableDkim: false,
        disableSpf: false,
        disableSpamScore: false,
        verbose: false
    };
};
util.inherits(Mailin, events.EventEmitter);

Mailin.prototype.start = function (options, callback) {
    var _this = this;

    options = options || {};
    if (_.isFunction(options)) {
        callback = options;
        options = {};
    }

    this.options = _.defaults(options, this.options);

    callback = callback || function () {};

    /* Create tmp dir if necessary. */
    if (!fs.existsSync(this.options.tmp)) {
        shell.mkdir('-p', this.options.tmp);
    }

    /* Log to a file if necessary. */
    if (this.options.logFile) {
        logger.setLogFile(this.options.logFile);
    }

    /* Set log level if necessary. */
    if (this.options.verbose) {
        logger.setLevel('verbose');
    }

    /* Check the webhook validity. */
    if (!this.options.disableWebhook) {
        request.head(this.options.webhook, function (err, resp) {
            if (err || resp.statusCode !== 200) {
                logger.warn('Webhook ' + _this.options.webhook +
                    ' seems invalid or down. You may want to double check the webhook url.');
            } else {
                logger.info('Webhook ' + _this.options.webhook + ' is valid, up and running.');
            }
        });
    }

    var smtp = simplesmtp.createServer({
        SMTPBanner: 'Mailin Smtp Server',
        debug: this.options.verbose
    });

    smtp.on('startData', function (connection) {
        logger.info('Receiving message from ' + connection.from);

        /* Create a write stream to a tmp file to which the incoming mail
         * will be streamed. */
        var mailPath = path.join(_this.options.tmp, _this._makeId());
        var mailWriteStream = fs.createWriteStream(mailPath);

        connection.mailPath = mailPath;
        connection.mailWriteStream = mailWriteStream;

        _this.emit('startMessage', {
            from: connection.from,
            to: connection.to
        });
    });

    smtp.on('data', function (connection, chunk) {
        connection.mailWriteStream.write(chunk);
    });

    smtp.on('dataReady', function (connection, cbConnection) {
        logger.info('Processing message from ' + connection.from);

        async.auto({
                retrieveRawEmail: function (cbAuto) {
                    fs.readFile(connection.mailPath, function (err, data) {
                        if (err) return cbAuto(err);
                        cbAuto(null, data.toString());
                    });
                },

                validateDkim: ['retrieveRawEmail',
                    function (cbAuto, results) {
                        if (_this.options.disableDkim) return cbAuto(null, false);

                        logger.verbose('Validating dkim.');
                        var rawEmail = results.retrieveRawEmail;
                        mailUtilities.validateDkim(rawEmail, cbAuto);
                    }
                ],

                validateSpf: function (cbAuto) {
                    if (_this.options.disableSpf) return cbAuto(null, false);

                    logger.verbose('Validating spf.');
                    /* Get ip and host. */
                    mailUtilities.validateSpf(connection.remoteAddress,
                        connection.from, connection.host, cbAuto);
                },

                computeSpamScore: ['retrieveRawEmail',
                    function (cbAuto, results) {
                        if (_this.options.disableSpamScore) return cbAuto(null, 0.0);

                        logger.verbose('Computing spam score.');
                        var rawEmail = results.retrieveRawEmail;
                        mailUtilities.computeSpamScore(rawEmail, cbAuto);
                    }
                ],

                parseEmail: function (cbAuto) {
                    logger.verbose('Parsing email.');
                    /* Prepare the mail parser. */
                    var mailParser = new MailParser();
                    mailParser.on('end', function (mail) {
                        logger.verbose(util.inspect(mail, {
                            depth: 5
                        }));

                        /* Make sure that both text and html versions of the
                         * body are available. */
                        if (!mail.text && !mail.html) {
                            mail.text = '';
                            mail.html = '

';
                        } else if (!mail.html) {
                            mail.html = _this._convertTextToHtml(mail.text);
                        } else if (!mail.text) {
                            mail.text = _this._convertHtmlToText(mail.html);
                        }

                        cbAuto(null, mail);
                    });

                    /* Stream the written email to the parser. */
                    var mailReadStream = fs.createReadStream(connection.mailPath);
                    mailReadStream.pipe(mailParser);
                },

                detectLanguage: ['parseEmail',
                    function (cbAuto, results) {
                        logger.verbose('Detecting language.');
                        var text = results.parseEmail.text;

                        var language = '';
                        var languageDetector = new LanguageDetect();
                        var potentialLanguages = languageDetector.detect(text, 2);
                        if (potentialLanguages.length !== 0) {
                            logger.verbose('Potential languages: ' + util.inspect(potentialLanguages, {
                                depth: 5
                            }));

                            /* Use the first detected language.
                             * potentialLanguages = [['english', 0.5969], ['hungarian', 0.40563]] */
                            language = potentialLanguages[0][0];
                        } else {
                            logger.info('Unable to detect language for the current message.');
                        }

                        cbAuto(null, language);
                    }
                ],

                finalizeMessage: ['validateDkim', 'validateSpf', 'computeSpamScore',
                    'parseEmail', 'detectLanguage',
                    function (cbAuto, results) {
                        var isDkimValid = results.validateDkim;
                        var isSpfValid = results.validateSpf;
                        var spamScore = results.computeSpamScore;
                        var parsedEmail = results.parseEmail;
                        var language = results.detectLanguage;

                        /* Finalize the parsed email object. */
                        parsedEmail.dkim = isDkimValid ? 'pass' : 'failed';
                        parsedEmail.spf = isSpfValid ? 'pass' : 'failed';
                        parsedEmail.spamScore = spamScore;
                        parsedEmail.language = language;

                        /* Make fields exist, even if empty. That will make
                         * json easier to use on the webhook receiver side. */
                        parsedEmail.cc = parsedEmail.cc || [];
                        parsedEmail.attachments = parsedEmail.attachments || [];

                        _this.emit('message', parsedEmail);

                        cbAuto(null, parsedEmail);
                    }
                ],

                postToWebhook: ['finalizeMessage',
                    function (cbAuto, results) {
                        if (_this.options.disableWebhook) return cbAuto(null);

                        logger.info('Sending request to webhook ' + _this.options.webhook);
                        var finalizedMessage = results.finalizeMessage;

                        /* Remove the attachments contents from the message,
                         * they will be posted as multipart of a form as key
                         * values pairs (attachmentName, attachmentContent). */
                        var attachmentNamesAndContent = {};
                        finalizedMessage.attachments.forEach(function (attachment) {
                            attachmentNamesAndContent[attachment.generatedFileName] = attachment.content;
                            delete attachment.content;
                        });

                        logger.verbose(finalizedMessage);

                        /* Send the request to the webhook. */
                        var r = request.post({
                            url: _this.options.webhook,
                            timeout: 30000
                        }, function (err, resp, body) {
                            if (err || resp.statusCode !== 200) {
                                logger.warn('Error in posting to webhook ' + _this.options.webhook);
                                if (resp) logger.warn('Response status code: ' + resp.statusCode);
                                return cbAuto(err);
                            }

                            logger.info('Succesfully posted to webhook ' + _this.options.webhook);
                            logger.verbose(body);
                            return cbAuto(null);
                        });

                        var form = r.form();
                        form.append('mailinMsg', JSON.stringify(finalizedMessage));
                        for (var attachmentName in attachmentNamesAndContent) {
                            if (attachmentNamesAndContent.hasOwnProperty(attachmentName)) {
                                form.append(attachmentName, attachmentNamesAndContent[attachmentName]);
                            }
                        }
                    }
                ]
            },
            function (err) {
                if (err) return logger.error(err);

                /* Don't forget to unlink the tmp file. */
                fs.unlink(connection.mailPath, function (err) {
                    if (err) return logger.error(err);
                });

                logger.info('End processing message.');
            });

        /* Close the connection. */
        cbConnection(null, 'ABC1');
    });

    smtp.listen(_this.options.port, function (err) {
        if (!err) {
            logger.info('Mailin Smtp server listening on port ' + _this.options.port);
        } else {
            logger.error('Could not start server on port ' + _this.options.port + '.');
            if (_this.options.port < 1000) {
                logger.error('Ports under 1000 require root privileges.');
            }

            logger.error(err.message);
        }

        callback(err);
    });
};

Mailin.prototype._makeId = function () {
    return crypto.randomBytes(20).toString('hex');
};

Mailin.prototype._makeShortId = function () {
    return this._makeId().substr(0, 6);
};

Mailin.prototype._convertTextToHtml = function (text) {
    /* Replace newlines by
. */
    text = text.replace(/(\n\r)|(\n)/g, '
');
    /* Remove
 at the begining. */
    text = text.replace(/^\s*(
)*\s*/, '');
    /* Remove
 at the end. */
    text = text.replace(/\s*(
)*\s*$/, '');

    return text;
};

Mailin.prototype._convertHtmlToText = function (html) {
    var $ = cheerio.load(html);
    return $.root().text();
};

module.exports = new Mailin();
<|endoftext|>


<commit_before>/*
 * Flush stats to a downstream statsd server.
 *
 * To enable this backend, include 'smart_repeater' in the backends
 * configuration array:
 *
 *   backends: ['smart_repeater']
 */

var util = require('util'),
    dgram = require('dgram'),
    net = require('net'),
    logger = require('../lib/logger');

var l;
var debug;

function SmartRepeater(startupTime, config, emitter){
	var self = this;

	this.config = config.smartRepeater || {};

	this.prefix = this.config.prefix || '';
	this.prefix = (this.prefix.length == 0) ? "" : (this.prefix + ".");
	this.checkExistingPrefix = this.config.checkExistingPrefix || false;
	this.batchSize = this.config.batchSize || 1024;
	this.blacklist = this.config.blacklist || [];

	this.hostinfo = [];
	for (var i = 0; i < this.config.hosts.length; i++) {
		var host = this.config.hosts[i];
		this.hostinfo.push({
			config: host,
			errors: 0,
			flushTime: 0,
			flushes: 0,
		});
	}

	emitter.on('flush', function(time_stamp, metrics) { self.process(time_stamp, metrics); });
};

SmartRepeater.prototype.prefixedKey = function(key) {
	if (!this.checkExistingPrefix && key.indexOf(this.prefix) === 0) {
		return key;
	}

	return this.prefix + key;
}

SmartRepeater.prototype.blacklisted = function(key) {
	var i;

	for (i = 0; i < this.blacklist.length; i++) {
		if (this.blacklist[i].test(key)) {
			return true;
		}
	}

	return false;
}

SmartRepeater.prototype.sampleRateToString = function(number) {
	var string = number.toFixed(3);
	var index = string.length - 1;

	if (string[index] == "0") {
		while (index >= 0 && (string[index] == "0" || string[index] == ".")) { index--; }
		if (index < 0) {
			string = '0';
		}
		else {
			string = string.substring(0, index + 1);
		}
	}

	return string;
};

SmartRepeater.prototype.reconstituteMessages = function(metrics) {
	var key, i;
	var outgoing = [];

	for (key in metrics.gauges) {
		if (this.blacklisted(key)) { continue; }
		outgoing.push(this.prefixedKey(key) + ":" + metrics.gauges[key] + "|g");
	}

	for (key in metrics.counters) {
		if (this.blacklisted(key)) { continue; }
		outgoing.push(this.prefixedKey(key) + ":" + metrics.counters[key] + "|c");
	}

	for (key in metrics.timers) {
		if (this.blacklisted(key)) { continue; }
		var values = metrics.timers[key];
		var sampleRate = values.length / metrics.timer_counters[key];
		var sampleRateString = (sampleRate >= 1) ? "" : ("|@" + this.sampleRateToString(sampleRate));

		var rebuiltValues = [];
		for (i = 0; i < values.length; i++) {
			rebuiltValues.push(values[i] + "|ms" + sampleRateString);
		}

		outgoing.push(this.prefixedKey(key) + ":" + rebuiltValues.join(":"));
	}

	for (key in metrics.sets) {
		if (this.blacklisted(key)) { continue; }
		var values = metrics.sets[key].values();

		var rebuiltValues = [];
		for (i = 0; i < values.length; i++) {
			rebuiltValues.push(values[i] + "|s");
		}

		outgoing.push(this.prefixedKey(key) + ":" + rebuiltValues.join(":"));
	}

	return outgoing;
};

SmartRepeater.prototype.sendToHost = function(host, metrics) {
	var i;

	var starttime = Date.now();

	var data = this.splitStats([
		this.prefix + "statsd-smart-repeater.errors:" + host.errors + "|g",
		this.prefix + "statsd-smart-repeater.flushTime:" + host.flushTime + "|g",
		this.prefix + "statsd-smart-repeater.flushes:" + host.flushes + "|g",
	]).concat(metrics);

	try {
		if (host.config.protocol == "udp4" || host.config.protocol == "udp6") {
			var sock = dgram.createSocket(host.config.protocol);
			for (i = 0; i < data.length; i++) {
				var single = data[i];
				var buffer = new Buffer(single);
				sock.send(buffer, 0, single.length, host.config.port, host.config.hostname, function(err, bytes) {
					if (err && debug) {
						l.log(err);
					}
				});
			}
		}
		else {
			var connection = net.createConnection(host.config.port, host.config.hostname);
			connection.addListener('error', function(connectionException) {
				if (debug) {
					l.log(connectionException);
				}
				host.errors++;
			});
			connection.on('connect', function() {
				for (i = 0; i < data.length; i++) {
					var single = data[i];
					if (i != 0) {
						this.write("\n");
					}
					this.write(single);
				}
				this.end();
			});
		}
	}
	catch(e) {
		if (debug) {
			l.log(e);
		}
		host.errors++;
	}

	host.flushTime = (Date.now() - starttime);
	host.flushes++;
};

SmartRepeater.prototype.splitStats = function(stats) {
	var i;

	var buffers = [];
	var buffer = [];
	var bufferLength = 0;

	for (i = 0; i < stats.length; i++) {
		var line = stats[i];
		var lineLength = line.length;

		if (bufferLength != 0 && (bufferLength + lineLength) > this.config.batchSize) {
			buffers.push(buffer);
			buffer = [];
			bufferLength = 0;
		}

		buffer.push(line);
		bufferLength += lineLength;
	}

	if (bufferLength > 0) {
		buffers.push(buffer);
	}

	var lines = [];
	for (i = 0; i < buffers.length; i++) {
		lines.push(buffers[i].join("\n"));
	}

	return lines;
}

SmartRepeater.prototype.distribute = function(reconstituted) {
	var i;

	var lines = this.splitStats(reconstituted);

	for (i = 0; i < this.hostinfo.length; i++) {
		var host = this.hostinfo[i];
		this.sendToHost(host, lines);
	}
};

SmartRepeater.prototype.process = function(time_stamp, metrics) {
	this.distribute(this.reconstituteMessages(metrics));
};

exports.init = function(startupTime, config, events) {
	var instance = new SmartRepeater(startupTime, config, events);
	l = new logger.Logger(config.log || {});
	debug = config.debug;
	return true;
};
<commit_msg>fix existing prefix negation bug<commit_after>/*
 * Flush stats to a downstream statsd server.
 *
 * To enable this backend, include 'smart_repeater' in the backends
 * configuration array:
 *
 *   backends: ['smart_repeater']
 */

var util = require('util'),
    dgram = require('dgram'),
    net = require('net'),
    logger = require('../lib/logger');

var l;
var debug;

function SmartRepeater(startupTime, config, emitter){
	var self = this;

	this.config = config.smartRepeater || {};

	this.prefix = this.config.prefix || '';
	this.prefix = (this.prefix.length == 0) ? "" : (this.prefix + ".");
	this.checkExistingPrefix = this.config.checkExistingPrefix || false;
	this.batchSize = this.config.batchSize || 1024;
	this.blacklist = this.config.blacklist || [];

	this.hostinfo = [];
	for (var i = 0; i < this.config.hosts.length; i++) {
		var host = this.config.hosts[i];
		this.hostinfo.push({
			config: host,
			errors: 0,
			flushTime: 0,
			flushes: 0,
		});
	}

	emitter.on('flush', function(time_stamp, metrics) { self.process(time_stamp, metrics); });
};

SmartRepeater.prototype.prefixedKey = function(key) {
	if (this.checkExistingPrefix && key.indexOf(this.prefix) === 0) {
		return key;
	}

	return this.prefix + key;
}

SmartRepeater.prototype.blacklisted = function(key) {
	var i;

	for (i = 0; i < this.blacklist.length; i++) {
		if (this.blacklist[i].test(key)) {
			return true;
		}
	}

	return false;
}

SmartRepeater.prototype.sampleRateToString = function(number) {
	var string = number.toFixed(3);
	var index = string.length - 1;

	if (string[index] == "0") {
		while (index >= 0 && (string[index] == "0" || string[index] == ".")) { index--; }
		if (index < 0) {
			string = '0';
		}
		else {
			string = string.substring(0, index + 1);
		}
	}

	return string;
};

SmartRepeater.prototype.reconstituteMessages = function(metrics) {
	var key, i;
	var outgoing = [];

	for (key in metrics.gauges) {
		if (this.blacklisted(key)) { continue; }
		outgoing.push(this.prefixedKey(key) + ":" + metrics.gauges[key] + "|g");
	}

	for (key in metrics.counters) {
		if (this.blacklisted(key)) { continue; }
		outgoing.push(this.prefixedKey(key) + ":" + metrics.counters[key] + "|c");
	}

	for (key in metrics.timers) {
		if (this.blacklisted(key)) { continue; }
		var values = metrics.timers[key];
		var sampleRate = values.length / metrics.timer_counters[key];
		var sampleRateString = (sampleRate >= 1) ? "" : ("|@" + this.sampleRateToString(sampleRate));

		var rebuiltValues = [];
		for (i = 0; i < values.length; i++) {
			rebuiltValues.push(values[i] + "|ms" + sampleRateString);
		}

		outgoing.push(this.prefixedKey(key) + ":" + rebuiltValues.join(":"));
	}

	for (key in metrics.sets) {
		if (this.blacklisted(key)) { continue; }
		var values = metrics.sets[key].values();

		var rebuiltValues = [];
		for (i = 0; i < values.length; i++) {
			rebuiltValues.push(values[i] + "|s");
		}

		outgoing.push(this.prefixedKey(key) + ":" + rebuiltValues.join(":"));
	}

	return outgoing;
};

SmartRepeater.prototype.sendToHost = function(host, metrics) {
	var i;

	var starttime = Date.now();

	var data = this.splitStats([
		this.prefix + "statsd-smart-repeater.errors:" + host.errors + "|g",
		this.prefix + "statsd-smart-repeater.flushTime:" + host.flushTime + "|g",
		this.prefix + "statsd-smart-repeater.flushes:" + host.flushes + "|g",
	]).concat(metrics);

	try {
		if (host.config.protocol == "udp4" || host.config.protocol == "udp6") {
			var sock = dgram.createSocket(host.config.protocol);
			for (i = 0; i < data.length; i++) {
				var single = data[i];
				var buffer = new Buffer(single);
				sock.send(buffer, 0, single.length, host.config.port, host.config.hostname, function(err, bytes) {
					if (err && debug) {
						l.log(err);
					}
				});
			}
		}
		else {
			var connection = net.createConnection(host.config.port, host.config.hostname);
			connection.addListener('error', function(connectionException) {
				if (debug) {
					l.log(connectionException);
				}
				host.errors++;
			});
			connection.on('connect', function() {
				for (i = 0; i < data.length; i++) {
					var single = data[i];
					if (i != 0) {
						this.write("\n");
					}
					this.write(single);
				}
				this.end();
			});
		}
	}
	catch(e) {
		if (debug) {
			l.log(e);
		}
		host.errors++;
	}

	host.flushTime = (Date.now() - starttime);
	host.flushes++;
};

SmartRepeater.prototype.splitStats = function(stats) {
	var i;

	var buffers = [];
	var buffer = [];
	var bufferLength = 0;

	for (i = 0; i < stats.length; i++) {
		var line = stats[i];
		var lineLength = line.length;

		if (bufferLength != 0 && (bufferLength + lineLength) > this.config.batchSize) {
			buffers.push(buffer);
			buffer = [];
			bufferLength = 0;
		}

		buffer.push(line);
		bufferLength += lineLength;
	}

	if (bufferLength > 0) {
		buffers.push(buffer);
	}

	var lines = [];
	for (i = 0; i < buffers.length; i++) {
		lines.push(buffers[i].join("\n"));
	}

	return lines;
}

SmartRepeater.prototype.distribute = function(reconstituted) {
	var i;

	var lines = this.splitStats(reconstituted);

	for (i = 0; i < this.hostinfo.length; i++) {
		var host = this.hostinfo[i];
		this.sendToHost(host, lines);
	}
};

SmartRepeater.prototype.process = function(time_stamp, metrics) {
	this.distribute(this.reconstituteMessages(metrics));
};

exports.init = function(startupTime, config, events) {
	var instance = new SmartRepeater(startupTime, config, events);
	l = new logger.Logger(config.log || {});
	debug = config.debug;
	return true;
};
<|endoftext|>


<commit_before>var path = require('path'),
    _settings = require('./mapnik_settings'),
    mapnik = require('./_mapnik.node');

exports = module.exports = mapnik;
exports.settings = _settings;

if (_settings.paths.fonts) {
    // TODO - make async and warn if not successful
    mapnik.register_fonts(_settings.paths.fonts);
}

if (_settings.paths.input_plugins) {
    // TODO - make async and warn if not successful
    mapnik.register_datasources(_settings.paths.input_plugins);
}

// TODO - move into mapnik core - http://trac.mapnik.org/ticket/602
exports.register_system_fonts = function() {
    var num_faces = mapnik.fonts().length;
    var dirs = [];

    /*
      node process.version is based on tools/wafadmin/Utils.py
      which pulls from python's sys.platform (uname)
      this info taken from Tools/ccroot.py

    platforms = {
    '__linux__'   : 'linux',
    '__GNU__'     : 'hurd',
    '__FreeBSD__' : 'freebsd',
    '__NetBSD__'  : 'netbsd',
    '__OpenBSD__' : 'openbsd',
    '__sun'       : 'sunos',
    '__hpux'      : 'hpux',
    '__sgi'       : 'irix',
    '_AIX'        : 'aix',
    '__CYGWIN__'  : 'cygwin',
    '__MSYS__'    : 'msys',
    '_UWIN'       : 'uwin',
    '_WIN64'      : 'win32',
    '_WIN32'      : 'win32'
    }

    */

    // linux, bsd systems or solaris/opensolaris
    if (process.platform == 'linux' ||
       (process.platform.indexOf('bsd') != -1) ||
       process.platform.indexOf('bsd') == 'sunos') {
        dirs.push('/usr/share/fonts/truetype/');
        dirs.push('/usr/local/share/fonts/truetype/');
        dirs.push(path.join(process.env.HOME, '.fonts'));
    }

    else if (process.platform == 'darwin') {
        dirs.push('/Library/Fonts');
        dirs.push('/System/Library/Fonts');
        if (path.existsSync('/usr/X11R6/lib/X11/fonts/')) {
            // if xcode's X11 is installed
            dirs.push('/usr/X11R6/lib/X11/fonts/');
        }
        dirs.push(path.join(process.env.HOME, 'Library/Fonts'));
    }

    else if (process.platform == 'win32') {
       dirs.push('C:\\Windows\\Fonts');
    }

    // TODO mysys and cygwin

    var found = [];
    for (var idx in dirs) {
        var p = dirs[idx];
        if (path.existsSync(p)) {
                if (mapnik.register_fonts(p, {recurse: true}))
                    found.push(p);
        } else {
            dirs.splice(dirs.indexOf(p), 1);
        }
    }

    if (mapnik.fonts().length == num_faces) {
       return false;
    }
    else {
       return true;
    }
};

exports.register_system_fonts();
<commit_msg>clean up system font registration<commit_after>var path = require('path'),
    _settings = require('./mapnik_settings'),
    mapnik = require('./_mapnik.node');

exports = module.exports = mapnik;
exports.settings = _settings;

if (_settings.paths.fonts) {
    // TODO - make async and warn if not successful
    mapnik.register_fonts(_settings.paths.fonts);
}

if (_settings.paths.input_plugins) {
    // TODO - make async and warn if not successful
    mapnik.register_datasources(_settings.paths.input_plugins);
}

// TODO - move into mapnik core - http://trac.mapnik.org/ticket/602
exports.register_system_fonts = function() {
    var num_faces = mapnik.fonts().length;
    var dirs = [];

    /*
      node process.version is based on tools/wafadmin/Utils.py
      which pulls from python's sys.platform (uname)
      this info taken from Tools/ccroot.py

    platforms = {
    '__linux__'   : 'linux',
    '__GNU__'     : 'hurd',
    '__FreeBSD__' : 'freebsd',
    '__NetBSD__'  : 'netbsd',
    '__OpenBSD__' : 'openbsd',
    '__sun'       : 'sunos',
    '__hpux'      : 'hpux',
    '__sgi'       : 'irix',
    '_AIX'        : 'aix',
    '__CYGWIN__'  : 'cygwin',
    '__MSYS__'    : 'msys',
    '_UWIN'       : 'uwin',
    '_WIN64'      : 'win32',
    '_WIN32'      : 'win32'
    }

    */

    // linux, bsd systems or solaris/opensolaris
    if (process.platform == 'linux' ||
       (process.platform.indexOf('bsd') != -1) ||
       process.platform.indexOf('bsd') == 'sunos') {
        dirs.push('/usr/share/fonts/truetype/');
        dirs.push('/usr/local/share/fonts/truetype/');
        dirs.push(path.join(process.env.HOME, '.fonts'));
    }

    else if (process.platform == 'darwin') {
        dirs.push('/Library/Fonts');
        dirs.push('/System/Library/Fonts');
        if (path.existsSync('/usr/X11R6/lib/X11/fonts/')) {
            // if xcode's X11 is installed
            dirs.push('/usr/X11R6/lib/X11/fonts/');
        }
        dirs.push(path.join(process.env.HOME, 'Library/Fonts'));
    }

    else if (process.platform == 'win32') {
       dirs.push('C:\\Windows\\Fonts');
    }

    dirs.forEach(function(p) {
        if (path.existsSync(p)) {
            mapnik.register_fonts(p, {recurse: true})
        }
    });

    if (mapnik.fonts().length == num_faces) {
       return false;
    }
    else {
       return true;
    }
};

exports.register_system_fonts();
<|endoftext|>


<commit_before>dojo.provide("dojox.socket");

dojox.socket = function(/*dojo.__XhrArgs*/ argsOrUrl){
	// summary:
	//		Provides a simple socket connection using WebSocket, or alternate
	// 		communication mechanisms in legacy browsers for comet-style communication. This is based
	//		on the WebSocket API and returns an object that implements the WebSocket interface:
	//		http://dev.w3.org/html5/websockets/#websocket
	//	description:
	//		Provides socket connections. This can be used with virtually any Comet protocol.
	//	argsOrUrl:
	//		This uses the same arguments as the other I/O functions in Dojo, or a
	// 		URL to connect to. The URL should be a relative URL in order to properly
	//		work with WebSockets (it can still be host relative, like //other-site.org/endpoint)
	// returns:
	// 		An object that implements the WebSocket API
	// example:
	//		| dojo.require("dojox.socket");
	//		| var socket = dojox.socket({"//comet-server/comet");
	//		| // we could also add auto-reconnect support
	//		| // now we can connect to standard HTML5 WebSocket-style events
	//		| dojo.connect(socket, "onmessage", function(event){
	//		|    var message = event.data;
	//		|    // do something with the message
	//		| });
	//		| // send something
	//		| socket.send("hi there");
	//		| whenDone(function(){
	//		|   socket.close();
	//		| });
	//		You can also use the Reconnect module:
	//		| dojo.require("dojox.socket");
	//		| dojo.require("dojox.socket.Reconnect");
	//		| var socket = dojox.socket({url:"/comet"});
	//		| // add auto-reconnect support
	//		| socket = dojox.socket.Reconnect(socket);
	if(typeof argsOrUrl == "string"){
		argsOrUrl = {url: argsOrUrl};
	}
	return window.WebSocket ? dojox.socket.WebSocket(argsOrUrl) : dojox.socket.LongPoll(argsOrUrl);
};

dojox.socket.WebSocket = function(args){
	// summary:
	//		A wrapper for WebSocket, than handles standard args and relative URLs
	var ws = new WebSocket(new dojo._Url(document.baseURI.replace(/^http/i,'ws'), args.url));
	ws.on = ws.addEventListener;
	return ws;
};

dojox.socket.LongPoll = function(/*dojo.__XhrArgs*/ args){
	// summary:
	//		Provides a simple long-poll based comet-style socket/connection to a server and returns an
	// 		object implementing the WebSocket interface:
	//		http://dev.w3.org/html5/websockets/#websocket
	//	args:
	//		This uses the same arguments as the other I/O functions in Dojo, with this addition:
	//	args.transport:
	//		Provide an alternate transport like dojo.io.script.get
	// returns:
	// 		An object that implements the WebSocket API
	// example:
	//		| dojo.require("dojox.socket.LongPoll");
	//		| var socket = dojox.socket.LongPoll({url:"/comet"});
	//		or:
	//		| dojo.require("dojox.socket.LongPoll");
	//		| dojox.socket.LongPoll.add();
	//		| var socket = dojox.socket({url:"/comet"});

var cancelled = false,
		first = true,
		connections = [];

	// create the socket object
	var socket = {
		send: function(data){
			// summary:
			// 		Send some data using XHR or provided transport
			args.postData = data;
			var deferred = first ? (first = false) || socket.firstRequest(args) :
				socket.transport(args);
			connections.push(deferred);
			deferred.then(function(response){
				// got a response
				socket.readyState = 1;
				// remove the current connection
				connections.splice(dojo.indexOf(connections, deferred), 1);
				// reconnect to listen for the next message if there are no active connections,
				// we queue it up in case one of the onmessage handlers has a message to send
				if(!connections.length){
					setTimeout(connect);
				}
				if(response){
					// now send the message along to listeners
					fire("message", {data: response}, deferred);
				}
			}, function(error){
				connections.splice(dojo.indexOf(connections, deferred), 1);
				// an error occurred, fire the appropriate event listeners
				if(!cancelled){
					fire("error", {error:error}, deferred);
					if(!connections.length){
						socket.readyState = 3;
						fire("close", {wasClean:false}, deferred);
					}
				}
			});
			return deferred;
		},
		close: function(){
			// summary:
			// 		Close the connection
			socket.readyState = 2;
			cancelled = true;
			for(var i = 0; i < connections.length; i++){
				connections[i].cancel();
			}
			socket.readyState = 3;
			fire("close", {wasClean:true}, deferred);
		},
		transport: args.transport || dojo.xhrPost,
		args: args,
		url: args.url,
		readyState: 0,
		CONNECTING: 0,
		OPEN: 1,
		CLOSING: 2,
		CLOSED: 3,
		dispatchEvent: function(event){
			fire(event.type, event);
		},
		on: function(type, callback){
			return dojo.connect(this, "on" + type, callback);
		},
		firstRequest: function(args){
			// summary:
			// 		This allows for special handling for the first request. This is useful for
			//		providing information to disambiguate between the first request and
			//		subsequent long-poll requests so the server can properly setup a
			// 		connection on the first connection or reject a request for an expired
			// 		connection if the request is not expecting to be the first for a connection.
			//		This method can be overriden. The default behavior is to include a Pragma
			//		header with a value of "start-long-poll"
			var headers = (args.headers || (args.headers = {}));
			headers.Pragma = "start-long-poll";
			try{
				return this.transport(args);
			}finally{
				// cleanup the header so it is not used on subsequent requests
				delete headers.Pragma;
			}
		}
	};
	function connect(){
		if(socket.readyState == 0){
			// we fire the open event now because we really don't know when the "socket"
			// is truly open, and this gives us a to do a send() and get it included in the
			// HTTP request
			fire("open",{});
		}
		// make the long-poll connection, to wait for response from the server
		if(!connections.length){
			socket.send();
		}
	}
	function fire(event, object, deferred){
		if(socket["on" + event]){
			object.type = event;
			object.ioArgs = deferred && deferred.ioArgs;
			socket["on" + event](object);
		}
	}
	socket.connect = socket.on;
	// do the initial connection
	setTimeout(connect);
	return socket;
};
<commit_msg>Interval handling, delegate args, using rawBody.<commit_after>dojo.provide("dojox.socket");

dojox.socket = function(/*dojo.__XhrArgs*/ argsOrUrl){
	// summary:
	//		Provides a simple socket connection using WebSocket, or alternate
	// 		communication mechanisms in legacy browsers for comet-style communication. This is based
	//		on the WebSocket API and returns an object that implements the WebSocket interface:
	//		http://dev.w3.org/html5/websockets/#websocket
	//	description:
	//		Provides socket connections. This can be used with virtually any Comet protocol.
	//	argsOrUrl:
	//		This uses the same arguments as the other I/O functions in Dojo, or a
	// 		URL to connect to. The URL should be a relative URL in order to properly
	//		work with WebSockets (it can still be host relative, like //other-site.org/endpoint)
	// returns:
	// 		An object that implements the WebSocket API
	// example:
	//		| dojo.require("dojox.socket");
	//		| var socket = dojox.socket({"//comet-server/comet");
	//		| // we could also add auto-reconnect support
	//		| // now we can connect to standard HTML5 WebSocket-style events
	//		| dojo.connect(socket, "onmessage", function(event){
	//		|    var message = event.data;
	//		|    // do something with the message
	//		| });
	//		| // send something
	//		| socket.send("hi there");
	//		| whenDone(function(){
	//		|   socket.close();
	//		| });
	//		You can also use the Reconnect module:
	//		| dojo.require("dojox.socket");
	//		| dojo.require("dojox.socket.Reconnect");
	//		| var socket = dojox.socket({url:"/comet"});
	//		| // add auto-reconnect support
	//		| socket = dojox.socket.Reconnect(socket);
	if(typeof argsOrUrl == "string"){
		argsOrUrl = {url: argsOrUrl};
	}
	return window.WebSocket ? dojox.socket.WebSocket(argsOrUrl) : dojox.socket.LongPoll(argsOrUrl);
};

dojox.socket.WebSocket = function(args){
	// summary:
	//		A wrapper for WebSocket, than handles standard args and relative URLs
	var ws = new WebSocket(new dojo._Url(document.baseURI.replace(/^http/i,'ws'), args.url));
	ws.on = ws.addEventListener;
	return ws;
};

dojox.socket.LongPoll = function(/*dojo.__XhrArgs*/ args){
	// summary:
	//		Provides a simple long-poll based comet-style socket/connection to a server and returns an
	// 		object implementing the WebSocket interface:
	//		http://dev.w3.org/html5/websockets/#websocket
	//	args:
	//		This uses the same arguments as the other I/O functions in Dojo, with this addition:
	//	args.interval:
	//		Indicates the amount of time (in milliseconds) after a response was received
	//		before another request is made. By default, a request is made immediately
	//		after getting a response. The interval can be increased to reduce load on the
	//		server or to do simple time-based polling where the server always responds
	// 		immediately.
	//	args.transport:
	//		Provide an alternate transport like dojo.io.script.get
	// returns:
	// 		An object that implements the WebSocket API
	// example:
	//		| dojo.require("dojox.socket.LongPoll");
	//		| var socket = dojox.socket.LongPoll({url:"/comet"});
	//		or:
	//		| dojo.require("dojox.socket.LongPoll");
	//		| dojox.socket.LongPoll.add();
	//		| var socket = dojox.socket({url:"/comet"});

var cancelled = false,
		first = true,
		timeoutId,
		connections = [];

	// create the socket object
	var socket = {
		send: function(data){
			// summary:
			// 		Send some data using XHR or provided transport
			var sendArgs = dojo.delegate(args);
			sendArgs.rawBody = data;
			clearTimeout(timeoutId);
			var deferred = first ? (first = false) || socket.firstRequest(sendArgs) :
				socket.transport(sendArgs);
			connections.push(deferred);
			deferred.then(function(response){
				// got a response
				socket.readyState = 1;
				// remove the current connection
				connections.splice(dojo.indexOf(connections, deferred), 1);
				// reconnect to listen for the next message if there are no active connections,
				// we queue it up in case one of the onmessage handlers has a message to send
				if(!connections.length){
					timeoutId = setTimeout(connect, args.interval);
				}
				if(response){
					// now send the message along to listeners
					fire("message", {data: response}, deferred);
				}
			}, function(error){
				connections.splice(dojo.indexOf(connections, deferred), 1);
				// an error occurred, fire the appropriate event listeners
				if(!cancelled){
					fire("error", {error:error}, deferred);
					if(!connections.length){
						socket.readyState = 3;
						fire("close", {wasClean:false}, deferred);
					}
				}
			});
			return deferred;
		},
		close: function(){
			// summary:
			// 		Close the connection
			socket.readyState = 2;
			cancelled = true;
			for(var i = 0; i < connections.length; i++){
				connections[i].cancel();
			}
			socket.readyState = 3;
			fire("close", {wasClean:true});
		},
		transport: args.transport || dojo.xhrPost,
		args: args,
		url: args.url,
		readyState: 0,
		CONNECTING: 0,
		OPEN: 1,
		CLOSING: 2,
		CLOSED: 3,
		dispatchEvent: function(event){
			fire(event.type, event);
		},
		on: function(type, callback){
			return dojo.connect(this, "on" + type, callback);
		},
		firstRequest: function(args){
			// summary:
			// 		This allows for special handling for the first request. This is useful for
			//		providing information to disambiguate between the first request and
			//		subsequent long-poll requests so the server can properly setup a
			// 		connection on the first connection or reject a request for an expired
			// 		connection if the request is not expecting to be the first for a connection.
			//		This method can be overriden. The default behavior is to include a Pragma
			//		header with a value of "start-long-poll"
			var headers = (args.headers || (args.headers = {}));
			headers.Pragma = "start-long-poll";
			try{
				return this.transport(args);
			}finally{
				// cleanup the header so it is not used on subsequent requests
				delete headers.Pragma;
			}
		}
	};
	function connect(){
		if(socket.readyState == 0){
			// we fire the open event now because we really don't know when the "socket"
			// is truly open, and this gives us a to do a send() and get it included in the
			// HTTP request
			fire("open",{});
		}
		// make the long-poll connection, to wait for response from the server
		if(!connections.length){
			socket.send();
		}
	}
	function fire(event, object, deferred){
		if(socket["on" + event]){
			object.type = event;
			object.ioArgs = deferred && deferred.ioArgs;
			socket["on" + event](object);
		}
	}
	// provide an alias for Dojo's connect method
	socket.connect = socket.on;
	// do the initial connection
	setTimeout(connect);
	return socket;
};
<|endoftext|>


<commit_before>// Mast.Socket wraps around socket.io to provide a universal API
// for programatic communication with the Sails backend
Mast.Socket =_.extend(
{
	// Local reference to Socket.io object
	io: window.io,

	// The base url of the application
	baseurl: window.location.origin,

	// Whether to connect automatically
	autoconnect: true,

	// Map of entities and actions, by uri
	routes: {},

	// Override backbone.sync when Socket object is instantiated
	initialize: function() {
		_.bindAll(this);
		this.autoconnect && this.connect();
		Backbone.sync = function(method, model, options) {						// Override Backbone.sync
			switch (method) {													// (reference: http://documentcloud.github.com/backbone/docs/backbone-localstorage.html)
				case "read":
					model.id ?
						Mast.Socket.find(model,options) :
						Mast.Socket.findAll(model,options);
					break;
				case "create":
					Mast.Socket.create(model,options);
					break;
				case "update":
					Mast.Socket.update(model,options);
					break;
				case "delete":
					Mast.Socket['delete'](model,options);
					break;
			}
		};
	},

	// Connect to socket
	connect: function(baseurl) {
		if (!this.io) {
			throw new Error(
			"Can't connect to socket because the Socket.io "+
			"client library (io) cannot be found!"
			);
		}
		if (this.connected) {
			throw new Error(
				"Can't connect to "+baseurl+ " because you're "+
				"already connected to a socket @ " + this.baseurl+"!"
				);
		}
		this.baseurl = baseurl || this.baseurl;
		this._socket = this.io.connect(this.baseurl);
		Mast.Socket._socket.on('sessionUpdated',function(data) {				// Listen for latest session data from server and update local store
			Mast.Session = data;
		});
		Mast.Socket._socket.on('message',function(cometMessage) {				// Route server-sent comet events
			if (cometMessage.uri) {
				Mast.Socket.route(cometMessage.uri,_.clone(cometMessage.data));
			}
			else {
				debug.warn('Unknown message received from server.',cometMessage);
				throw new Error('Unknown message received from server.');
			}
		});
		this.connected = true;
	},

	// Comet route handler
	route: function (serverUri,serverData) {
		var extractParams = Backbone.Router.prototype._extractParameters,
			calculateRegex = Backbone.Router.prototype._routeToRegExp;
		_.each(Mast.Socket.routes,function(instances,routeUri) {
			var regex=calculateRegex(routeUri);
			if (serverUri.match(regex)) {
				var params=extractParams(regex,serverUri);
				_.each(instances,function(instance,index) {
					params.push(serverData);
					instance.action.apply(instance.context,params);
				});
			}
		});
	},

	// Subscribe a client-side handler action to a server-sent event
	subscribe: function (routeUri,action,context) {
		if (!Mast.Socket.routes[routeUri]) {
			Mast.Socket.routes[routeUri] = [];
		}
		Mast.Socket.routes[routeUri].push({
			action: action,														// The function to trigger
			context: context													// Component where the logic will run
		})
	},

	// CRUD methods for Backbone usage
	create: function(model,options){
		var url = (model.url() || model.collection.url) + "/create";
		this.post(url,model.toJSON(),function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	find: function(model,options){
		url = model.url().replace(/\/*$/,'');									// Remove trailing slash and add /update to url
		var id = +(url.match(/(\/[^\/]+)$/)[0].replace(/[^0-9]/,''));
		url = url.replace(/(\/[^\/]+)$/,'/find');
		var data = _.extend({id:id},model.toJSON());							// Add id to data params

		this.get(url, data, function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	findAll: function(collection,options){
		var url = (collection.url) + "/findAll";
		this.get(url, model.toJSON(), function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	update: function(model,options){
		var url = model.url().replace(/\/*$/,'');								// Remove trailing slash and add /update to url
		var id = +(url.match(/(\/[^\/]+)$/)[0].replace(/[^0-9]/,''));
		url = url.replace(/(\/[^\/]+)$/,'/update');
		var data = _.extend({id:id},model.toJSON());							// Add id to data params

		this.put(url, data, function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	'delete': function(model,options){
		var url = model.url().replace(/\/*$/,'');								// Remove trailing slash and add /destroy to url
		var id = +(url.match(/(\/[^\/]+)$/)[0].replace(/[^0-9]/,''));
		url = url.replace(/(\/[^\/]+)$/,'/destroy');
		var data = _.extend({id:id},model.toJSON());							// Add id to data params

		this['delete'](url,data,function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	get: function (url,data,options) { this.request(url,data,options,'get') },
	post: function (url,data,options) { this.request(url,data,options,'post') },
	put: function (url,data,options) { this.request(url,data,options,'put') },
	'delete': function (url,data,options) { this.request(url,data,options,'delete') },

	// Simulate an HTTP request to the backend
	request: function (url,data,options, method) {
		// Remove trailing slash
		url = url.replace(/\/*$/,'');

		this._send('message',{
			url: url,
			data: data,
			method: method || 'get'
		},function (parsedResult) {
			options && ((_.isFunction(options) ?
				options :														// If options is a function, treat it as a callback
				options.success) (parsedResult));								// Otherwise the "success" property will be treated as the callback
		});
	},

	/**
	 * Make a call to the server over the socket
	 *   label:		the request label
	 *   params:	data to pass with the request
	 *   callback:	optional callback fired when server responds
	 */
	_send: function (label,params,callback) {
		Mast.Socket._socket.emit(label,JSON.stringify(params),function(result) {
			try {
				var parsedResult = JSON.parse(result);
			}
			catch (e) {
				throw new Error("Server response could not be parsed:",result,e);
			}
			callback && callback(parsedResult);									// Call success callback if specified
		})
	}
},Backbone.Events)<commit_msg>Cleaned up documentation and api.<commit_after>// Mast.Socket wraps around socket.io to provide a universal API
// for programatic communication with the Sails backend
Mast.Socket =_.extend(
{
	// Local reference to Socket.io object
	io: window.io,

	// The base url of the application
	baseurl: window.location.origin,

	// Whether to connect automatically
	autoconnect: true,

	// Map of entities and actions, by uri
	routes: {},

	// Override backbone.sync when Socket object is instantiated
	initialize: function() {
		_.bindAll(this);
		this.autoconnect && this.connect();
		Backbone.sync = function(method, model, options) {						// Override Backbone.sync
			switch (method) {													// (reference: http://documentcloud.github.com/backbone/docs/backbone-localstorage.html)
				case "read":
					model.id ?
						Mast.Socket.find(model,options) :
						Mast.Socket.findAll(model,options);
					break;
				case "create":
					Mast.Socket.create(model,options);
					break;
				case "update":
					Mast.Socket.update(model,options);
					break;
				case "delete":
					Mast.Socket['delete'](model,options);
					break;
			}
		};
	},

	// Connect to socket
	connect: function(baseurl) {
		if (!this.io) {
			throw new Error(
			"Can't connect to socket because the Socket.io "+
			"client library (io) cannot be found!"
			);
		}
		if (this.connected) {
			throw new Error(
				"Can't connect to "+baseurl+ " because you're "+
				"already connected to a socket @ " + this.baseurl+"!"
				);
		}
		this.baseurl = baseurl || this.baseurl;
		this._socket = this.io.connect(this.baseurl);
		Mast.Socket._socket.on('sessionUpdated',function(data) {				// Listen for latest session data from server and update local store
			Mast.Session = data;
		});
		Mast.Socket._socket.on('message',function(cometMessage) {				// Route server-sent comet events
			if (cometMessage.uri) {
				Mast.Socket.route(cometMessage.uri,_.clone(cometMessage.data));
			}
			else {
				debug.warn('Unknown message received from server.',cometMessage);
				throw new Error('Unknown message received from server.');
			}
		});
		this.connected = true;
	},

	// Comet route handler
	route: function (serverUri,serverData) {
		var extractParams = Backbone.Router.prototype._extractParameters,
			calculateRegex = Backbone.Router.prototype._routeToRegExp;
		_.each(Mast.Socket.routes,function(instances,routeUri) {
			var regex=calculateRegex(routeUri);
			if (serverUri.match(regex)) {
				var params=extractParams(regex,serverUri);
				_.each(instances,function(instance,index) {
					params.push(serverData);
					instance.action.apply(instance.context,params);
				});
			}
		});
	},

	// Subscribe a client-side handler action to a server-sent event
	subscribe: function (routeUri,action,context) {
		if (!Mast.Socket.routes[routeUri]) {
			Mast.Socket.routes[routeUri] = [];
		}
		Mast.Socket.routes[routeUri].push({
			action: action,														// The function to trigger
			context: context													// Component where the logic will run
		})
	},

	// CRUD methods for Backbone usage
	create: function(model,options){
		var url = (model.url() || model.collection.url) + "/create";
		this.post(url,model.toJSON(),function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	find: function(model,options){
		url = model.url().replace(/\/*$/,'');									// Remove trailing slash and add /update to url
		var id = +(url.match(/(\/[^\/]+)$/)[0].replace(/[^0-9]/,''));
		url = url.replace(/(\/[^\/]+)$/,'/find');
		var data = _.extend({id:id},model.toJSON());							// Add id to data params

		this.get(url, data, function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	findAll: function(collection,options){
		var url = (collection.url) + "/findAll";
		this.get(url, model.toJSON(), function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	update: function(model,options){
		var url = model.url().replace(/\/*$/,'');								// Remove trailing slash and add /update to url
		var id = +(url.match(/(\/[^\/]+)$/)[0].replace(/[^0-9]/,''));
		url = url.replace(/(\/[^\/]+)$/,'/update');
		var data = _.extend({id:id},model.toJSON());							// Add id to data params

		this.put(url, data, function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	'delete': function(model,options){
		var url = model.url().replace(/\/*$/,'');								// Remove trailing slash and add /destroy to url
		var id = +(url.match(/(\/[^\/]+)$/)[0].replace(/[^0-9]/,''));
		url = url.replace(/(\/[^\/]+)$/,'/destroy');
		var data = _.extend({id:id},model.toJSON());							// Add id to data params

		this['delete'](url,data,function (parsedResult) {
			options && options.success && options.success(parsedResult);
		});
	},

	get: function (url,data,options) { this.request(url,data,options,'get') },
	post: function (url,data,options) { this.request(url,data,options,'post') },
	put: function (url,data,options) { this.request(url,data,options,'put') },
	'delete': function (url,data,options) { this.request(url,data,options,'delete') },

	// Simulate an HTTP request to the backend
	request: function (url,data,options, method) {
		url = url.replace(/\/*$/,'');											// Remove trailing slash
		this._send('message',{
			url: url,
			data: data,
			method: method || 'get'
		},function (parsedResult) {
			options && ((_.isFunction(options) ?
				options :														// If options is a function, treat it as a callback
				options.success) (parsedResult));								// Otherwise the "success" property will be treated as the callback
		});
	},

	/**
	 * Make a call to the server over the socket
	 *   label:		the request label
	 *   params:	data to pass with the request
	 *   callback:	optional callback fired when server responds
	 */
	_send: function (label,params,callback) {
		Mast.Socket._socket.emit(label,JSON.stringify(params),function(result) {
			try {
				var parsedResult = JSON.parse(result);
			}
			catch (e) {
				throw new Error("Server response could not be parsed:",result,e);
			}
			callback && callback(parsedResult);									// Call success callback if specified
		})
	}
},Backbone.Events)<|endoftext|>