<commit_before>require("sugar");

var async = require("async"),
    fs = require("fs"),
    path = require("path"),
    Table = require("./table"),
    LineReader = require('file-line-reader');

module.exports = function(connection) {

    var me = this;

    this.connection = connection;

    this.tables = { };

    this.definition = { };

    ////////////////////////////////////////////////////////////////////////////////////////////
    // TABLE DEFINITION METHODS
    ////////////////////////////////////////////////////////////////////////////////////////////
    this.listSomeTables = function(last, cb) {
        var options = { };

        if (cb == null && Object.isFunction(last)) {
            cb = last;
            last = null;
        }

        if (last) {
            options.ExclusiveStartTableName = last;
        }

        me.connection.listTables(options, function(err, data) {
            if (err) cb(err);
            else cb(null, { tables: data.TableNames, last: data.LastEvaluatedTableName });
        });
    };

    this.listAllTables = function(cb) {
        var done = false,
            last = null,
            tables = [ ];

        async.whilst(
            function() { return !done; },
            function(cb) {
                me.listSomeTables(last, function(err, data) {
                    if (err) cb(err);
                    else {
                        if (!data.last) done = true;
                        else last = data.last;

                        tables.add(data.tables);
                        cb();
                    }
                });
            },
            function(err) {
                cb(err, tables);
            }
        );
    };

    this.createTable = function(definition, cb) {
        var options = {
            TableName: definition.name,
            ProvisionedThroughput: {
                ReadCapacityUnits: definition.read || 5,
                WriteCapacityUnits: definition.write || 5
            }
        };

        if (!definition.name) {
            cb(new Error("No table name specified."));
            return;
        }

        try {
            var columns = { },
                hash = null;

            if (definition.key) {
                var cols = Object.keys(definition.key);
                hash = cols[0];
                options.KeySchema = [ { AttributeName: hash, KeyType: "HASH" } ];
                if (cols.length > 1) {
                    options.KeySchema.push({ AttributeName: cols[1], KeyType: "RANGE" });
                }

                columns = Object.merge(columns, definition.key);
            }
            else {
                cb(new Error("No primary key is defined."));
                return;
            }

            if (definition.sorts) {
                options.LocalSecondaryIndexes = Object.keys(definition.sorts).map(function(name) {
                    var index = definition.sorts[name],
                        indexOptions = {
                            IndexName: name,
                            KeySchema: [ options.KeySchema.first() ]
                        };

                    var cols = Object.keys(index.columns);
                    indexOptions.KeySchema.push({ AttributeName: cols[0], KeyType: "RANGE" });

                    if (Array.isArray(index.project)) {
                        indexOptions.Projection = { NonKeyAttributes: index.project, ProjectionType: "INCLUDE" };
                    }
                    else if (index.project) {
                        indexOptions.Projection = { ProjectionType: index.project };
                    }
                    else {
                        indexOptions.Projection = { ProjectionType: "KEYS_ONLY" };
                    }

                    columns = Object.merge(columns, index.columns);

                    return indexOptions;
                });
            }

            if (definition.indices) {
                options.GlobalSecondaryIndexes = Object.keys(definition.indices).map(function(name) {
                    var index = definition.indices[name],
                        indexOptions = {
                            IndexName: name,
                            ProvisionedThroughput: {
                                ReadCapacityUnits: index.read || 5,
                                WriteCapacityUnits: index.write || 5
                            }
                        };

                    var cols = Object.keys(index.columns);
                    indexOptions.KeySchema = [ { AttributeName: cols[0], KeyType: "HASH" } ];
                    if (cols.length > 1) {
                        indexOptions.KeySchema.push({ AttributeName: cols[1], KeyType: "RANGE" });
                    }

                    if (Array.isArray(index.project)) {
                        indexOptions.Projection = { NonKeyAttributes: index.project, ProjectionType: "INCLUDE" };
                    }
                    else if (index.project) {
                        indexOptions.Projection = { ProjectionType: index.project };
                    }
                    else {
                        indexOptions.Projection = { ProjectionType: "KEYS_ONLY" };
                    }

                    columns = Object.merge(columns, index.columns);

                    return indexOptions;
                });
            }

            options.AttributeDefinitions = Object.keys(columns).map(function(name) {
                return { AttributeName: name, AttributeType: dataTypes[columns[name]] };
            });
        }
        catch (ex) {
            cb(ex);
            return;
        }

        me.connection.createTable(options, function(err, data) {
            if (err) cb(err);
            else if (data != null) {
                var status = data.TableDescription.TableStatus;
                if (status == "CREATING") {
                    me.connection.waitFor('tableExists', { TableName: definition.name }, function(err, data) {
                        cb(null, true);
                    });
                }
                else if (status == "ACTIVE") {
                    cb();
                }
                else {
                    cb(new Error(name + " status is " + status));
                }
            }
            else cb();
        });
    };

    this.deleteTable = function(table, cb) {
        me.connection.deleteTable({
            TableName: table
        }, function(err) {
            if (err) {
                cb();
            }
            else {
                var interval = setInterval(function() {
                    me.describeTable(table, function(err, data) {
                        if (err) {
                            clearInterval(interval);
                            cb();
                        }
                    });
                }, 2500);
            }
        });
    };

    this.describeTable = function(table, cb) {
        me.connection.describeTable({
            TableName: table
        }, cb);
    };

    this.changeThroughput = function(table, read, write, cb) {
        me.connection.updateTable({
            TableName: table,
            ProvisionedThroughput: {
                ReadCapacityUnits: read,
                WriteCapacityUnits: write
            }
        }, cb);
    };

    this.changeIndexThroughput = function(table, index, read, write, cb) {
        me.connection.updateTable({
            TableName: table,
            GlobalSecondaryIndexUpdates: [
                {
                    Update: {
                        IndexName: table,
                        ProvisionedThroughput: {
                            ReadCapacityUnits: read,
                            WriteCapacityUnits: write
                        }
                    }
                }
            ]
        }, cb);
    };

    this.factorThroughput = function(description, factor, cb) {
        var update = {
            TableName: description.TableName,
            ProvisionedThroughput: {
                ReadCapacityUnits: Math.round(factor * description.ProvisionedThroughput.ReadCapacityUnits),
                WriteCapacityUnits: Math.round(factor * description.ProvisionedThroughput.WriteCapacityUnits)
            }
        };

        if (description.GlobalSecondaryIndexes.length) {
            update.GlobalSecondaryIndexUpdates = [ ];
            description.GlobalSecondaryIndexes.forEach(function(index) {
                update.GlobalSecondaryIndexUpdates.push({
                    Update: {
                        IndexName: index.IndexName,
                        ProvisionedThroughput: {
                            ReadCapacityUnits: Math.round(factor * index.ProvisionedThroughput.ReadCapacityUnits),
                            WriteCapacityUnits: Math.round(factor * index.ProvisionedThroughput.WriteCapacityUnits)
                        }
                    }
                });
            });
        }

        me.connection.updateTable(update, cb);
    };

    ////////////////////////////////////////////////////////////////////////////////////////////
    // SCHEMA MANAGEMENT METHODS
    ////////////////////////////////////////////////////////////////////////////////////////////
    this.load = function(filter, cb) {
        if (cb == null && Object.isFunction(filter)) {
            cb = filter;
            filter = null;
        }

        me.listAllTables(function(err, tables) {
            if (err) cb(err);
            else {
                var candidates = null;
                if (filter) {
                    if (Object.isFunction(filter) || Object.isRegExp(filter)) {
                        tables = tables.filter(filter);
                    }
                    else {
                        candidates = { };
                        Object.keys(filter).forEach((key) => {
                            candidates[(filter[key].name || filter[key])] = key;
                        });

                        tables = tables.filter((t) => { return candidates[t] != null; });
                    }
                }

                if (tables.length) {
                    async.forEachSeries(tables, function(table, cb) {
                        me.describeTable(table, function(err, metadata) {
                            if (err) cb(err);
                            else {
                                var name = candidates ? candidates[table] : table;
                                me.tables[name] = new Table(me, metadata);
                                cb();
                            }
                        });
                    }, cb);
                }
                else cb();
            }
        });
    };

    this.define = function(definition) {
        me.definition = definition;
        return me;
    };

    this.require = function(filepath, options) {
        var stat = fs.lstatSync(filepath);
        if (stat.isDirectory()) {
            fs.readDirSync(filepath).filter(/.*js/i).forEach(function(file) {
                var tableName = path.basename(file, '.js'),
                    table = require(path.join(filepath, file));

                if (Object.isFunction(table)) {
                    table = table(options || {});
                }

                me.definition[tableName] = table;
            });
        }
        else {
            var tableName = path.basename(filepath, '.js'),
                table = require(filepath);

            if (Object.isFunction(table)) {
                table = table(options || {});
            }

            me.definition[tableName] = table;
        }

        return me;
    };

    this.create = function(options, cb) {
        if (cb == null && Object.isFunction(options)) {
            cb = options;
            options = { };
        }

        options.prefix = options.prefix || "";
        Object.values(me.definition).forEach(function(table) {
            table.name = options.prefix + table.name;

            if (options.minReadCapacity) table.read = Math.max(table.read || 5, options.minReadCapacity);
            if (options.minWriteCapacity) table.write = Math.max(table.write || 5, options.minWriteCapacity);

            if (table.indices) {
                Object.values(table.indices).forEach(function(index) {
                    if (options.minReadCapacity) index.read = Math.max(index.read || 5, options.minReadCapacity);
                    if (options.minWriteCapacity) index.write = Math.max(index.write || 5, options.minWriteCapacity);
                });
            }
        });

        me.load(me.definition, function(err) {
            if (err) cb(err);
            else {
                async.forEachSeries(Object.keys(me.definition), function(table, cb) {
                    if (me.tables[table] == null) {
                        me.createTable(
                            me.definition[table],
                            function(err) {
                                if (err) cb(err);
                                else {
                                    me.describeTable(me.definition[table].name, function(err, metadata) {
                                        if (err) cb(err);
                                        else {
                                            me.tables[table] = new Table(me, metadata);

                                            var Methods = me.definition[table].methods;
                                            if (Methods) {
                                                var methods = new Methods(me.tables[table]);
                                                for (var p in methods) {
                                                    if (Object.isFunction(methods[p]) && me.tables[table][p] == null) {
                                                        me.tables[table][p] = methods[p];
                                                    }
                                                }
                                            }

                                            cb();
                                        }
                                    });
                                }
                            }
                        );
                    }
                    else {
                        cb();
                    }
                }, cb);
            }
        });
    };

    this.drop = function(cb) {
        var tables = Object.values(me.definition).map("name").union(Object.values(me.tables).map("name"));
        async.forEachSeries(tables, function(table, cb) {
            me.deleteTable(table, cb);
        }, cb);
    };

    ////////////////////////////////////////////////////////////////////////////////////////////
    // FILE STORAGE METHODS
    ////////////////////////////////////////////////////////////////////////////////////////////
    this.backup = function(dir, cb) {
        fs.lstat(dir, function(err, stat) {
            var isDirectory = (!err && stat.isDirectory());
            if (!isDirectory) {
                cb(new Error(dir + " does not exists as a directory."));
            }
            else {
                async.forEachSeries(Object.values(me.tables), function(table, cb) {
                    table.backup(path.join(dir, table.name + ".json"), cb);
                }, cb);
            }
        });
    };

    this.restore = function(dir, cb) {
        fs.lstat(dir, function(err, stat) {
            var isDirectory = (!err && stat.isDirectory());
            if (!isDirectory) {
                cb(new Error(dir + " does not exists as a directory."));
            }
            else {
                async.forEachSeries(Object.values(me.tables), function(table, cb) {
                    table.restore(path.join(dir, table.name + ".json"), cb);
                }, cb);
            }
        });
    };

};

const dataTypes = exports.dataTypes = {

    /* String data type */
    "S": "S",
    "string": "S",
    "text": "S",
    "SS": "SS",
    "string set": "SS",
    "text set": "SS",

    /* Numeric data type */
    "N": "N",
    "number": "N",
    "NS": "NS",
    "number set": "NS",
    "numeric set": "NS",

    /* Binary data type */
    "B": "B",
    "binary": "B",
    "buffer": "B",
    "bytes": "B",
    "BS": "BS",
    "binary set": "BS",
    "buffer set": "BS",
    "bytes set": "BS",

    /* Boolean data type */
    "BOOL": "BOOL",
    "bool": "BOOL",
    "boolean": "boolean",

    /* List data type */
    "L": "L",
    "list": "L",
    "array": "L",

    /* Map data type */
    "M": "M",
    "map": "M",
    "object": "M",
    "document": "M",

    /* Null data type */
    "NULL": "NULL",
    "null": "NULL"

};<commit_msg>Rearrange requires<commit_after>require("sugar");

var async = require("async"),
    fs = require("fs"),
    path = require("path"),
    LineReader = require('file-line-reader'),
    Table = require("./table");

module.exports = function(connection) {

    var me = this;

    this.connection = connection;

    this.tables = { };

    this.definition = { };

    ////////////////////////////////////////////////////////////////////////////////////////////
    // TABLE DEFINITION METHODS
    ////////////////////////////////////////////////////////////////////////////////////////////
    this.listSomeTables = function(last, cb) {
        var options = { };

        if (cb == null && Object.isFunction(last)) {
            cb = last;
            last = null;
        }

        if (last) {
            options.ExclusiveStartTableName = last;
        }

        me.connection.listTables(options, function(err, data) {
            if (err) cb(err);
            else cb(null, { tables: data.TableNames, last: data.LastEvaluatedTableName });
        });
    };

    this.listAllTables = function(cb) {
        var done = false,
            last = null,
            tables = [ ];

        async.whilst(
            function() { return !done; },
            function(cb) {
                me.listSomeTables(last, function(err, data) {
                    if (err) cb(err);
                    else {
                        if (!data.last) done = true;
                        else last = data.last;

                        tables.add(data.tables);
                        cb();
                    }
                });
            },
            function(err) {
                cb(err, tables);
            }
        );
    };

    this.createTable = function(definition, cb) {
        var options = {
            TableName: definition.name,
            ProvisionedThroughput: {
                ReadCapacityUnits: definition.read || 5,
                WriteCapacityUnits: definition.write || 5
            }
        };

        if (!definition.name) {
            cb(new Error("No table name specified."));
            return;
        }

        try {
            var columns = { },
                hash = null;

            if (definition.key) {
                var cols = Object.keys(definition.key);
                hash = cols[0];
                options.KeySchema = [ { AttributeName: hash, KeyType: "HASH" } ];
                if (cols.length > 1) {
                    options.KeySchema.push({ AttributeName: cols[1], KeyType: "RANGE" });
                }

                columns = Object.merge(columns, definition.key);
            }
            else {
                cb(new Error("No primary key is defined."));
                return;
            }

            if (definition.sorts) {
                options.LocalSecondaryIndexes = Object.keys(definition.sorts).map(function(name) {
                    var index = definition.sorts[name],
                        indexOptions = {
                            IndexName: name,
                            KeySchema: [ options.KeySchema.first() ]
                        };

                    var cols = Object.keys(index.columns);
                    indexOptions.KeySchema.push({ AttributeName: cols[0], KeyType: "RANGE" });

                    if (Array.isArray(index.project)) {
                        indexOptions.Projection = { NonKeyAttributes: index.project, ProjectionType: "INCLUDE" };
                    }
                    else if (index.project) {
                        indexOptions.Projection = { ProjectionType: index.project };
                    }
                    else {
                        indexOptions.Projection = { ProjectionType: "KEYS_ONLY" };
                    }

                    columns = Object.merge(columns, index.columns);

                    return indexOptions;
                });
            }

            if (definition.indices) {
                options.GlobalSecondaryIndexes = Object.keys(definition.indices).map(function(name) {
                    var index = definition.indices[name],
                        indexOptions = {
                            IndexName: name,
                            ProvisionedThroughput: {
                                ReadCapacityUnits: index.read || 5,
                                WriteCapacityUnits: index.write || 5
                            }
                        };

                    var cols = Object.keys(index.columns);
                    indexOptions.KeySchema = [ { AttributeName: cols[0], KeyType: "HASH" } ];
                    if (cols.length > 1) {
                        indexOptions.KeySchema.push({ AttributeName: cols[1], KeyType: "RANGE" });
                    }

                    if (Array.isArray(index.project)) {
                        indexOptions.Projection = { NonKeyAttributes: index.project, ProjectionType: "INCLUDE" };
                    }
                    else if (index.project) {
                        indexOptions.Projection = { ProjectionType: index.project };
                    }
                    else {
                        indexOptions.Projection = { ProjectionType: "KEYS_ONLY" };
                    }

                    columns = Object.merge(columns, index.columns);

                    return indexOptions;
                });
            }

            options.AttributeDefinitions = Object.keys(columns).map(function(name) {
                return { AttributeName: name, AttributeType: dataTypes[columns[name]] };
            });
        }
        catch (ex) {
            cb(ex);
            return;
        }

        me.connection.createTable(options, function(err, data) {
            if (err) cb(err);
            else if (data != null) {
                var status = data.TableDescription.TableStatus;
                if (status == "CREATING") {
                    me.connection.waitFor('tableExists', { TableName: definition.name }, function(err, data) {
                        cb(null, true);
                    });
                }
                else if (status == "ACTIVE") {
                    cb();
                }
                else {
                    cb(new Error(name + " status is " + status));
                }
            }
            else cb();
        });
    };

    this.deleteTable = function(table, cb) {
        me.connection.deleteTable({
            TableName: table
        }, function(err) {
            if (err) {
                cb();
            }
            else {
                var interval = setInterval(function() {
                    me.describeTable(table, function(err, data) {
                        if (err) {
                            clearInterval(interval);
                            cb();
                        }
                    });
                }, 2500);
            }
        });
    };

    this.describeTable = function(table, cb) {
        me.connection.describeTable({
            TableName: table
        }, cb);
    };

    this.changeThroughput = function(table, read, write, cb) {
        me.connection.updateTable({
            TableName: table,
            ProvisionedThroughput: {
                ReadCapacityUnits: read,
                WriteCapacityUnits: write
            }
        }, cb);
    };

    this.changeIndexThroughput = function(table, index, read, write, cb) {
        me.connection.updateTable({
            TableName: table,
            GlobalSecondaryIndexUpdates: [
                {
                    Update: {
                        IndexName: table,
                        ProvisionedThroughput: {
                            ReadCapacityUnits: read,
                            WriteCapacityUnits: write
                        }
                    }
                }
            ]
        }, cb);
    };

    this.factorThroughput = function(description, factor, cb) {
        var update = {
            TableName: description.TableName,
            ProvisionedThroughput: {
                ReadCapacityUnits: Math.round(factor * description.ProvisionedThroughput.ReadCapacityUnits),
                WriteCapacityUnits: Math.round(factor * description.ProvisionedThroughput.WriteCapacityUnits)
            }
        };

        if (description.GlobalSecondaryIndexes.length) {
            update.GlobalSecondaryIndexUpdates = [ ];
            description.GlobalSecondaryIndexes.forEach(function(index) {
                update.GlobalSecondaryIndexUpdates.push({
                    Update: {
                        IndexName: index.IndexName,
                        ProvisionedThroughput: {
                            ReadCapacityUnits: Math.round(factor * index.ProvisionedThroughput.ReadCapacityUnits),
                            WriteCapacityUnits: Math.round(factor * index.ProvisionedThroughput.WriteCapacityUnits)
                        }
                    }
                });
            });
        }

        me.connection.updateTable(update, cb);
    };

    ////////////////////////////////////////////////////////////////////////////////////////////
    // SCHEMA MANAGEMENT METHODS
    ////////////////////////////////////////////////////////////////////////////////////////////
    this.load = function(filter, cb) {
        if (cb == null && Object.isFunction(filter)) {
            cb = filter;
            filter = null;
        }

        me.listAllTables(function(err, tables) {
            if (err) cb(err);
            else {
                var candidates = null;
                if (filter) {
                    if (Object.isFunction(filter) || Object.isRegExp(filter)) {
                        tables = tables.filter(filter);
                    }
                    else {
                        candidates = { };
                        Object.keys(filter).forEach((key) => {
                            candidates[(filter[key].name || filter[key])] = key;
                        });

                        tables = tables.filter((t) => { return candidates[t] != null; });
                    }
                }

                if (tables.length) {
                    async.forEachSeries(tables, function(table, cb) {
                        me.describeTable(table, function(err, metadata) {
                            if (err) cb(err);
                            else {
                                var name = candidates ? candidates[table] : table;
                                me.tables[name] = new Table(me, metadata);
                                cb();
                            }
                        });
                    }, cb);
                }
                else cb();
            }
        });
    };

    this.define = function(definition) {
        me.definition = definition;
        return me;
    };

    this.require = function(filepath, options) {
        var stat = fs.lstatSync(filepath);
        if (stat.isDirectory()) {
            fs.readDirSync(filepath).filter(/.*js/i).forEach(function(file) {
                var tableName = path.basename(file, '.js'),
                    table = require(path.join(filepath, file));

                if (Object.isFunction(table)) {
                    table = table(options || {});
                }

                me.definition[tableName] = table;
            });
        }
        else {
            var tableName = path.basename(filepath, '.js'),
                table = require(filepath);

            if (Object.isFunction(table)) {
                table = table(options || {});
            }

            me.definition[tableName] = table;
        }

        return me;
    };

    this.create = function(options, cb) {
        if (cb == null && Object.isFunction(options)) {
            cb = options;
            options = { };
        }

        options.prefix = options.prefix || "";
        Object.values(me.definition).forEach(function(table) {
            table.name = options.prefix + table.name;

            if (options.minReadCapacity) table.read = Math.max(table.read || 5, options.minReadCapacity);
            if (options.minWriteCapacity) table.write = Math.max(table.write || 5, options.minWriteCapacity);

            if (table.indices) {
                Object.values(table.indices).forEach(function(index) {
                    if (options.minReadCapacity) index.read = Math.max(index.read || 5, options.minReadCapacity);
                    if (options.minWriteCapacity) index.write = Math.max(index.write || 5, options.minWriteCapacity);
                });
            }
        });

        me.load(me.definition, function(err) {
            if (err) cb(err);
            else {
                async.forEachSeries(Object.keys(me.definition), function(table, cb) {
                    if (me.tables[table] == null) {
                        me.createTable(
                            me.definition[table],
                            function(err) {
                                if (err) cb(err);
                                else {
                                    me.describeTable(me.definition[table].name, function(err, metadata) {
                                        if (err) cb(err);
                                        else {
                                            me.tables[table] = new Table(me, metadata);

                                            var Methods = me.definition[table].methods;
                                            if (Methods) {
                                                var methods = new Methods(me.tables[table]);
                                                for (var p in methods) {
                                                    if (Object.isFunction(methods[p]) && me.tables[table][p] == null) {
                                                        me.tables[table][p] = methods[p];
                                                    }
                                                }
                                            }

                                            cb();
                                        }
                                    });
                                }
                            }
                        );
                    }
                    else {
                        cb();
                    }
                }, cb);
            }
        });
    };

    this.drop = function(cb) {
        var tables = Object.values(me.definition).map("name").union(Object.values(me.tables).map("name"));
        async.forEachSeries(tables, function(table, cb) {
            me.deleteTable(table, cb);
        }, cb);
    };

    ////////////////////////////////////////////////////////////////////////////////////////////
    // FILE STORAGE METHODS
    ////////////////////////////////////////////////////////////////////////////////////////////
    this.backup = function(dir, cb) {
        fs.lstat(dir, function(err, stat) {
            var isDirectory = (!err && stat.isDirectory());
            if (!isDirectory) {
                cb(new Error(dir + " does not exists as a directory."));
            }
            else {
                async.forEachSeries(Object.values(me.tables), function(table, cb) {
                    table.backup(path.join(dir, table.name + ".json"), cb);
                }, cb);
            }
        });
    };

    this.restore = function(dir, cb) {
        fs.lstat(dir, function(err, stat) {
            var isDirectory = (!err && stat.isDirectory());
            if (!isDirectory) {
                cb(new Error(dir + " does not exists as a directory."));
            }
            else {
                async.forEachSeries(Object.values(me.tables), function(table, cb) {
                    table.restore(path.join(dir, table.name + ".json"), cb);
                }, cb);
            }
        });
    };

};

const dataTypes = exports.dataTypes = {

    /* String data type */
    "S": "S",
    "string": "S",
    "text": "S",
    "SS": "SS",
    "string set": "SS",
    "text set": "SS",

    /* Numeric data type */
    "N": "N",
    "number": "N",
    "NS": "NS",
    "number set": "NS",
    "numeric set": "NS",

    /* Binary data type */
    "B": "B",
    "binary": "B",
    "buffer": "B",
    "bytes": "B",
    "BS": "BS",
    "binary set": "BS",
    "buffer set": "BS",
    "bytes set": "BS",

    /* Boolean data type */
    "BOOL": "BOOL",
    "bool": "BOOL",
    "boolean": "boolean",

    /* List data type */
    "L": "L",
    "list": "L",
    "array": "L",

    /* Map data type */
    "M": "M",
    "map": "M",
    "object": "M",
    "document": "M",

    /* Null data type */
    "NULL": "NULL",
    "null": "NULL"

};<|endoftext|>


<commit_before>function fetch(url, successCallback, errorCallback) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);

  request.onload = function() {
    if (request.status >= 200 && request.status < 400) {
      successCallback(JSON.parse(request.responseText));
    } else {
      errorCallback();
    }
  };

  request.onerror = function(err) {
    errorCallback(err);
  };

  request.send();
}

function setOnEachFeature(feature, layer){

  layer.on({
    mouseover: function (e) {
      var layer = e.target;

      layer.setStyle({
        fillOpacity: 0.7,
      });

      if (!L.Browser.ie && !L.Browser.opera) {
        layer.bringToFront();
      }

      info.update(layer.feature.properties);
    },
    mouseout: function(e) {
      geojson.resetStyle(e.target);
      info.update();
    }
  });

  layer.bindPopup('' + feature.properties.GN + '
'
    + 'Open Data Portal: ' + feature.properties["Open Data Portal"] + '
'
    + 'Website: ' + feature.properties.Website + '
'
    + 'CKAN / DCAN: ' + feature.properties["CKAN\/DCAN"] + '
'
    + 'Maschinenlesbare Formate: ' + feature.properties["Maschinenlesbare Formate"] + '
'
    + 'Lizenz: ' + feature.properties["Lizenz"] + '
'
    + 'OGD-Metadaten Pflichtfelder: ' + feature.properties["OGD-Metadaten Pflichtfelder"] + '
'
    + 'Anbindung an Open.NRW Portal: ' + feature.properties["Anbindung Open.NRW Portal"]
    );

  info.update(layer.feature.properties);
}

function setStyleByFeature(feature) {
  console.log(feature.properties);

  var fillOpacity = 0.7;
  var color = 'green';

  if(feature.properties["Open Data Portal"] == "In Planung") {
    color = 'yellow';
  }

 if(feature.properties["GN"] == "Gelsenkirchen") {
    color = 'yellow';
 }

  if(feature.properties["Open Data Portal"] == "Nein"){
    fillOpacity = 0.5;
    color = 'red';
  }

  if(feature.properties["Open Data Portal"] == null){
    fillOpacity = 0.5;
    color = 'red';
  }

  return {
    weight: 1,
    opacity: 1,
    color: 'white',
    dashArray: '3',
    fillOpacity: fillOpacity,
    fillColor: color
  };
}

// 7.208449443900787, 51.537503853624365

var info = L.control();
var gemein = new L.LayerGroup();
var overlays = { "Open Data Verf&uumlgbarkeit": gemein};

var geojson;

var map = L.map('map', {
  zoomControl: false,
  scrollWheelZoom: false,
  doubleClickZoom: false,
  boxZoom: false
}).setView([51.52, 7.15], 10);

mapLink = 'OpenStreetMap';
usedDataLink = 'Open.NRW';

var tileLayer = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.{ext}', {
  attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap',
  subdomains: 'abcd',
  minZoom: 0,
  maxZoom: 20,
  ext: 'png'
});

tileLayer.addTo(map);

// Add legend

var legend = L.control({ position: 'bottomright' });

legend.onAdd = function (map) {
  var div = L.DomUtil.create('div', 'info legend'),
  labels = [];
  labels.push('

 ' + "Nein");
  labels.push('

 ' + "In Planung");
  labels.push('

 ' + "Ja");

  div.innerHTML = '
Open Data
' + labels.join('
');

  return div;
}

 legend.addTo(map);

// Setup Info box
info.onAdd = function (map) {
  this._div = L.DomUtil.create('div', 'info');
  this.update();
  return this._div;
};

info.update = function (props) {
  var text = '
Open Data Verf&uumlgbarkeit 2016
';

  if(props) {
    text += '' + props.GN + '
';
  } else {
    text += 'Weitere Daten werden durch
Klick angezeigt';
  }

  this._div.innerHTML = text;
};

info.addTo(map);

// Set scales
L.control.scale({
  metric: true,
  imperial: false,
  position: 'bottomleft'
}).addTo(map);

// Fetch Data and add to map
fetch('./data/map-data.json', function (data) {
  geojson = L.geoJson(data, {
    style: setStyleByFeature,
    onEachFeature: setOnEachFeature
  }).addTo(gemein);

  map.addLayer(gemein);

}, function (err) {
  console.error(err);
});
<commit_msg>Moved the extra if to the bottom<commit_after>function fetch(url, successCallback, errorCallback) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);

  request.onload = function() {
    if (request.status >= 200 && request.status < 400) {
      successCallback(JSON.parse(request.responseText));
    } else {
      errorCallback();
    }
  };

  request.onerror = function(err) {
    errorCallback(err);
  };

  request.send();
}

function setOnEachFeature(feature, layer){

  layer.on({
    mouseover: function (e) {
      var layer = e.target;

      layer.setStyle({
        fillOpacity: 0.7,
      });

      if (!L.Browser.ie && !L.Browser.opera) {
        layer.bringToFront();
      }

      info.update(layer.feature.properties);
    },
    mouseout: function(e) {
      geojson.resetStyle(e.target);
      info.update();
    }
  });

  layer.bindPopup('' + feature.properties.GN + '
'
    + 'Open Data Portal: ' + feature.properties["Open Data Portal"] + '
'
    + 'Website: ' + feature.properties.Website + '
'
    + 'CKAN / DCAN: ' + feature.properties["CKAN\/DCAN"] + '
'
    + 'Maschinenlesbare Formate: ' + feature.properties["Maschinenlesbare Formate"] + '
'
    + 'Lizenz: ' + feature.properties["Lizenz"] + '
'
    + 'OGD-Metadaten Pflichtfelder: ' + feature.properties["OGD-Metadaten Pflichtfelder"] + '
'
    + 'Anbindung an Open.NRW Portal: ' + feature.properties["Anbindung Open.NRW Portal"]
    );

  info.update(layer.feature.properties);
}

function setStyleByFeature(feature) {
  console.log(feature.properties);

  var fillOpacity = 0.7;
  var color = 'green';

  if(feature.properties["Open Data Portal"] == "In Planung") {
    color = 'yellow';
  }

  if(feature.properties["Open Data Portal"] == "Nein"){
    fillOpacity = 0.5;
    color = 'red';
  }

  if(feature.properties["Open Data Portal"] == null){
    fillOpacity = 0.5;
    color = 'red';
  }

 if(feature.properties["GN"] == "Gelsenkirchen") {
    color = 'yellow';
 }

  return {
    weight: 1,
    opacity: 1,
    color: 'white',
    dashArray: '3',
    fillOpacity: fillOpacity,
    fillColor: color
  };
}

// 7.208449443900787, 51.537503853624365

var info = L.control();
var gemein = new L.LayerGroup();
var overlays = { "Open Data Verf&uumlgbarkeit": gemein};

var geojson;

var map = L.map('map', {
  zoomControl: false,
  scrollWheelZoom: false,
  doubleClickZoom: false,
  boxZoom: false
}).setView([51.52, 7.15], 10);

mapLink = 'OpenStreetMap';
usedDataLink = 'Open.NRW';

var tileLayer = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.{ext}', {
  attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap',
  subdomains: 'abcd',
  minZoom: 0,
  maxZoom: 20,
  ext: 'png'
});

tileLayer.addTo(map);

// Add legend

var legend = L.control({ position: 'bottomright' });

legend.onAdd = function (map) {
  var div = L.DomUtil.create('div', 'info legend'),
  labels = [];
  labels.push('

 ' + "Nein");
  labels.push('

 ' + "In Planung");
  labels.push('

 ' + "Ja");

  div.innerHTML = '
Open Data
' + labels.join('
');

  return div;
}

 legend.addTo(map);

// Setup Info box
info.onAdd = function (map) {
  this._div = L.DomUtil.create('div', 'info');
  this.update();
  return this._div;
};

info.update = function (props) {
  var text = '
Open Data Verf&uumlgbarkeit 2016
';

  if(props) {
    text += '' + props.GN + '
';
  } else {
    text += 'Weitere Daten werden durch
Klick angezeigt';
  }

  this._div.innerHTML = text;
};

info.addTo(map);

// Set scales
L.control.scale({
  metric: true,
  imperial: false,
  position: 'bottomleft'
}).addTo(map);

// Fetch Data and add to map
fetch('./data/map-data.json', function (data) {
  geojson = L.geoJson(data, {
    style: setStyleByFeature,
    onEachFeature: setOnEachFeature
  }).addTo(gemein);

  map.addLayer(gemein);

}, function (err) {
  console.error(err);
});
<|endoftext|>


<commit_before>'use strict'

var ElasticSearch = require('elasticsearch')

var opts = {
  elastic: {
    host: 'localhost',
    port: 9200
  },
  mapping: {
    index: 'modules',
    type: 'document',
    body: {
      properties: {
        name: {
          type: 'string'
        },
        detail: {
          type: 'object'
        }
      }
    }
  }
}

module.exports = function (options) {
  var seneca = this
  var extend = seneca.util.deepextend

  opts = extend(opts, options)
  opts.elastic.client = new ElasticSearch.Client({
    host: {
      host: opts.elastic.host,
      port: opts.elastic.port
    }
  })

  ensureElasticSearch((err) => {
    if (err) {
      seneca.log.error(err)
      process.exit()
    }
  })

  seneca.add('role:search,cmd:search', search)
  seneca.add('role:search,cmd:upsert', upsert)
  seneca.add('role:info,info:updated', aliasUpsert)

  return {
    name: 'nodezoo-search'
  }
}

function ensureElasticSearch (done) {
  var client = opts.elastic.client
  var index = client.indices

  index.exists({index: 'modules'}, (err, exists) => {
    if (err) return done(err)
    if (exists) return done()

    index.create({index: 'modules'}, (err) => {
      if (err) return done(err)

      index.putMapping(opts.mapping, (err) => {
        done(err)
      })
    })
  })
}

function search (msg, done) {
  var client = opts.elastic.client
  var payload = {
    index: 'modules',
    body: {
      query: {
        match: {
          name: msg.query
        }
      }
    }
  }

  client.search(payload, (err, reply) => {
    if (err) return done(err)

    var hits = reply.hits && reply.hits.hits
    var items = []

    if (hits) {
      for (var i = 0; i < hits.length; i++) {
        items.push(hits[i]._source.detail)
      }
    }

    done(null, {
      ok: true,
      items: items
    })
  })
}

function upsert (msg, done) {
  var data = msg.data
  var github = data.github || {}

  var name = data.name

  var detail = {
    name: name,
    travis: {
    },
    github: {
      stars: github.stars || 0,
      watches: github.watches || 0,
      forks: github.forks || 0
    },
    npm: {
    }
  }

  var client = opts.elastic.client
  var index = client.indices
  var payload = {
    index: 'modules',
    type: 'document',
    id: name,
    body: {
      name: name,
      detail: detail
    }
  }

  index.exists({index: 'modules'}, (err, exists) => {
    if (err) return done(err)
    if (exists) return

    index.create({index: 'modules'}, (err) => {
      if (err) return done(err)

      index.putMapping(opts.mapping, (err) => {
        if (err) return done(err)
      })
    })
  })

  client.index(payload, (err, reply) => {
    if (err) return done(err)

    done(null, {
      ok: true,
      index: reply._index,
      version: reply._version,
      intial: reply.created
    })
  })
}

function aliasUpsert (msg, done) {
  this.act('role:search,cmd:upsert', {data: msg.data}, done)
}
<commit_msg>Added connected state<commit_after>'use strict'

var ElasticSearch = require('elasticsearch')

var opts = {
  elastic: {
    host: 'localhost',
    port: 9200
  },
  mapping: {
    index: 'modules',
    type: 'document',
    body: {
      properties: {
        name: {
          type: 'string'
        },
        detail: {
          type: 'object'
        }
      }
    }
  }
}

module.exports = function (options) {
  var seneca = this
  var extend = seneca.util.deepextend

  opts = extend(opts, options)
  opts.elastic.client = new ElasticSearch.Client({
    host: {
      host: opts.elastic.host,
      port: opts.elastic.port
    }
  })

  ensureElasticSearch((err) => {
    if (err) {
      seneca.log.error(err)
      process.exit()
    }
  })

  seneca.add('role:search,cmd:search', search)
  seneca.add('role:search,cmd:upsert', upsert)
  seneca.add('role:info,info:updated', aliasUpsert)

  return {
    name: 'nodezoo-search'
  }
}

function ensureElasticSearch (done) {
  var client = opts.elastic.client
  var index = client.indices

  index.exists({index: 'modules'}, (err, exists) => {
    if (err) return done(err)
    if (exists) return done()

    index.create({index: 'modules'}, (err) => {
      if (err) return done(err)

      index.putMapping(opts.mapping, (err) => {
        done(err)
      })
    })
  })
}

function search (msg, done) {
  var client = opts.elastic.client
  var payload = {
    index: 'modules',
    body: {
      query: {
        match: {
          name: msg.query
        }
      }
    }
  }

  client.search(payload, (err, reply) => {
    if (err) return done(err)

    var hits = reply.hits && reply.hits.hits
    var items = []

    if (hits) {
      for (var i = 0; i < hits.length; i++) {
        items.push(hits[i]._source.detail)
      }
    }

    done(null, {
      ok: true,
      items: items
    })
  })
}

function upsert (msg, done) {
  var data = msg.data
  var github = data.github || {}

  var name = data.name

  var detail = {
    name: name,
    travis: {
      connected: false
    },
    github: {
      connected: false,
      stars: github.stars || 0,
      watches: github.watches || 0,
      forks: github.forks || 0
    },
    npm: {
      connected: false
    }
  }

  if (data.npm) {
    detail.npm.connected = true
  }
  if (data.github) {
    detail.github.connected = true
  }
  if (data.travis) {
    detail.travis.connected = true
  }

  var client = opts.elastic.client
  var index = client.indices
  var payload = {
    index: 'modules',
    type: 'document',
    id: name,
    body: {
      name: name,
      detail: detail
    }
  }

  index.exists({index: 'modules'}, (err, exists) => {
    if (err) return done(err)
    if (exists) return

    index.create({index: 'modules'}, (err) => {
      if (err) return done(err)

      index.putMapping(opts.mapping, (err) => {
        if (err) return done(err)
      })
    })
  })

  client.index(payload, (err, reply) => {
    if (err) return done(err)

    done(null, {
      ok: true,
      index: reply._index,
      version: reply._version,
      intial: reply.created
    })
  })
}

function aliasUpsert (msg, done) {
  this.act('role:search,cmd:upsert', {data: msg.data}, done)
}
<|endoftext|>


<commit_before>/*!
 * node-gcm
 * Copyright(c) 2012 Marcus Farkas <marcus.farkas@spaceteam.at>
 * MIT Licensed
 */
var Constants = require('./constants');
var Result = require('./result');

var https = require('https');
var timer = require('timers');

function Sender(key) {
    this.key = key;
}

var sendNoRetryMethod = Sender.prototype.sendNoRetry = function (message, registrationIds, callback) {
    var body = {},
        result = new Result(),
        requestBody,
        post_options,
        post_req;

    body[Constants.JSON_REGISTRATION_IDS] = registrationIds;

    if (message.delayWhileIdle !== undefined) {
        body[Constants.PARAM_DELAY_WHILE_IDLE] = message.delayWhileIdle;
    }
    if (message.collapseKey !== undefined) {
        body[Constants.PARAM_COLLAPSE_KEY] = message.collapseKey;
    }
    if (message.timeToLive !== undefined) {
        body[Constants.PARAM_TIME_TO_LIVE] = message.timeToLive;
    }
    if (message.hasData) {
        body[Constants.PARAM_PAYLOAD_KEY] = message.data;
    }

    requestBody = JSON.stringify(body);
    post_options = {
        host: Constants.GCM_SEND_ENDPOINT,
        port: '443',
        path: Constants.GCM_SEND_ENDPATH,
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-length': Buffer.byteLength(requestBody, 'utf8'),
            'Authorization': 'key=' + this.key
        }
    };

    post_req = https.request(post_options, function (res) {
        var statusCode = res.statusCode,
            buf = '';
        res.setEncoding('utf-8');
        res.on('data', function (data) {
            buf += data;
        });

        res.on('end', function () {
            if (statusCode === 503) {
                console.log('GCM service is unavailable');
                return callback(statusCode, null);
            } else if(statusCode == 401){
		console.log('Unauthorized');
            	return callback(statusCode, null);
            } else if (statusCode !== 200) {
                console.log('Invalid request: ' + statusCode);
                return callback(statusCode, null);
            }

            // Make sure that we don't crash in case something goes wrong while
            // handling the response.
            try {
                var data = JSON.parse(buf);
                callback(null, data);
            } catch (e) {
                console.log("Error handling GCM response " + e);
                callback("error", null);
            }
        });
    });

    post_req.on('error', function (e) {
        console.log("Exception during GCM request: " + e);
        callback("request error", null);
    });

    post_req.write(requestBody);
    post_req.end();
};

Sender.prototype.send = function (message, registrationId, retries, callback) {

    var attempt = 1,
        backoff = Constants.BACKOFF_INITIAL_DELAY;

    if (registrationId.length === 1) {

        this.sendNoRetry(message, registrationId, function lambda(result) {

            if (result === undefined) {
                if (attempt < retries) {
                    var sleepTime = backoff * 2 * attempt;
                    if (sleepTime > Constants.MAX_BACKOFF_DELAY) {
                        sleepTime = Constants.MAX_BACKOFF_DELAY;
                    }
                    timer.setTimeout(function () {
                        sendNoRetryMethod(message, registrationId, lambda);
                    }, sleepTime);
                } else {
                    console.log('Could not send message after ' + retries + ' attempts');
                    callback(null, result);
                }
                attempt += 1;
            } else callback(null, result);
        });
    } else if (registrationId.length > 1) {
        this.sendNoRetry(message, registrationId, function lambda(err, result) {

            if (attempt < retries) {
                var sleepTime = backoff * 2 * attempt,
                    unsentRegIds = [],
                    i;
                if (sleepTime > Constants.MAX_BACKOFF_DELAY) {
                    sleepTime = Constants.MAX_BACKOFF_DELAY;
                }

                for (i = 0; i < registrationId.length; i += 1) {
                    if (result.results[i].error === 'Unavailable') {
                        unsentRegIds.push(registrationId[i]);
                    }
                }

                registrationId = unsentRegIds;
                if (registrationId.length !== 0) {
                    timer.setTimeout(function () {
                        sendNoRetryMethod(message, registrationId, lambda);
                    }, sleepTime);
                    attempt += 1;
                } else callback(null, result);

            } else {
                console.log('Could not send message to all devices after ' + retries + ' attempts');
                callback(null, result);
            }
        });
    } else {
        console.log('No RegistrationIds given!');
    }
};

module.exports = Sender;
<commit_msg>Aggregated callback 'err' parameter missing<commit_after>/*!
 * node-gcm
 * Copyright(c) 2012 Marcus Farkas <marcus.farkas@spaceteam.at>
 * MIT Licensed
 */
var Constants = require('./constants');
var Result = require('./result');

var https = require('https');
var timer = require('timers');

function Sender(key) {
    this.key = key;
}

var sendNoRetryMethod = Sender.prototype.sendNoRetry = function (message, registrationIds, callback) {
    var body = {},
        result = new Result(),
        requestBody,
        post_options,
        post_req;

    body[Constants.JSON_REGISTRATION_IDS] = registrationIds;

    if (message.delayWhileIdle !== undefined) {
        body[Constants.PARAM_DELAY_WHILE_IDLE] = message.delayWhileIdle;
    }
    if (message.collapseKey !== undefined) {
        body[Constants.PARAM_COLLAPSE_KEY] = message.collapseKey;
    }
    if (message.timeToLive !== undefined) {
        body[Constants.PARAM_TIME_TO_LIVE] = message.timeToLive;
    }
    if (message.hasData) {
        body[Constants.PARAM_PAYLOAD_KEY] = message.data;
    }

    requestBody = JSON.stringify(body);
    post_options = {
        host: Constants.GCM_SEND_ENDPOINT,
        port: '443',
        path: Constants.GCM_SEND_ENDPATH,
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-length': Buffer.byteLength(requestBody, 'utf8'),
            'Authorization': 'key=' + this.key
        }
    };

    post_req = https.request(post_options, function (res) {
        var statusCode = res.statusCode,
            buf = '';
        res.setEncoding('utf-8');
        res.on('data', function (data) {
            buf += data;
        });

        res.on('end', function () {
            if (statusCode === 503) {
                console.log('GCM service is unavailable');
                return callback(statusCode, null);
            } else if(statusCode == 401){
		console.log('Unauthorized');
            	return callback(statusCode, null);
            } else if (statusCode !== 200) {
                console.log('Invalid request: ' + statusCode);
                return callback(statusCode, null);
            }

            // Make sure that we don't crash in case something goes wrong while
            // handling the response.
            try {
                var data = JSON.parse(buf);
                callback(null, data);
            } catch (e) {
                console.log("Error handling GCM response " + e);
                callback("error", null);
            }
        });
    });

    post_req.on('error', function (e) {
        console.log("Exception during GCM request: " + e);
        callback("request error", null);
    });

    post_req.write(requestBody);
    post_req.end();
};

Sender.prototype.send = function (message, registrationId, retries, callback) {

    var attempt = 1,
        backoff = Constants.BACKOFF_INITIAL_DELAY;

    if (registrationId.length === 1) {

        this.sendNoRetry(message, registrationId, function lambda(err, result) {

            if (result === undefined) {
                if (attempt < retries) {
                    var sleepTime = backoff * 2 * attempt;
                    if (sleepTime > Constants.MAX_BACKOFF_DELAY) {
                        sleepTime = Constants.MAX_BACKOFF_DELAY;
                    }
                    timer.setTimeout(function () {
                        sendNoRetryMethod(message, registrationId, lambda);
                    }, sleepTime);
                } else {
                    console.log('Could not send message after ' + retries + ' attempts');
                    callback(null, result);
                }
                attempt += 1;
            } else callback(null, result);
        });
    } else if (registrationId.length > 1) {
        this.sendNoRetry(message, registrationId, function lambda(err, result) {

            if (attempt < retries) {
                var sleepTime = backoff * 2 * attempt,
                    unsentRegIds = [],
                    i;
                if (sleepTime > Constants.MAX_BACKOFF_DELAY) {
                    sleepTime = Constants.MAX_BACKOFF_DELAY;
                }

                for (i = 0; i < registrationId.length; i += 1) {
                    if (result.results[i].error === 'Unavailable') {
                        unsentRegIds.push(registrationId[i]);
                    }
                }

                registrationId = unsentRegIds;
                if (registrationId.length !== 0) {
                    timer.setTimeout(function () {
                        sendNoRetryMethod(message, registrationId, lambda);
                    }, sleepTime);
                    attempt += 1;
                } else callback(null, result);

            } else {
                console.log('Could not send message to all devices after ' + retries + ' attempts');
                callback(null, result);
            }
        });
    } else {
        console.log('No RegistrationIds given!');
    }
};

module.exports = Sender;
<|endoftext|>


<commit_before>/*!
 * node-gcm
 * Copyright(c) 2013 Marcus Farkas <toothlessgear@finitebox.com>
 * MIT Licensed
 */
var Constants = require('./constants');
var req = require('request');
var debug = require('debug')('node-gcm');

function Sender(key , options) {
    if (!(this instanceof Sender)) {
        return new Sender(key, options);
    }

    this.key = key;
    this.options = options;
}

Sender.prototype.sendNoRetry = function(message, registrationIds, callback) {
    if(!callback) {
        callback = function() {};
    }

    var body = {},
        requestBody,
        post_options,
        post_req,
        timeout;

    body[Constants.JSON_REGISTRATION_IDS] = registrationIds;

    if (message.delayWhileIdle !== undefined) {
        body[Constants.PARAM_DELAY_WHILE_IDLE] = message.delayWhileIdle;
    }
    if (message.collapseKey !== undefined) {
        body[Constants.PARAM_COLLAPSE_KEY] = message.collapseKey;
    }
    if (message.timeToLive !== undefined) {
        body[Constants.PARAM_TIME_TO_LIVE] = message.timeToLive;
    }
    if (message.dryRun !== undefined) {
        body[Constants.PARAM_DRY_RUN] = message.dryRun;
    }
    if (Object.keys(message.data)) {
        body[Constants.PARAM_PAYLOAD_KEY] = message.data;
    }

    requestBody = JSON.stringify(body);

    post_options = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-length': Buffer.byteLength(requestBody, 'utf8'),
            'Authorization': 'key=' + this.key
        },
        uri: Constants.GCM_SEND_URI,
        body: requestBody
    };

    if (this.options && this.options.proxy) {
        post_options.proxy = this.options.proxy;
    }

    if (this.options && this.options.maxSockets) {
        post_options.maxSockets = this.options.maxSockets;
    }

    timeout = Constants.SOCKET_TIMEOUT;

    if (this.options && this.options.timeout) {
        timeout =  this.options.timeout;
    }

    post_options.timeout = timeout;

    post_req = req(post_options, function (err, res, resBody) {

        if (err) {
            return callback(err, null);

        }
        if (!res) {
            return callback('response is null', null);

        }
        if (res.statusCode >= 500) {
            debug('GCM service is unavailable (500)');
            return callback(res.statusCode, null);
        } else if (res.statusCode === 401) {
            debug('Unauthorized (401). Check that your API token is correct.');
            return callback(res.statusCode, null);
        } else if (res.statusCode !== 200) {
            debug('Invalid request (' + res.statusCode + '): ' + resBody);
            return callback(res.statusCode, null);
        }

        try {
            callback(null, JSON.parse(resBody));
        } catch (e) {
            debug("Error handling GCM response " + e);
            callback("error", null);
        }
    });
};

Sender.prototype.send = function(message, registrationIds, retries, backoff, callback) {
    if (!registrationIds.length) {
        debug('No RegistrationIds given!');
        callback('No RegistrationIds given!', null);
        return;
    }

    if(typeof backoff == "undefined" || typeof backoff == "function") {
        callback = backoff;
        backoff = Constants.BACKOFF_INITIAL_DELAY;
    }
    if(typeof retries == "undefined" || typeof retries == "function") {
        callback = retries;
        retries = 5;
    }
    if(!callback) {
        callback = function() {};
    }

    if(retries == 0) {
        return this.sendNoRetry(message, registrationIds, callback);
    }
    if (backoff > Constants.MAX_BACKOFF_DELAY) {
        backoff = Constants.MAX_BACKOFF_DELAY;
    }

    var self = this;

    this.sendNoRetry(message, registrationIds, function lambda(err, result) {
        // if we err'd resend them all
        if (err) {
            return self.send(message, registrationIds, retries - 1, backoff * 2, callback);
        }

        // check for bad ids
        var unsentRegIds = [], regIdPositionMap = [];
        for (var i = 0; i < registrationIds.length; i += 1) {
            if (result.results[i].error === 'Unavailable') {
                regIdPositionMap.push(i);
                unsentRegIds.push(registrationIds[i]);
            }
        }

        if (unsentRegIds.length) {
            if (result) {
                debug("Results received but not all successful", result);
            }

            setTimeout(function () {
                self.sendNoRetry(message, unsentRegIds, lambda);
            }, backoff);
        } else {
            // No more registration ids to send
            callback(err, result);
        }
    });
};

module.exports = Sender;<commit_msg>Reverse if-statement (early-stop)<commit_after>/*!
 * node-gcm
 * Copyright(c) 2013 Marcus Farkas <toothlessgear@finitebox.com>
 * MIT Licensed
 */
var Constants = require('./constants');
var req = require('request');
var debug = require('debug')('node-gcm');

function Sender(key , options) {
    if (!(this instanceof Sender)) {
        return new Sender(key, options);
    }

    this.key = key;
    this.options = options;
}

Sender.prototype.sendNoRetry = function(message, registrationIds, callback) {
    if(!callback) {
        callback = function() {};
    }

    var body = {},
        requestBody,
        post_options,
        post_req,
        timeout;

    body[Constants.JSON_REGISTRATION_IDS] = registrationIds;

    if (message.delayWhileIdle !== undefined) {
        body[Constants.PARAM_DELAY_WHILE_IDLE] = message.delayWhileIdle;
    }
    if (message.collapseKey !== undefined) {
        body[Constants.PARAM_COLLAPSE_KEY] = message.collapseKey;
    }
    if (message.timeToLive !== undefined) {
        body[Constants.PARAM_TIME_TO_LIVE] = message.timeToLive;
    }
    if (message.dryRun !== undefined) {
        body[Constants.PARAM_DRY_RUN] = message.dryRun;
    }
    if (Object.keys(message.data)) {
        body[Constants.PARAM_PAYLOAD_KEY] = message.data;
    }

    requestBody = JSON.stringify(body);

    post_options = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-length': Buffer.byteLength(requestBody, 'utf8'),
            'Authorization': 'key=' + this.key
        },
        uri: Constants.GCM_SEND_URI,
        body: requestBody
    };

    if (this.options && this.options.proxy) {
        post_options.proxy = this.options.proxy;
    }

    if (this.options && this.options.maxSockets) {
        post_options.maxSockets = this.options.maxSockets;
    }

    timeout = Constants.SOCKET_TIMEOUT;

    if (this.options && this.options.timeout) {
        timeout =  this.options.timeout;
    }

    post_options.timeout = timeout;

    post_req = req(post_options, function (err, res, resBody) {

        if (err) {
            return callback(err, null);

        }
        if (!res) {
            return callback('response is null', null);

        }
        if (res.statusCode >= 500) {
            debug('GCM service is unavailable (500)');
            return callback(res.statusCode, null);
        } else if (res.statusCode === 401) {
            debug('Unauthorized (401). Check that your API token is correct.');
            return callback(res.statusCode, null);
        } else if (res.statusCode !== 200) {
            debug('Invalid request (' + res.statusCode + '): ' + resBody);
            return callback(res.statusCode, null);
        }

        try {
            callback(null, JSON.parse(resBody));
        } catch (e) {
            debug("Error handling GCM response " + e);
            callback("error", null);
        }
    });
};

Sender.prototype.send = function(message, registrationIds, retries, backoff, callback) {
    if (!registrationIds.length) {
        debug('No RegistrationIds given!');
        callback('No RegistrationIds given!', null);
        return;
    }

    if(typeof backoff == "undefined" || typeof backoff == "function") {
        callback = backoff;
        backoff = Constants.BACKOFF_INITIAL_DELAY;
    }
    if(typeof retries == "undefined" || typeof retries == "function") {
        callback = retries;
        retries = 5;
    }
    if(!callback) {
        callback = function() {};
    }

    if(retries == 0) {
        return this.sendNoRetry(message, registrationIds, callback);
    }
    if (backoff > Constants.MAX_BACKOFF_DELAY) {
        backoff = Constants.MAX_BACKOFF_DELAY;
    }

    var self = this;

    this.sendNoRetry(message, registrationIds, function lambda(err, result) {
        // if we err'd resend them all
        if (err) {
            return self.send(message, registrationIds, retries - 1, backoff * 2, callback);
        }

        // check for bad ids
        var unsentRegIds = [], regIdPositionMap = [];
        for (var i = 0; i < registrationIds.length; i += 1) {
            if (result.results[i].error === 'Unavailable') {
                regIdPositionMap.push(i);
                unsentRegIds.push(registrationIds[i]);
            }
        }

        if (!unsentRegIds.length) {
            return callback(err, result);
        }

            if (result) {
                debug("Results received but not all successful", result);
            }

            setTimeout(function () {
                self.sendNoRetry(message, unsentRegIds, lambda);
            }, backoff);

    });
};

module.exports = Sender;<|endoftext|>


<commit_before>var Log = require('./logger'),
  logger = new Log(global.logLevel || 'info'),
  http = require('http'),
  url = require('url'),
  path = require('path'),
  util = require('util'),
  fs = require('fs'),
  qs = require('querystring'),
  utils = require('./utils'),
  proxyServer = require('./proxy').proxyServer,
  chalk = require('chalk'),
  mime = require('mime'),
  send = require('send'),
  vm = require('vm'),
  CircularJSON = require('circular-json'),
  resolve = require('resolve');

exports.Server = function Server(bsClient, workers, config, callback) {
  var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ])
    .map(function (path) {
      return path.split(/[?#]/)[0];
    }),
    reports = [];

  function getBrowserReport(browserInfo) {
    var browserReport = null;
    reports.forEach(function(report) {
      if(report && report.browser === browserInfo) {
        browserReport = report;
      }
    });
    if(!browserReport) {
      browserReport = {
        browser: browserInfo
      };
      reports.push(browserReport);
    }
    browserReport.tests = browserReport.tests || [];
    return browserReport;
  }

  function handleFile(filename, request, response, doNotUseProxy) {
    var url_parts = url.parse(request.url, true);
    var query = url_parts.query;

    if (query._worker_key && workers[query._worker_key]) {
      var worker = workers[query._worker_key];
      worker.markAckd();
    }

    var getReporterPatch = function () {
      var scripts = [
        'json2.js',
        'browserstack.js',
        'browserstack-util.js'
      ];

      var framework_scripts = {
        'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js']
      };

      var filePath = path.relative(process.cwd(), filename);
      var pathMatches = (testFilePaths.indexOf(filePath) !== -1);

      if (pathMatches) {
        var framework = config['test_framework'];
        var tag_name = (framework === 'mocha') ? 'head' : 'body';
        var patch = '$1';

        scripts.forEach(function(script) {
          patch += '<script type="text/javascript" src="/_patch/' + script + '"></script>\n';
        });

        patch += externalScript('js-reporters/dist/js-reporters.js');
        patch += externalScript('circular-json/build/circular-json.js');

        // adding framework scripts
        if (framework === 'jasmine') {
          framework_scripts['jasmine'].forEach(function(script) {
            patch += '<script type="text/javascript" src="/_patch/' + script + '"></script>\n';
          });
          patch += '<script type="text/javascript">jasmine.getEnv().addReporter(new jasmine.JSReporter());</script>\n';
        } else {
          patch += '<script type="text/javascript" src="/_patch/reporter.js"></script>\n';
        }
        patch += '</' + tag_name + '>';
        return patch;
      }
    };

    var writeResponse = function(err, data) {

      if (err) {
        sendError(response, err, 500);
        return;
      }

      response.writeHead(200, {
        'Content-Type': 'text/html; charset=utf-8'
      });
      var tag_name = (config['test_framework'] === 'mocha') ? 'head' : 'body';
      var matcher = new RegExp('(.*)<\/' + tag_name + '>'); ///(.*)<\/body>/;
      var patch = getReporterPatch();
      data = data.replace(matcher, patch);

      response.write(data);
      response.end();
    };

    if (!doNotUseProxy && config.test_server) {
      proxyServer.onRequest(request, response, config.test_server, function(remote_response, response_data) {
        var mimeType = mime.lookup(filename);
        var final_data = response_data;
        var headers = remote_response.headers;
        if (mimeType === 'text/html') {
          var matcher = /(.*)<\/head>/;
          var patch = getReporterPatch();
          final_data = response_data.replace(matcher, patch);
          headers['content-length'] = final_data.length;
        }
        response.writeHead(remote_response.statusCode, headers);
        response.write(final_data);
        response.end();
        return;
      });

    } else {

      fs.exists(filename, function(exists) {
        if (!exists) {
          sendError(response,'file not found', 404);
          return;
        }

        if (fs.lstatSync(filename).isDirectory()) {
          filename = filename + (filename.lastIndexOf('/') === filename.length - 1 ? '' : '/') + 'index.html';
        }

        var mimeType = mime.lookup(filename);
        if (mimeType === 'text/html') {
          fs.readFile(filename, { encoding: 'utf8' }, function (err, data) {
            writeResponse(err, data);
          });
        } else {
          send(request, filename)
            .on('error', function onSendError(err) {
              sendError(response, (err.message || 'Internal Server Error'), err.status || 500);
            })
            .pipe(response);
        }
      });
    }
  }

  function formatTraceback(details) {
    var output = '"' + details.testName + '" failed';
    if(details.error) {
      if (details.error.message) {
        output += ', ' + details.error.message;
      }
      if (details.error.actual != null && details.error.expected != null) {
        output += '\n' + chalk.blue('Expected: ') + details.error.expected +
          '\n' + chalk.blue('  Actual: ') + details.error.actual;
      }
      if (details.error.source || details.error.stack) {
        output += '\n' + chalk.blue('  Source: ') + '';
        output += ( details.error.source || details.error.stack ).split('\n').join('\n\t  ');
      }
    }
    return output;
  }

  function checkAndTerminateWorker(worker, callback) {
    var next_path = getNextTestPath(worker);
    if (next_path) {
      var url = worker.buildUrl(next_path);
      worker.test_path = next_path;
      worker.config.url = next_path;

      bsClient.changeUrl(worker.id, { url: url }, function () {
        callback(true);
      });

    } else {
      bsClient.terminateWorker(worker.id, function () {
        callback(false);
      });
    }
  }

  function getNextTestPath(worker) {
    if (!config.multipleTest) {
      return null;
    }
    return config.test_path[ ++worker.path_index ];
  }

  function getWorkerUuid(request) {
    var uuid = request.headers['x-worker-uuid'];

    uuid = uuid && uuid.replace(/[^a-zA-Z0-9\-]/, '');
    logger.trace('cleaning up worker uuid:', uuid);

    uuid = (uuid && typeof workers[uuid] === 'object') ? uuid : null;
    logger.trace('worker uuid', uuid, (uuid ? 'valid' : 'invalid'));

    return (uuid && workers[uuid]) ? uuid : null;
  }

  function sendError(response, errMessage, statusCode) {
    response.writeHead(statusCode || 400, {
      'Content-Type': 'text/plain'
    });

    if (errMessage) {
      response.write(errMessage + '\n');
    }

    response.end();
  }

  function externalScript(scriptPath) {
    var resolvedPath = resolve.sync(scriptPath, { basedir: __dirname });
    var scriptContents = fs.readFileSync(resolvedPath, { encoding: 'utf8' });
    return '<script type="text/javascript">' + scriptContents + '</script>';
  }

  var handlers = {
    '_progress': function progressHandler(uri, body, request, response) {
      var uuid = getWorkerUuid(request);

      if (!uuid) {
        sendError(response, 'worker not found', 404);
        return;
      }

      var worker = workers[uuid];
      var browserInfo = worker.getTestBrowserInfo();
      var query = null;

      try {
        query = CircularJSON.parse(body);
      } catch(e) {
        logger.info('[%s] Exception in parsing log', worker.string);
        logger.info('[%s] Log: ' + qs.parse(body).data, worker.string);
      }

      logger.trace('[%s] _progress', worker.id, CircularJSON.stringify(query));

      if (query && query.test && query.test.errors) {
        var browserReport = getBrowserReport(browserInfo);
        browserReport.tests.push(query.test || {});

        query.test.errors.forEach(function(error) {
          logger.info('[%s] ' + chalk.red('Error:'), browserInfo, formatTraceback({
            error: error,
            testName: query.test.name,
            suiteName: query.test.suiteName
          }));
        });
      }
      response.end();
    },

    '_report': function reportHandler(uri, body, request, response) {
      var uuid = getWorkerUuid(request);
      if (!uuid) {
        sendError(response, 'worker not found', 404);
        return;
      }

      var worker = workers[uuid];
      worker._worker_key = uuid;
      var browserInfo = worker.getTestBrowserInfo();

      var query = null;
      try {
        query = CircularJSON.parse(body);
      } catch (e) {}

      logger.trace('[%s] _report', worker.id, CircularJSON.stringify(query));

      if (query === null) {
        logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']);
      } else {
        var browserReport = getBrowserReport(browserInfo);
        browserReport.suites = query;

        var color;
        if(config['test_framework'] === 'jasmine') {
          color = ( query.total !== query.passed ) ? 'red' : 'green';
          logger.info('[%s] ' + chalk[color](( query.total !== query.passed ) ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', browserInfo, query.total, query.passed, query.failed, query.runtime);
          config.status += query.failed;
        } else if(query.testCounts) {
          color = query.status === 'failed' ? 'red' : 'green';
          logger.info('[%s] ' + chalk[color](query.status === 'failed' ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed, %d skipped; ran for %dms', browserInfo, query.testCounts.total, query.testCounts.passed, query.testCounts.failed, query.testCounts.skipped, query.runtime);
          config.status += query.testCounts.failed;
        }
      }

      logger.trace('[%s] _report: client.takeScreenshot', worker.id);

      bsClient.takeScreenshot(worker.id, function(error, screenshot) {
        logger.trace('[%s] _report: client.takeScreenshot | response:', worker.id, screenshot, error);

        if (!error && screenshot.url && query && query.failed) {
          logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', browserInfo, screenshot.url);
        }

        checkAndTerminateWorker(worker, function(reusedWorker) {
          if (!workers[uuid]) {
            logger.trace('[%s] _report: checkAndTerminateWorker: worker not found', worker.id);
            return;
          }

          if (reusedWorker) {
            logger.trace('[%s] _report: checkAndTerminateWorker: reused worker', worker.id);
            logger.debug('[%s] Reused', browserInfo);
            worker.resetAck();
            worker.awaitAck();
            return;
          }

          logger.trace('[%s] _report: checkAndTerminateWorker: terminated', worker.id);
          logger.debug('[%s] Terminated', browserInfo);

          clearTimeout(workers[uuid].ackTimeout);
          clearTimeout(workers[uuid].activityTimeout);
          clearTimeout(workers[uuid].testActivityTimeout);
          delete workers[uuid];

          if (utils.objectSize(workers) === 0) {
            var color = config.status > 0 ? 'red' : 'green';
            logger.info(chalk[color]('All tests done, failures: %d.'), config.status);

            if (config.status > 0) {
              config.status = 1;
            }

            logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures');
            callback(null, reports);
          }
        });
      });

      response.end();
    },
    '_log': function logHandler(uri, body, request, response) {
      var uuid = getWorkerUuid(request);
      var query = null;
      try {
        query = CircularJSON.parse(body);
      } catch (e) {}

      logger.trace('[%s] _log', ((uuid && workers[uuid]) || {}).id, query);

      var logged = false;

      if (query && Array.isArray(query.arguments)) {
        var context = { input: query.arguments, format: util.format, output: '' };
        var tryEvalOrString = 'function (arg) { try { return eval(\'o = \' + arg); } catch (e) { return arg; } }';

        try {
          // eval each element of query.arguments safely in an isolated context
          vm.runInNewContext('output = format.apply(null, input.map(' + tryEvalOrString + '));', context);
          logger.info('[' + request.headers['x-browser-string'] + '] ' + context.output);
          logged = true;
        } catch (e) {
          logger.debug('_log: failed to format console log data', query);
        }
      }

      if (!logged) {
        logger.info('[' + request.headers['x-browser-string'] + '] ' + query);
      }

      response.end();
    },
    '_patch': function patchHandler(uri, body, request, response) {
      var filePath = path.join(__dirname, uri);
      logger.trace('_patch', filePath);

      handleFile(filePath, request, response, true);
    },
    '_default': function defaultHandler(uri, body, request, response) {
      var filePath = path.join(process.cwd(), uri);
      logger.trace('_default', filePath);

      handleFile(filePath, request, response);
    }
  };

  return http.createServer(function(request, response) {
    var uri = url.parse(request.url).pathname;
    var method = uri.split('/')[1];
    var body = '';

    request.on('data', function(data) {
      body += data;
    });
    request.on('end', function() {
      (handlers[method] || handlers._default)(uri, body, request, response);
    });
  });
};
<commit_msg>fix server logs for non-json logs<commit_after>var Log = require('./logger'),
  logger = new Log(global.logLevel || 'info'),
  http = require('http'),
  url = require('url'),
  path = require('path'),
  util = require('util'),
  fs = require('fs'),
  qs = require('querystring'),
  utils = require('./utils'),
  proxyServer = require('./proxy').proxyServer,
  chalk = require('chalk'),
  mime = require('mime'),
  send = require('send'),
  vm = require('vm'),
  CircularJSON = require('circular-json'),
  resolve = require('resolve');

exports.Server = function Server(bsClient, workers, config, callback) {
  var testFilePaths = (Array.isArray(config.test_path) ? config.test_path : [ config.test_path ])
    .map(function (path) {
      return path.split(/[?#]/)[0];
    }),
    reports = [];

  function getBrowserReport(browserInfo) {
    var browserReport = null;
    reports.forEach(function(report) {
      if(report && report.browser === browserInfo) {
        browserReport = report;
      }
    });
    if(!browserReport) {
      browserReport = {
        browser: browserInfo
      };
      reports.push(browserReport);
    }
    browserReport.tests = browserReport.tests || [];
    return browserReport;
  }

  function handleFile(filename, request, response, doNotUseProxy) {
    var url_parts = url.parse(request.url, true);
    var query = url_parts.query;

    if (query._worker_key && workers[query._worker_key]) {
      var worker = workers[query._worker_key];
      worker.markAckd();
    }

    var getReporterPatch = function () {
      var scripts = [
        'json2.js',
        'browserstack.js',
        'browserstack-util.js'
      ];

      var framework_scripts = {
        'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js']
      };

      var filePath = path.relative(process.cwd(), filename);
      var pathMatches = (testFilePaths.indexOf(filePath) !== -1);

      if (pathMatches) {
        var framework = config['test_framework'];
        var tag_name = (framework === 'mocha') ? 'head' : 'body';
        var patch = '$1';

        scripts.forEach(function(script) {
          patch += '<script type="text/javascript" src="/_patch/' + script + '"></script>\n';
        });

        patch += externalScript('js-reporters/dist/js-reporters.js');
        patch += externalScript('circular-json/build/circular-json.js');

        // adding framework scripts
        if (framework === 'jasmine') {
          framework_scripts['jasmine'].forEach(function(script) {
            patch += '<script type="text/javascript" src="/_patch/' + script + '"></script>\n';
          });
          patch += '<script type="text/javascript">jasmine.getEnv().addReporter(new jasmine.JSReporter());</script>\n';
        } else {
          patch += '<script type="text/javascript" src="/_patch/reporter.js"></script>\n';
        }
        patch += '</' + tag_name + '>';
        return patch;
      }
    };

    var writeResponse = function(err, data) {

      if (err) {
        sendError(response, err, 500);
        return;
      }

      response.writeHead(200, {
        'Content-Type': 'text/html; charset=utf-8'
      });
      var tag_name = (config['test_framework'] === 'mocha') ? 'head' : 'body';
      var matcher = new RegExp('(.*)<\/' + tag_name + '>'); ///(.*)<\/body>/;
      var patch = getReporterPatch();
      data = data.replace(matcher, patch);

      response.write(data);
      response.end();
    };

    if (!doNotUseProxy && config.test_server) {
      proxyServer.onRequest(request, response, config.test_server, function(remote_response, response_data) {
        var mimeType = mime.lookup(filename);
        var final_data = response_data;
        var headers = remote_response.headers;
        if (mimeType === 'text/html') {
          var matcher = /(.*)<\/head>/;
          var patch = getReporterPatch();
          final_data = response_data.replace(matcher, patch);
          headers['content-length'] = final_data.length;
        }
        response.writeHead(remote_response.statusCode, headers);
        response.write(final_data);
        response.end();
        return;
      });

    } else {

      fs.exists(filename, function(exists) {
        if (!exists) {
          sendError(response,'file not found', 404);
          return;
        }

        if (fs.lstatSync(filename).isDirectory()) {
          filename = filename + (filename.lastIndexOf('/') === filename.length - 1 ? '' : '/') + 'index.html';
        }

        var mimeType = mime.lookup(filename);
        if (mimeType === 'text/html') {
          fs.readFile(filename, { encoding: 'utf8' }, function (err, data) {
            writeResponse(err, data);
          });
        } else {
          send(request, filename)
            .on('error', function onSendError(err) {
              sendError(response, (err.message || 'Internal Server Error'), err.status || 500);
            })
            .pipe(response);
        }
      });
    }
  }

  function formatTraceback(details) {
    var output = '"' + details.testName + '" failed';
    if(details.error) {
      if (details.error.message) {
        output += ', ' + details.error.message;
      }
      if (details.error.actual != null && details.error.expected != null) {
        output += '\n' + chalk.blue('Expected: ') + details.error.expected +
          '\n' + chalk.blue('  Actual: ') + details.error.actual;
      }
      if (details.error.source || details.error.stack) {
        output += '\n' + chalk.blue('  Source: ') + '';
        output += ( details.error.source || details.error.stack ).split('\n').join('\n\t  ');
      }
    }
    return output;
  }

  function checkAndTerminateWorker(worker, callback) {
    var next_path = getNextTestPath(worker);
    if (next_path) {
      var url = worker.buildUrl(next_path);
      worker.test_path = next_path;
      worker.config.url = next_path;

      bsClient.changeUrl(worker.id, { url: url }, function () {
        callback(true);
      });

    } else {
      bsClient.terminateWorker(worker.id, function () {
        callback(false);
      });
    }
  }

  function getNextTestPath(worker) {
    if (!config.multipleTest) {
      return null;
    }
    return config.test_path[ ++worker.path_index ];
  }

  function getWorkerUuid(request) {
    var uuid = request.headers['x-worker-uuid'];

    uuid = uuid && uuid.replace(/[^a-zA-Z0-9\-]/, '');
    logger.trace('cleaning up worker uuid:', uuid);

    uuid = (uuid && typeof workers[uuid] === 'object') ? uuid : null;
    logger.trace('worker uuid', uuid, (uuid ? 'valid' : 'invalid'));

    return (uuid && workers[uuid]) ? uuid : null;
  }

  function sendError(response, errMessage, statusCode) {
    response.writeHead(statusCode || 400, {
      'Content-Type': 'text/plain'
    });

    if (errMessage) {
      response.write(errMessage + '\n');
    }

    response.end();
  }

  function externalScript(scriptPath) {
    var resolvedPath = resolve.sync(scriptPath, { basedir: __dirname });
    var scriptContents = fs.readFileSync(resolvedPath, { encoding: 'utf8' });
    return '<script type="text/javascript">' + scriptContents + '</script>';
  }

  var handlers = {
    '_progress': function progressHandler(uri, body, request, response) {
      var uuid = getWorkerUuid(request);

      if (!uuid) {
        sendError(response, 'worker not found', 404);
        return;
      }

      var worker = workers[uuid];
      var browserInfo = worker.getTestBrowserInfo();
      var query = null;

      try {
        query = CircularJSON.parse(body);
      } catch(e) {
        logger.info('[%s] Exception in parsing log', worker.string);
        logger.info('[%s] Log: ' + qs.parse(body).data, worker.string);
      }

      logger.trace('[%s] _progress', worker.id, CircularJSON.stringify(query));

      if (query && query.test && query.test.errors) {
        var browserReport = getBrowserReport(browserInfo);
        browserReport.tests.push(query.test || {});

        query.test.errors.forEach(function(error) {
          logger.info('[%s] ' + chalk.red('Error:'), browserInfo, formatTraceback({
            error: error,
            testName: query.test.name,
            suiteName: query.test.suiteName
          }));
        });
      }
      response.end();
    },

    '_report': function reportHandler(uri, body, request, response) {
      var uuid = getWorkerUuid(request);
      if (!uuid) {
        sendError(response, 'worker not found', 404);
        return;
      }

      var worker = workers[uuid];
      worker._worker_key = uuid;
      var browserInfo = worker.getTestBrowserInfo();

      var query = null;
      try {
        query = CircularJSON.parse(body);
      } catch (e) {}

      logger.trace('[%s] _report', worker.id, CircularJSON.stringify(query));

      if (query === null) {
        logger.info('[%s] Null response from remote Browser', request.headers['x-browser-string']);
      } else {
        var browserReport = getBrowserReport(browserInfo);
        browserReport.suites = query;

        var color;
        if(config['test_framework'] === 'jasmine') {
          color = ( query.total !== query.passed ) ? 'red' : 'green';
          logger.info('[%s] ' + chalk[color](( query.total !== query.passed ) ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed; ran for %dms', browserInfo, query.total, query.passed, query.failed, query.runtime);
          config.status += query.failed;
        } else if(query.testCounts) {
          color = query.status === 'failed' ? 'red' : 'green';
          logger.info('[%s] ' + chalk[color](query.status === 'failed' ? 'Failed:' : 'Passed:') + ' %d tests, %d passed, %d failed, %d skipped; ran for %dms', browserInfo, query.testCounts.total, query.testCounts.passed, query.testCounts.failed, query.testCounts.skipped, query.runtime);
          config.status += query.testCounts.failed;
        }
      }

      logger.trace('[%s] _report: client.takeScreenshot', worker.id);

      bsClient.takeScreenshot(worker.id, function(error, screenshot) {
        logger.trace('[%s] _report: client.takeScreenshot | response:', worker.id, screenshot, error);

        if (!error && screenshot.url && query && query.failed) {
          logger.info('[%s] ' + chalk.yellow('Screenshot:') + ' %s', browserInfo, screenshot.url);
        }

        checkAndTerminateWorker(worker, function(reusedWorker) {
          if (!workers[uuid]) {
            logger.trace('[%s] _report: checkAndTerminateWorker: worker not found', worker.id);
            return;
          }

          if (reusedWorker) {
            logger.trace('[%s] _report: checkAndTerminateWorker: reused worker', worker.id);
            logger.debug('[%s] Reused', browserInfo);
            worker.resetAck();
            worker.awaitAck();
            return;
          }

          logger.trace('[%s] _report: checkAndTerminateWorker: terminated', worker.id);
          logger.debug('[%s] Terminated', browserInfo);

          clearTimeout(workers[uuid].ackTimeout);
          clearTimeout(workers[uuid].activityTimeout);
          clearTimeout(workers[uuid].testActivityTimeout);
          delete workers[uuid];

          if (utils.objectSize(workers) === 0) {
            var color = config.status > 0 ? 'red' : 'green';
            logger.info(chalk[color]('All tests done, failures: %d.'), config.status);

            if (config.status > 0) {
              config.status = 1;
            }

            logger.trace('[%s] _report: checkAndTerminateWorker: all tests done', worker.id, config.status && 'with failures');
            callback(null, reports);
          }
        });
      });

      response.end();
    },
    '_log': function logHandler(uri, body, request, response) {
      var uuid = getWorkerUuid(request);
      var query = null;
      try {
        query = CircularJSON.parse(body);
      } catch (e) {
        query = body;
      }

      logger.trace('[%s] _log', ((uuid && workers[uuid]) || {}).id, query);

      var logged = false;

      if (query && Array.isArray(query.arguments)) {
        var context = { input: query.arguments, format: util.format, output: '' };
        var tryEvalOrString = 'function (arg) { try { return eval(\'o = \' + arg); } catch (e) { return arg; } }';

        try {
          // eval each element of query.arguments safely in an isolated context
          vm.runInNewContext('output = format.apply(null, input.map(' + tryEvalOrString + '));', context);
          logger.info('[' + request.headers['x-browser-string'] + '] ' + context.output);
          logged = true;
        } catch (e) {
          logger.debug('_log: failed to format console log data', query);
        }
      }

      if (!logged) {
        logger.info('[' + request.headers['x-browser-string'] + '] ' + query);
      }

      response.end();
    },
    '_patch': function patchHandler(uri, body, request, response) {
      var filePath = path.join(__dirname, uri);
      logger.trace('_patch', filePath);

      handleFile(filePath, request, response, true);
    },
    '_default': function defaultHandler(uri, body, request, response) {
      var filePath = path.join(process.cwd(), uri);
      logger.trace('_default', filePath);

      handleFile(filePath, request, response);
    }
  };

  return http.createServer(function(request, response) {
    var uri = url.parse(request.url).pathname;
    var method = uri.split('/')[1];
    var body = '';

    request.on('data', function(data) {
      body += data;
    });
    request.on('end', function() {
      (handlers[method] || handlers._default)(uri, body, request, response);
    });
  });
};

exports.logger = logger;
<|endoftext|>