<commit_before>//
//  Author: Vlad Seryakov vseryakov@gmail.com
//  Nov 2014
//

var util = require('util');
var fs = require('fs');
var repl = require('repl');
var path = require('path');
var logger = require(__dirname + '/logger');
var core = require(__dirname + '/core');
var lib = require(__dirname + '/lib');
var aws = require(__dirname + '/aws');
var ipc = require(__dirname + '/ipc');
var cluster = require('cluster');
var apnagent = require("apnagent");
var gcm = require('node-gcm');

// Messaging and push notifications for mobile and other clients, supports Apple, Google and AWS/SNS push notifications.
var msg = {
    args: [ { name: "apn-cert", type: "list", descr: "List of certificates for APN service in pfx format, can be a file name with .p12 extension or a string with certificate contents encoded with base64, the certificate can be appended with @app to separate different applications where `app` is a bundle identifier of the app" },
            { name: "apn-sandbox", type: "bool", descr: "Enable sandbox mode for testing APN notifications, default is production mode" },
            { name: "gcm-key", type:"list", descr: "Google Cloud Messaging API key(s), a key can be appended with @app for different applications similar to APN certificate file names" },
            { name: "queue-key", descr: "A queue key to subscribe for clients and listen for servers so messages can be exchanged in the queue environment where multiple queue exist" },
            { name: "server-queue-host", descr: "A queue to create for receiving messages from the clients and forwarding to the actual gateways" },
            { name: "server-queue-options", type: "json", descr: "JSON object with options to the queue server" },
            { name: "client-queue-host", descr: "A queue to create where to send all messages instead of actual gateways" },
            { name: "client-queue-options", type: "json", descr: "JSON object with options to the queue client" },
            { name: "shutdown-timeout", type:" int", min: 0, descr: "How long to wait for messages draining out in ms on shutdown before exiting" },],
    apnAgents: {},
    gcmAgents: {},
    shutdownTimeout: 1000,
};

module.exports = msg;

// A callback to be called for APN device uninstalls, it is called by te feedback service,
// the function must be defined as `function(device, timestamp, next)`, the next callback must be called at the end.
msg.onDeviceUninstall = null;

// Initialize supported notification services, it supports jobs agrument convention so can be used in the jobs that
// need to send push notifications in the worker process
msg.init = function(options, callback)
{
    var self = this;
    if (typeof options == "function") callback = options, options = null;
    if (typeof callback != "function") callback = lib.noop;

    // Explicitely configured notification client queue, send all messages there
    if (this.clientQueue) {
        this.clientQueue = ipc.createClient(this.clientQueue, this.clientQueueOptions);
        if (this.clientQueue) return callback();
    }

    // Explicitely configured notification server queue
    if (this.serverQueue) {
        this.serverQueue = ipc.createClient(this.serverQueue, this.serverQueueOptions);
        this.serverQueue.subscribe(this.queueKey || "", function(key, data, next) {
            self.send(lib.jsonParse(data, { obj: 1 }), function(err) {
                if (next) next(err);
            });
        });
    }

    // Direct access to the gateways
    this.initAPN(options);
    this.initGCM(options);
    logger.debug("msg:", "init");
    callback();
}

// Shutdown notification services, wait till all pending messages are sent before calling the callback
msg.shutdown = function(options, callback)
{
    var self = this;
    if (typeof options == "function") callback = options, options = null;
    if (!options) options = {};

    logger.debug("msg:", "shutdown");
    // Wait a little just in case for some left over tasks
    setTimeout(function() {
        lib.parallel([
           function(next) {
               self.closeAPN(next);
           },
           function(next) {
               self.closeGCM(next);
           },
        ], callback);
    }, options.timeout || self.shutdownTimeout);
}

// Gracefully drain all message queues on worker exit
msg.shutdownWorker = function(options, callback)
{
    this.shutdown(options, callback);
}

// Gracefully drain all message queues on web process exit
msg.shutdownWeb = function(options, callback)
{
    this.shutdown(options, callback);
}

// Deliver a notification using the specified service, apple is default.
// Options may contain the following properties:
//  - device_id - device(s) where to send the message to, can be multiple ids separated by , or |
//  - service - which service to use for delivery: sns, apn, gcm
//  - msg - text message to send
//  - badge - badge number to show if supported by the service
//  - type - set type of the message, service specific
//  - id - send id with the notification, this is application specific data, sent as is
//  - queueKey - key where to publish in case of queue existence, i fnot given global key is used or empty key
msg.send = function(options, callback)
{
    var self = this;
    if (typeof callback != "function") callback = lib.noop;
    if (!options || !options.device_id) return callback(lib.newError("invalid device or options"));

    // Queue to the server instead of sending directly
    if (this.clientQueue) return this.clientQueue.publish(options.queueKey || this.queueKey || "", options, callback);

    logger.info("send:", options.id, options.device_id, options.type, options.msg);

    // Determine the service to use from the device token
    var service = options.service || "";
    var devices = lib.strSplit(options.device_id, null, "string");
    lib.forEachSeries(devices, function(device, next) {
        var device_id = device;
        var dev = self.parseDevice(device_id);
        if (!dev.id) return next();
        logger.dev("send:", dev, options.id || "");
        switch (dev.service) {
        case "gcm":
            self.sendGCM(device_id, options, function(err) {
                if (err) logger.error("send:", device_id, err);
                // Stop on explicit fatal errors only
                next(err && err.status >= 500 ? err : null);
            });
            break;

        case "sns":
            self.sendSNS(device_id, options, function(err) {
                if (err) logger.error("send:", device_id, err);
                next(err && err.status >= 500 ? err : null);
            });
            break;

        case "apn":
            self.sendAPN(device_id, options, function(err) {
                if (err) logger.error("send:", device_id, err);
                next(err && err.status >= 500 ? err : null);
            });
            break;

        default:
            logger.error("send:", device, "invalid service");
        }
    }, callback);
}

// Parse device URN and returns an object with all parts into separate properties. A device URN can be in the following format:
//    [service://]device_token[@app]
//
//  - service is optional and defaults to `apn`, other options are `gcm`, `aws`
//  - app is optional and can define an application id which is used by APN for routing to the devices with corresponding APS certificate.
msg.parseDevice = function(device)
{
    var dev = { id: "", service: "apn", app: "default" };
    var d = String(device || "").match(/^([a-z]+\:\/\/)?([^@]+)@?([a-z0-9\.\_-]+)?/);
    if (d) {
        if (d[2] && d[2] != "undefined") dev.id = d[2];
        if (d[1]) dev.service = d[1].replace("://", "");
        if (d[3]) dev.app = d[3];
    }
    // Special cases
    if (dev.id.match(/^arn:aws:sns/)) dev.service = "sns";
    return dev;
}

// Initiaize Apple Push Notification service in the current process, Apple supports multiple connections to the APN gateway but
// not too many so this should be called on the dedicated backend hosts, on multi-core servers every spawn web process will initialize a
// connection to APN gateway.
msg.initAPN = function(options)
{
    var self = this;

    if (!this.apnCert || !this.apnCert.length) return;
    for (var i = 0; i < this.apnCert.length; i++) {
        var file = this.apnCert[i], app = "default";
        if (file.indexOf("@") > -1) {
            var d = file.split("@");
            file = d[0];
            app = d[1];
        }
        var agent = new apnagent.Agent();
        if (file.match(/\.p12$/)) {
            agent.set('pfx file', file);
        } else {
            agent.set("pfx", new Buffer(file, "base64"));
        }
        agent.enable(this.apnSandbox ? "sandbox" : "production");
        agent.on('message:error', function(err, msg) { logger[err && err.code != 10 && err.code != 8 ? "error" : "log"]('apn:message:', err.stack) });
        agent.on('gateway:error', function(err) { logger[err && err.code != 10 && err.code != 8 ? "error" : "log"]('apn:gateway:', err.stack) });
        agent.on('gateway:close', function(err) { logger.info('apn: closed') });
        agent.connect(function(err) { logger[err ? "error" : "log"]('apn:', err || "connected"); });
        agent.decoder.on("error", function(err) { logger.error('apn:decoder:', err.stack); });

        // A posible workaround for the queue being stuck and not sending anything
        agent._timeout = setInterval(function() { agent.queue.process() }, 3000);
        agent._sent = 0;
        logger.debug("initAPN:", app, agent.settings);

        // Only run if we need to handle uninstalls
        if (this.onDeviceUninstall) {
            agent.feedback = new apnagent.Feedback();
            if (file.match(/\.p12$/)) {
                agent.feedback.set('pfx file', file);
            } else {
                agent.feedback.set("pfx", new Buffer(file, "base64"));
            }
            agent.feedback.set('interval', '1h');
            agent.feedback.connect(function(err) { if (err) logger.error('apn: feedback:', err);  });
            agent.feedback.use(function(device, timestamp, next) {
                logger.log('apn: feedback:', device, timestamp);
                self.onDeviceUninstall(device, timestamp, next);
            });
        }

        this.apnAgents[app] = agent;
    }
}

// Close APN agent, try to send all pending messages before closing the gateway connection
msg.closeAPN = function(callback)
{
    var self = this;
    lib.forEach(Object.keys(this.apnAgents), function(key, next) {
        var agent = self.apnAgents[key];
        delete self.apnAgents[key];
        logger.info('closeAPN:', key, agent.settings, 'connected:', agent.connected, 'queue:', agent.queue.length, 'sent:', agent._sent);
        clearInterval(agent._timeout);
        agent.close(function() {
            if (agent.feedback) agent.feedback.close();
            agent.feedback = null;
            logger.info('closeAPN: done', key);
            next();
        });
    }, callback);
}

// Send push notification to an Apple device, returns true if the message has been queued.
//
// The options may contain the following properties:
//  - msg - message text
//  - badge - badge number
//  - type - set type of the packet
//  - id - send id in the user properties
msg.sendAPN = function(device_id, options, callback)
{
    var dev = this.parseDevice(device_id);
    if (!dev.id) return typeof callback == "function" && callback(lib.newError("invalid device:" + device_id));

    // Catch invalid devices before they go into the queue where is it impossible to get the exact source of the error
    try {
        device_id = new Buffer(dev.id, "hex");
    } catch(e) {
        return typeof callback == "function" && callback(lib.newError(e.message));
    }

    var agent = this.apnAgents[dev.app] || this.apnAgents.default;
    if (!agent) return typeof callback == "function" && callback(lib.newError("APN is not initialized for " + dev.id, 500));

    var pkt = agent.createMessage().device(device_id);
    if (options.msg) pkt.alert(options.msg);
    if (options.badge) pkt.badge(options.badge);
    if (options.type) pkt.set("type", options.type);
    if (options.id) pkt.set("id", options.id);
    pkt.send(function(err) { if (!err) agent._sent++; });
    if (typeof callback == "function") process.nextTick(callback);
    return true;
}

// Initialize Google Cloud Messaginh servie to send push notifications to mobile devices
msg.initGCM = function(options)
{
    var self = this;
    if (!this.gcmKey || !this.gcmKey.length) return;
    for (var i = 0; i < this.gcmKey.length; i++) {
        var key = this.gcmKey[i], app = "default";
        if (key.indexOf("@") > -1) {
            var d = key.split("@");
            key = d[0];
            app = d[1];
        }
        var agent = new gcm.Sender(key);
        agent._sent = 0;
        agent._queue = 0;
        this.gcmAgents[app] = agent;
    }
}

// Close GCM connection, flush the queue
msg.closeGCM = function(callback)
{
    var self = this;
    lib.forEach(Object.keys(this.gcmAgents), function(key, next) {
        var agent = self.gcmAgents[key];
        delete self.gcmAgents[key];
        logger.info('closeGCM:', key, 'queue:', agent._queue, 'sent:', agent._sent);

        var n = 0;
        function check() {
            if (!agent._queue || ++n > 600) {
                logger.info('closeGCM: done', key);
                next();
            } else {
                setTimeout(check, 50);
            }
        }
        check();
    }, callback);
}

// Send push notification to an Android device, return true if queued.
msg.sendGCM = function(device_id, options, callback)
{
    var self = this;

    var dev = this.parseDevice(device_id);
    if (!dev.id) return typeof callback == "function" && callback(lib.newError("invalid device:" + device_id));

    var agent = this.gcmAgents[dev.app] || this.gcmAgents.default;
    if (!agent) return typeof callback == "function" && callback(lib.newError("GCM is not initialized for " + dev.id, 500));

    agent._queue++;
    var pkt = new gcm.Message();
    if (options.msg) pkt.addData('msg', options.msg);
    if (options.id) pkt.addData('id', options.id);
    if (options.type) pkt.addData("type", options.type);
    if (options.badge) pkt.addData('badge', options.badge);
    agent.send(pkt, [dev.id], 2, function(err) {
        agent._queue--;
        agent._sent++;
        if (typeof callback == "function") callback(err);
    });
    return true;
}

// Send push notification to a device using AWS SNS service, device_id must be a valid SNS endpoint ARN.
//
// The options may contain the following properties:
//  - msg - message text
//  - badge - badge number
//  - type - set type of the packet
//  - id - send id in the user properties
msg.sendSNS = function(device_id, options, callback)
{
    var self = this;
    var pkt = {};
    var dev = this.parseDevice(device_id);
    if (!dev.id) return typeof callback == "function" && callback(lib.newError("invalid device:" + device_id, 400));

    // Format according to the rules per platform
    if (dev.id.match("/APNS/")) {
        if (options.msg) pkt.alert = options.msg;
        ["id","type","badge"].forEach(function(x) { if (options[x]) pkt[x] = options[x]; });
        pkt = { APNS: JSON.stringify({ aps: pkt }) };
    } else
    if (dev.id.match("/GCM/")) {
        if (options.msg) pkt.message = options.msg;
        ["id","type","badge"].forEach(function(x) { if (options[x]) pkt[x] = options[x]; });
        pkt = { GCM: JSON.stringify({ data: pkt }) };
    }
    aws.snsPublish(dev.id, pkt, function(err) {
        if (typeof callback == "function") callback(err);
    });
    return true;
}
<commit_msg>remove queues from the messaging module, this will be built on top of it<commit_after>//
//  Author: Vlad Seryakov vseryakov@gmail.com
//  Nov 2014
//

var util = require('util');
var fs = require('fs');
var repl = require('repl');
var path = require('path');
var logger = require(__dirname + '/logger');
var core = require(__dirname + '/core');
var lib = require(__dirname + '/lib');
var aws = require(__dirname + '/aws');
var ipc = require(__dirname + '/ipc');
var cluster = require('cluster');
var apnagent = require("apnagent");
var gcm = require('node-gcm');

// Messaging and push notifications for mobile and other clients, supports Apple, Google and AWS/SNS push notifications.
var msg = {
    args: [ { name: "apn-cert", type: "list", descr: "List of certificates for APN service in pfx format, can be a file name with .p12 extension or a string with certificate contents encoded with base64, the certificate can be appended with @app to separate different applications where `app` is a bundle identifier of the app" },
            { name: "apn-sandbox", type: "bool", descr: "Enable sandbox mode for testing APN notifications, default is production mode" },
            { name: "gcm-key", type:"list", descr: "Google Cloud Messaging API key(s), a key can be appended with @app for different applications similar to APN certificate file names" },
            { name: "shutdown-timeout", type:" int", min: 0, descr: "How long to wait for messages draining out in ms on shutdown before exiting" },],
    apnAgents: {},
    gcmAgents: {},
    shutdownTimeout: 1000,
};

module.exports = msg;

// A callback to be called for APN device uninstalls, it is called by te feedback service,
// the function must be defined as `function(device, timestamp, next)`, the next callback must be called at the end.
msg.onDeviceUninstall = null;

// Gracefully drain all message queues on worker exit
msg.shutdownWorker = function(options, callback)
{
    this.shutdown(options, callback);
}

// Gracefully drain all message queues on web process exit
msg.shutdownWeb = function(options, callback)
{
    this.shutdown(options, callback);
}

// Initialize supported notification services, it supports jobs agrument convention so can be used in the jobs that
// need to send push notifications in the worker process
msg.init = function(options, callback)
{
    if (typeof options == "function") callback = options, options = null;

    this.initAPN(options);
    this.initGCM(options);
    logger.debug("msg:", "init");
    if (typeof callback == "function") callback();
}

// Shutdown notification services, wait till all pending messages are sent before calling the callback
msg.shutdown = function(options, callback)
{
    var self = this;
    if (typeof options == "function") callback = options, options = null;
    if (!options) options = {};

    logger.debug("msg:", "shutdown");
    // Wait a little just in case for some left over tasks
    setTimeout(function() {
        lib.parallel([
           function(next) {
               self.closeAPN(next);
           },
           function(next) {
               self.closeGCM(next);
           },
        ], callback);
    }, options.timeout || self.shutdownTimeout);
}

// Deliver a notification using the specified service, apple is default.
// Options may contain the following properties:
//  - device_id - device(s) where to send the message to, can be multiple ids separated by , or |
//  - service - which service to use for delivery: sns, apn, gcm
//  - msg - text message to send
//  - badge - badge number to show if supported by the service
//  - type - set type of the message, service specific
//  - id - send id with the notification, this is application specific data, sent as is
//  - queueKey - key where to publish in case of queue existence, i fnot given global key is used or empty key
msg.send = function(options, callback)
{
    var self = this;
    if (typeof callback != "function") callback = lib.noop;
    if (!options || !options.device_id) return callback(lib.newError("invalid device or options"));

    logger.info("send:", options.id, options.device_id, options.type, options.msg);

    // Determine the service to use from the device token
    var service = options.service || "";
    var devices = lib.strSplit(options.device_id, null, "string");
    lib.forEachSeries(devices, function(device, next) {
        var device_id = device;
        var dev = self.parseDevice(device_id);
        if (!dev.id) return next();
        logger.dev("send:", dev, options.id || "");
        switch (dev.service) {
        case "gcm":
            self.sendGCM(device_id, options, function(err) {
                if (err) logger.error("send:", device_id, err);
                // Stop on explicit fatal errors only
                next(err && err.status >= 500 ? err : null);
            });
            break;

        case "sns":
            self.sendSNS(device_id, options, function(err) {
                if (err) logger.error("send:", device_id, err);
                next(err && err.status >= 500 ? err : null);
            });
            break;

        case "apn":
            self.sendAPN(device_id, options, function(err) {
                if (err) logger.error("send:", device_id, err);
                next(err && err.status >= 500 ? err : null);
            });
            break;

        default:
            logger.error("send:", device, "invalid service");
        }
    }, callback);
}

// Parse device URN and returns an object with all parts into separate properties. A device URN can be in the following format:
//    [service://]device_token[@app]
//
//  - service is optional and defaults to `apn`, other options are `gcm`, `aws`
//  - app is optional and can define an application id which is used by APN for routing to the devices with corresponding APS certificate.
msg.parseDevice = function(device)
{
    var dev = { id: "", service: "apn", app: "default" };
    var d = String(device || "").match(/^([a-z]+\:\/\/)?([^@]+)@?([a-z0-9\.\_-]+)?/);
    if (d) {
        if (d[2] && d[2] != "undefined") dev.id = d[2];
        if (d[1]) dev.service = d[1].replace("://", "");
        if (d[3]) dev.app = d[3];
    }
    // Special cases
    if (dev.id.match(/^arn:aws:sns/)) dev.service = "sns";
    return dev;
}

// Initiaize Apple Push Notification service in the current process, Apple supports multiple connections to the APN gateway but
// not too many so this should be called on the dedicated backend hosts, on multi-core servers every spawn web process will initialize a
// connection to APN gateway.
msg.initAPN = function(options)
{
    var self = this;

    if (!this.apnCert || !this.apnCert.length) return;
    for (var i = 0; i < this.apnCert.length; i++) {
        var file = this.apnCert[i], app = "default";
        if (file.indexOf("@") > -1) {
            var d = file.split("@");
            file = d[0];
            app = d[1];
        }
        var agent = new apnagent.Agent();
        if (file.match(/\.p12$/)) {
            agent.set('pfx file', file);
        } else {
            agent.set("pfx", new Buffer(file, "base64"));
        }
        agent.enable(this.apnSandbox ? "sandbox" : "production");
        agent.on('message:error', function(err, msg) { logger[err && err.code != 10 && err.code != 8 ? "error" : "log"]('apn:message:', err.stack) });
        agent.on('gateway:error', function(err) { logger[err && err.code != 10 && err.code != 8 ? "error" : "log"]('apn:gateway:', err.stack) });
        agent.on('gateway:close', function(err) { logger.info('apn: closed') });
        agent.connect(function(err) { logger[err ? "error" : "log"]('apn:', err || "connected"); });
        agent.decoder.on("error", function(err) { logger.error('apn:decoder:', err.stack); });

        // A posible workaround for the queue being stuck and not sending anything
        agent._timeout = setInterval(function() { agent.queue.process() }, 3000);
        agent._sent = 0;
        logger.debug("initAPN:", app, agent.settings);

        // Only run if we need to handle uninstalls
        if (this.onDeviceUninstall) {
            agent.feedback = new apnagent.Feedback();
            if (file.match(/\.p12$/)) {
                agent.feedback.set('pfx file', file);
            } else {
                agent.feedback.set("pfx", new Buffer(file, "base64"));
            }
            agent.feedback.set('interval', '1h');
            agent.feedback.connect(function(err) { if (err) logger.error('apn: feedback:', err);  });
            agent.feedback.use(function(device, timestamp, next) {
                logger.log('apn: feedback:', device, timestamp);
                self.onDeviceUninstall(device, timestamp, next);
            });
        }

        this.apnAgents[app] = agent;
    }
}

// Close APN agent, try to send all pending messages before closing the gateway connection
msg.closeAPN = function(callback)
{
    var self = this;
    lib.forEach(Object.keys(this.apnAgents), function(key, next) {
        var agent = self.apnAgents[key];
        delete self.apnAgents[key];
        logger.info('closeAPN:', key, agent.settings, 'connected:', agent.connected, 'queue:', agent.queue.length, 'sent:', agent._sent);
        clearInterval(agent._timeout);
        agent.close(function() {
            if (agent.feedback) agent.feedback.close();
            agent.feedback = null;
            logger.info('closeAPN: done', key);
            next();
        });
    }, callback);
}

// Send push notification to an Apple device, returns true if the message has been queued.
//
// The options may contain the following properties:
//  - msg - message text
//  - badge - badge number
//  - type - set type of the packet
//  - id - send id in the user properties
msg.sendAPN = function(device_id, options, callback)
{
    var dev = this.parseDevice(device_id);
    if (!dev.id) return typeof callback == "function" && callback(lib.newError("invalid device:" + device_id));

    // Catch invalid devices before they go into the queue where is it impossible to get the exact source of the error
    try {
        device_id = new Buffer(dev.id, "hex");
    } catch(e) {
        return typeof callback == "function" && callback(lib.newError(e.message));
    }

    var agent = this.apnAgents[dev.app] || this.apnAgents.default;
    if (!agent) return typeof callback == "function" && callback(lib.newError("APN is not initialized for " + dev.id, 500));

    var pkt = agent.createMessage().device(device_id);
    if (options.msg) pkt.alert(options.msg);
    if (options.badge) pkt.badge(options.badge);
    if (options.type) pkt.set("type", options.type);
    if (options.id) pkt.set("id", options.id);
    pkt.send(function(err) { if (!err) agent._sent++; });
    if (typeof callback == "function") process.nextTick(callback);
    return true;
}

// Initialize Google Cloud Messaginh servie to send push notifications to mobile devices
msg.initGCM = function(options)
{
    var self = this;
    if (!this.gcmKey || !this.gcmKey.length) return;
    for (var i = 0; i < this.gcmKey.length; i++) {
        var key = this.gcmKey[i], app = "default";
        if (key.indexOf("@") > -1) {
            var d = key.split("@");
            key = d[0];
            app = d[1];
        }
        var agent = new gcm.Sender(key);
        agent._sent = 0;
        agent._queue = 0;
        this.gcmAgents[app] = agent;
    }
}

// Close GCM connection, flush the queue
msg.closeGCM = function(callback)
{
    var self = this;
    lib.forEach(Object.keys(this.gcmAgents), function(key, next) {
        var agent = self.gcmAgents[key];
        delete self.gcmAgents[key];
        logger.info('closeGCM:', key, 'queue:', agent._queue, 'sent:', agent._sent);

        var n = 0;
        function check() {
            if (!agent._queue || ++n > 600) {
                logger.info('closeGCM: done', key);
                next();
            } else {
                setTimeout(check, 50);
            }
        }
        check();
    }, callback);
}

// Send push notification to an Android device, return true if queued.
msg.sendGCM = function(device_id, options, callback)
{
    var self = this;

    var dev = this.parseDevice(device_id);
    if (!dev.id) return typeof callback == "function" && callback(lib.newError("invalid device:" + device_id));

    var agent = this.gcmAgents[dev.app] || this.gcmAgents.default;
    if (!agent) return typeof callback == "function" && callback(lib.newError("GCM is not initialized for " + dev.id, 500));

    agent._queue++;
    var pkt = new gcm.Message();
    if (options.msg) pkt.addData('msg', options.msg);
    if (options.id) pkt.addData('id', options.id);
    if (options.type) pkt.addData("type", options.type);
    if (options.badge) pkt.addData('badge', options.badge);
    agent.send(pkt, [dev.id], 2, function(err) {
        agent._queue--;
        agent._sent++;
        if (typeof callback == "function") callback(err);
    });
    return true;
}

// Send push notification to a device using AWS SNS service, device_id must be a valid SNS endpoint ARN.
//
// The options may contain the following properties:
//  - msg - message text
//  - badge - badge number
//  - type - set type of the packet
//  - id - send id in the user properties
msg.sendSNS = function(device_id, options, callback)
{
    var self = this;
    var pkt = {};
    var dev = this.parseDevice(device_id);
    if (!dev.id) return typeof callback == "function" && callback(lib.newError("invalid device:" + device_id, 400));

    // Format according to the rules per platform
    if (dev.id.match("/APNS/")) {
        if (options.msg) pkt.alert = options.msg;
        ["id","type","badge"].forEach(function(x) { if (options[x]) pkt[x] = options[x]; });
        pkt = { APNS: JSON.stringify({ aps: pkt }) };
    } else
    if (dev.id.match("/GCM/")) {
        if (options.msg) pkt.message = options.msg;
        ["id","type","badge"].forEach(function(x) { if (options[x]) pkt[x] = options[x]; });
        pkt = { GCM: JSON.stringify({ data: pkt }) };
    }
    aws.snsPublish(dev.id, pkt, function(err) {
        if (typeof callback == "function") callback(err);
    });
    return true;
}
<|endoftext|>


<commit_before><commit_msg>add signings test case<commit_after>describe('SignInController', function() {
    beforeEach(module('anmApp'));

    var $controller;
    var $scope;
    var $location;
    var $http;
    var $httpBackend;

    var user = {
        username: '1',
        password: '1'
    };

    var thisForm = {
        username: {
            $setDirty: function() {}
        },
        password: {
            $setDirty: function() {}
        }
    };

    var signinController;

    beforeEach(inject(function(_$controller_, _$rootScope_, _$location_, _$http_, _$httpBackend_) {
        $controller = _$controller_;
        $scope = _$rootScope_.$new();

        $location = _$location_;
        spyOn($location, 'path');

        $http = _$http_;
        $httpBackend = _$httpBackend_;

        // create controller
        signinController = $controller('SignInController', {
            $scope: $scope,
            $location: $location,
            $http: $http,
            config: {
                apiUrl: ''
            }
        });

        spyOn(thisForm.username, '$setDirty');
        spyOn(thisForm.password, '$setDirty');
    }));

    describe('$scope.gotoMenu', function() {
        it('test location to root', function() {
            //var $scope;
            $scope.gotoMenu();
            expect($location.path).toHaveBeenCalledWith('/');
        });
    });

    describe('$scope.signin', function() {
        it('test signin new use success', () => {
            $scope.error = '';
            $httpBackend.expectPUT('user/login', user).respond(200, '');
            $scope.signin(user, thisForm);
            expect($scope.error).toBe('');
            $httpBackend.flush();
        });
    });

    describe('$scope.signin', function() {
        it('test signin new use fail', () => {
            var errorMsg = 'unit test error!';
            $httpBackend.whenPUT('user/login', user).respond({
                errorInfo: {
                    description: errorMsg
                }
            });
            $scope.signin(user, thisForm);
            $httpBackend.flush();
            expect($scope.error).toBe(errorMsg);
        });
    });

    describe('$scope.signin', function() {
        it('test signin new user: server error', function() {
            $httpBackend.whenPUT('user/login', user).respond(404);
            $scope.signin(user, thisForm);
            $httpBackend.flush();
            expect($scope.error).toBe('Server Error!');
        });
    });

});
<|endoftext|>


<commit_before>// ==UserScript==
// @name        GitHub Download ZIP
// @version     0.1.1
// @description A userscript adds download links so that downloaded filenames include the SHA
// @license     MIT
// @author      Rob Garrison
// @namespace   https://github.com/Mottie
// @include     https://github.com/*
// @run-at      document-idle
// @grant       none
// @require     https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
// @icon        https://assets-cdn.github.com/pinned-octocat.svg
// @updateURL   https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-download-zip.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-download-zip.user.js
// ==/UserScript==
(() => {
	"use strict";

	const zipIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" class="octicon" style="vertical-align:text-top">
		<path d="M28.7 7.2a28.4 28.4 0 0 0-5.9-5.9C21.2.1 20.4 0 20 0H4.5A2.5 2.5 0 0 0 2 2.5v27C2 30.9 3.1 32 4.5 32h23c1.4 0 2.5-1.1 2.5-2.5V10c0-.4-.1-1.2-1.3-2.8zm-4.2-1.7c1 1 1.8 1.8 2.3 2.5H22V3.2c.7.5 1.6 1.3 2.5 2.3zm3.5 24c0 .3-.2.5-.5.5h-23a.5.5 0 0 1-.5-.5v-27c0-.3.2-.5.5-.5H20v7c0 .6.4 1 1 1h7v19.5z"/>
		<path d="M8 2h4v2H8V2zM12 4h4v2h-4V4zM8 6h4v2H8V6zM12 8h4v2h-4V8zM8 10h4v2H8v-2zM12 12h4v2h-4v-2zM8 14h4v2H8v-2zM12 16h4v2h-4v-2zM8 26.5c0 .8.7 1.5 1.5 1.5h5c.8 0 1.5-.7 1.5-1.5v-5c0-.8-.7-1.5-1.5-1.5H12v-2H8v8.5zm6-2.5v2h-4v-2h4z"/>
	</svg>`;
	const span = document.createElement("span");
	span.innerHTML = zipIcon;

	const link = document.createElement("a");
	link.className = "btn btn-outline BtnGroup-item tooltipped tooltipped-s";
	link.setAttribute("aria-label", "Download ZIP");
	link.innerHTML = zipIcon;

	function buildURL(part) {
		const [, user, repo] = window.location.pathname.split("/");
		return `https://api.github.com/repos/${user}/${repo}/zipball/${part}`;
	}

	function updateLinks() {
		// Branch dropdown on main repo page
		const branch = $("button[data-hotkey='w'] span");
		// Download link in "Clone or Download" dropdown
		const downloadLink = $("a[data-ga-click*='download zip']");
		// Repo commits page
		const commits = $(".commits-listing");

		if (downloadLink && branch) {
			downloadLink.href = buildURL(branch.textContent.trim());
			downloadLink.appendChild(span.cloneNode(true));
		}

		// Branch doesn't matter when you're using the SHA (first 7 values)
		if (commits) {
			[...document.querySelectorAll(".commit-group .commit .commit-links-group")].forEach(group => {
				const sha = $(".sha", group).textContent.trim();
				const a = link.cloneNode(true);
				a.href = buildURL(sha);
				group.appendChild(a);
			});
		}
	}

	function $(selector, el) {
		return (el || document).querySelector(selector);
	}

	// DOM targets - to detect GitHub dynamic ajax page loading
	document.addEventListener("ghmo:container", updateLinks);
	updateLinks();

})();
<commit_msg>Fix for Github Download Zip stopped working due to GH changes #72 (#73)<commit_after>// ==UserScript==
// @name        GitHub Download ZIP
// @version     0.1.1
// @description A userscript adds download links so that downloaded filenames include the SHA
// @license     MIT
// @author      Rob Garrison
// @namespace   https://github.com/Mottie
// @include     https://github.com/*
// @run-at      document-idle
// @grant       none
// @require     https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
// @icon        https://assets-cdn.github.com/pinned-octocat.svg
// @updateURL   https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-download-zip.user.js
// @downloadURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-download-zip.user.js
// ==/UserScript==
(() => {
	"use strict";

	const zipIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" class="octicon" style="vertical-align:text-top">
		<path d="M28.7 7.2a28.4 28.4 0 0 0-5.9-5.9C21.2.1 20.4 0 20 0H4.5A2.5 2.5 0 0 0 2 2.5v27C2 30.9 3.1 32 4.5 32h23c1.4 0 2.5-1.1 2.5-2.5V10c0-.4-.1-1.2-1.3-2.8zm-4.2-1.7c1 1 1.8 1.8 2.3 2.5H22V3.2c.7.5 1.6 1.3 2.5 2.3zm3.5 24c0 .3-.2.5-.5.5h-23a.5.5 0 0 1-.5-.5v-27c0-.3.2-.5.5-.5H20v7c0 .6.4 1 1 1h7v19.5z"/>
		<path d="M8 2h4v2H8V2zM12 4h4v2h-4V4zM8 6h4v2H8V6zM12 8h4v2h-4V8zM8 10h4v2H8v-2zM12 12h4v2h-4v-2zM8 14h4v2H8v-2zM12 16h4v2h-4v-2zM8 26.5c0 .8.7 1.5 1.5 1.5h5c.8 0 1.5-.7 1.5-1.5v-5c0-.8-.7-1.5-1.5-1.5H12v-2H8v8.5zm6-2.5v2h-4v-2h4z"/>
	</svg>`;
	const span = document.createElement("span");
	span.innerHTML = zipIcon;

	const link = document.createElement("a");
	link.className = "btn btn-outline BtnGroup-item tooltipped tooltipped-s";
	link.setAttribute("aria-label", "Download ZIP");
	link.innerHTML = zipIcon;

	function buildURL(part) {
		const [, user, repo] = window.location.pathname.split("/");
		return `https://api.github.com/repos/${user}/${repo}/zipball/${part}`;
	}

	function updateLinks() {
		// Branch dropdown on main repo page
		const branch = $("summary[data-hotkey='w'] span");
		// Download link in "Clone or Download" dropdown
		const downloadLink = $("a[data-ga-click*='download zip']");
		// Repo commits page
		const commits = $(".commits-listing");

		if (downloadLink && branch) {
			downloadLink.href = buildURL(branch.textContent.trim());
			downloadLink.appendChild(span.cloneNode(true));
		}

		// Branch doesn't matter when you're using the SHA (first 7 values)
		if (commits) {
			[...document.querySelectorAll(".commit-group .commit .commit-links-group")].forEach(group => {
				const sha = $(".sha", group).textContent.trim();
				const a = link.cloneNode(true);
				a.href = buildURL(sha);
				group.appendChild(a);
			});
		}
	}

	function $(selector, el) {
		return (el || document).querySelector(selector);
	}

	// DOM targets - to detect GitHub dynamic ajax page loading
	document.addEventListener("ghmo:container", updateLinks);
	updateLinks();

})();
<|endoftext|>


<commit_before>"use strict";

import { EventEmitter } from 'events';
import N3 from 'n3';

export default function(
    $q,
    rdfUtils,
    wampUtils,
    sparqlUtils,
    commonUtils,
    CONFIG
) {

    let systems = [];

    class Instance extends EventEmitter {
        constructor() {
            super();
            // this.launchTestSystems();
        }

        subscribe() {
            wampUtils.subscribe([
                {
                    topic:  CONFIG.TOPICS.device_registered,
                    callback: this.onDeviceRegistered.bind(this)
                },
                {
                    topic:  CONFIG.TOPICS.device_turned_off,
                    callback: this.onDeviceTurnedOff.bind(this)
                }
            ]);
        }

        launchTestSystems() {
            setInterval(() => {
                this.onDeviceRegistered([commonUtils.getMockNewSystem()]);
            }, 1000);
        }

        onDeviceRegistered(args) {
            rdfUtils.parseTTL(args[0]).then((triples) => {

                let N3Store = N3.Store();

                N3Store.addPrefixes(CONFIG.SPARQL.prefixes);
                N3Store.addTriples(triples);

                // can not find another way to get system from description
                let system = N3Store.find(null, "ssn:hasSubSystem", null, "")[0];
                let uri = system.subject;
                let label = N3Store.find(uri, "rdfs:label", null, "")[0].object;

                if (!systems.find(function(system) { // if system is new
                    return system.uri === uri;
                })) {
                    systems.push({
                        index: systems.length + 1,
                        uri,
                        name: N3.Util.getLiteralValue(label),
                        isOnline: true
                    });
                    this.emit("systemsUpdate", systems);
                }
            });
        }
        onDeviceTurnedOff(payload) {
            console.info('received message on `turnOff` topic, processing..');
            // prefix saref: <http://ontology.tno.nl/saref#> <coap://winghouse.semiot.ru:60005/meter> saref:hasState saref:OnState.

            rdfUtils.parseTTL(payload[0]).then((triples) => {
                let N3Store = N3.Store();

                N3Store.addPrefixes(CONFIG.SPARQL.prefixes);
                N3Store.addTriples(triples);

                let triple = N3Store.find(null, "saref:hasState", null, "")[0];

                let uri = triple.subject;
                let state = triple.object;

                systems.forEach((system, index) => {
                    if (system.uri === uri) {
                        systems[index].isOnline = state === "http://ontology.tno.nl/saref#OnState";
                        console.info(`changin network state for ${uri}; now is ${systems[index].isOnline ? "online" : "offline"}`);

                        this.emit("systemsUpdate", systems);
                    }
                });
            });
        }
        fetchSystems() {
            let defer = $q.defer();
            sparqlUtils.executeQuery(CONFIG.SPARQL.queries.getAllSystems, function(data) {
                systems = data.results.bindings.sort(function(a, b) {
                    return a.label.value > b.label.value ? 1 : -1;
                }).map(function(binding, index) {
                    return {
                        index: index + 1,
                        name: binding.label.value,
                        uri: binding.uri.value,
                        isOnline: binding.state.value === "saref:OnState"
                    };
                });
                defer.resolve(systems);
            });
            return defer.promise;
        }
        removeSystem(id) {
            console.warn(`remove method is not implemented`);
            /*
            if (this.connection) {
                this.connection.publish(CONFIG.TOPICS.device_remove, [uri]);
            }
            systems.forEach((system, i) => {
                if (system.uri === uri) {
                    systems.splice(i, 1);
                }
            });
            this.emit("systemsUpdate", systems);
            */
        }
        getSystems() {
            return systems;
        }
        getSystemByURI(uri) {
            return systems.find(function(system) {
                return system.uri === uri;
            });
        }
        getSystemsInRange(from, to) {
            let response = [];
            for (let i = from; i < to; i++) {
                if (systems[i]) {
                    response.push(systems[i]);
                }
            }
            return response;
        }
    }

    return new Instance();
}

<commit_msg>Checking device state depends on full saref:state name<commit_after>"use strict";

import { EventEmitter } from 'events';
import N3 from 'n3';

function isDeviceOnline(state) {
    return state === "http://ontology.tno.nl/saref#OnState";
}

export default function(
    $q,
    rdfUtils,
    wampUtils,
    sparqlUtils,
    commonUtils,
    CONFIG
) {

    let systems = [];

    class Instance extends EventEmitter {
        constructor() {
            super();
            // this.launchTestSystems();
        }

        subscribe() {
            wampUtils.subscribe([
                {
                    topic:  CONFIG.TOPICS.device_registered,
                    callback: this.onDeviceRegistered.bind(this)
                },
                {
                    topic:  CONFIG.TOPICS.device_turned_off,
                    callback: this.onDeviceTurnedOff.bind(this)
                }
            ]);
        }

        launchTestSystems() {
            setInterval(() => {
                this.onDeviceRegistered([commonUtils.getMockNewSystem()]);
            }, 1000);
        }

        onDeviceRegistered(args) {
            rdfUtils.parseTTL(args[0]).then((triples) => {

                let N3Store = N3.Store();

                N3Store.addPrefixes(CONFIG.SPARQL.prefixes);
                N3Store.addTriples(triples);

                // can not find another way to get system from description
                let system = N3Store.find(null, "ssn:hasSubSystem", null, "")[0];
                let uri = system.subject;
                let label = N3Store.find(uri, "rdfs:label", null, "")[0].object;

                if (!systems.find(function(system) { // if system is new
                    return system.uri === uri;
                })) {
                    systems.push({
                        index: systems.length + 1,
                        uri,
                        name: N3.Util.getLiteralValue(label),
                        isOnline: true
                    });
                    this.emit("systemsUpdate", systems);
                }
            });
        }
        onDeviceTurnedOff(payload) {
            console.info('received message on `turnOff` topic, processing..');
            // prefix saref: <http://ontology.tno.nl/saref#> <coap://winghouse.semiot.ru:60005/meter> saref:hasState saref:OnState.

            rdfUtils.parseTTL(payload[0]).then((triples) => {
                let N3Store = N3.Store();

                N3Store.addPrefixes(CONFIG.SPARQL.prefixes);
                N3Store.addTriples(triples);

                let triple = N3Store.find(null, "saref:hasState", null, "")[0];

                let uri = triple.subject;
                let state = triple.object;

                systems.forEach((system, index) => {
                    if (system.uri === uri) {
                        systems[index].isOnline = isDeviceOnline(state);
                        console.info(`changin network state for ${uri}; now is ${systems[index].isOnline ? "online" : "offline"}`);

                        this.emit("systemsUpdate", systems);
                    }
                });
            });
        }
        fetchSystems() {
            let defer = $q.defer();
            sparqlUtils.executeQuery(CONFIG.SPARQL.queries.getAllSystems, function(data) {
                systems = data.results.bindings.sort(function(a, b) {
                    return a.label.value > b.label.value ? 1 : -1;
                }).map(function(binding, index) {
                    return {
                        index: index + 1,
                        name: binding.label.value,
                        uri: binding.uri.value,
                        isOnline: isDeviceOnline(binding.state.value)
                    };
                });
                defer.resolve(systems);
            });
            return defer.promise;
        }
        removeSystem(id) {
            console.warn(`remove method is not implemented`);
            /*
            if (this.connection) {
                this.connection.publish(CONFIG.TOPICS.device_remove, [uri]);
            }
            systems.forEach((system, i) => {
                if (system.uri === uri) {
                    systems.splice(i, 1);
                }
            });
            this.emit("systemsUpdate", systems);
            */
        }
        getSystems() {
            return systems;
        }
        getSystemByURI(uri) {
            return systems.find(function(system) {
                return system.uri === uri;
            });
        }
        getSystemsInRange(from, to) {
            let response = [];
            for (let i = from; i < to; i++) {
                if (systems[i]) {
                    response.push(systems[i]);
                }
            }
            return response;
        }
    }

    return new Instance();
}

<|endoftext|>


<commit_before>/**
 * almond 0.2.2 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/jrburke/almond for details
 */
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */

var requirejs, require, define;
(function (undef) {
  var main, req, makeMap, handlers,
      defined = {},
      waiting = {},
      config = {},
      defining = {},
      hasOwn = Object.prototype.hasOwnProperty,
      aps = [].slice;

  function hasProp(obj, prop) {
    return hasOwn.call(obj, prop);
  }

  /**
   * Given a relative module name, like ./something, normalize it to
   * a real name that can be mapped to a path.
   * @param {String} name the relative name
   * @param {String} baseName a real name that the name arg is relative
   * to.
   * @returns {String} normalized name
   */
  function normalize(name, baseName) {
    var nameParts, nameSegment, mapValue, foundMap,
        foundI, foundStarMap, starI, i, j, part,
        baseParts = baseName && baseName.split("/"),
        map = config.map,
        starMap = (map && map['*']) || {};

    //Adjust any relative paths.
    if (name && name.charAt(0) === ".") {
      //If have a base name, try to normalize against it,
      //otherwise, assume it is a top-level require that will
      //be relative to baseUrl in the end.
      if (baseName) {
        //Convert baseName to array, and lop off the last part,
        //so that . matches that "directory" and not name of the baseName's
        //module. For instance, baseName of "one/two/three", maps to
        //"one/two/three.js", but we want the directory, "one/two" for
        //this normalization.
        baseParts = baseParts.slice(0, baseParts.length - 1);

        name = baseParts.concat(name.split("/"));

        //start trimDots
        for (i = 0; i < name.length; i += 1) {
          part = name[i];
          if (part === ".") {
            name.splice(i, 1);
            i -= 1;
          } else if (part === "..") {
            if (i === 1 && (name[2] === '..' || name[0] === '..')) {
              //End of the line. Keep at least one non-dot
              //path segment at the front so it can be mapped
              //correctly to disk. Otherwise, there is likely
              //no path mapping for a path starting with '..'.
              //This can still fail, but catches the most reasonable
              //uses of ..
              break;
            } else if (i > 0) {
              name.splice(i - 1, 2);
              i -= 2;
            }
          }
        }
        //end trimDots

        name = name.join("/");
      }
    }

    //Apply map config if available.
    if ((baseParts || starMap) && map) {
      nameParts = name.split('/');

      for (i = nameParts.length; i > 0; i -= 1) {
        nameSegment = nameParts.slice(0, i).join("/");

        if (baseParts) {
          //Find the longest baseName segment match in the config.
          //So, do joins on the biggest to smallest lengths of baseParts.
          for (j = baseParts.length; j > 0; j -= 1) {
            mapValue = map[baseParts.slice(0, j).join('/')];

            //baseName segment has  config, find if it has one for
            //this name.
            if (mapValue) {
              mapValue = mapValue[nameSegment];
              if (mapValue) {
                //Match, update name to the new value.
                foundMap = mapValue;
                foundI = i;
                break;
              }
            }
          }
        }

        if (foundMap) {
          break;
        }

        //Check for a star map match, but just hold on to it,
        //if there is a shorter segment match later in a matching
        //config, then favor over this star map.
        if (!foundStarMap && starMap && starMap[nameSegment]) {
          foundStarMap = starMap[nameSegment];
          starI = i;
        }
      }

      if (!foundMap && foundStarMap) {
        foundMap = foundStarMap;
        foundI = starI;
      }

      if (foundMap) {
        nameParts.splice(0, foundI, foundMap);
        name = nameParts.join('/');
      }
    }

    return name;
  }

  function makeRequire(relName, forceSync) {
    return function () {
      //A version of a require function that passes a moduleName
      //value for items that may need to
      //look up paths relative to the moduleName
      return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
    };
  }

  function makeNormalize(relName) {
    return function (name) {
      return normalize(name, relName);
    };
  }

  function makeLoad(depName) {
    return function (value) {
      defined[depName] = value;
    };
  }

  function callDep(name) {
    if (hasProp(waiting, name)) {
      var args = waiting[name];
      delete waiting[name];
      defining[name] = true;
      main.apply(undef, args);
    }

    if (!hasProp(defined, name) && !hasProp(defining, name)) {
      throw new Error('No ' + name);
    }
    return defined[name];
  }

  //Turns a plugin!resource to [plugin, resource]
  //with the plugin being undefined if the name
  //did not have a plugin prefix.
  function splitPrefix(name) {
    var prefix,
        index = name ? name.indexOf('!') : -1;
    if (index > -1) {
      prefix = name.substring(0, index);
      name = name.substring(index + 1, name.length);
    }
    return [prefix, name];
  }

  /**
   * Makes a name map, normalizing the name, and using a plugin
   * for normalization if necessary. Grabs a ref to plugin
   * too, as an optimization.
   */
  makeMap = function (name, relName) {
    var plugin,
        parts = splitPrefix(name),
        prefix = parts[0];

    name = parts[1];

    if (prefix) {
      prefix = normalize(prefix, relName);
      plugin = callDep(prefix);
    }

    //Normalize according
    if (prefix) {
      if (plugin && plugin.normalize) {
        name = plugin.normalize(name, makeNormalize(relName));
      } else {
        name = normalize(name, relName);
      }
    } else {
      name = normalize(name, relName);
      parts = splitPrefix(name);
      prefix = parts[0];
      name = parts[1];
      if (prefix) {
        plugin = callDep(prefix);
      }
    }

    //Using ridiculous property names for space reasons
    return {
      f: prefix ? prefix + '!' + name : name, //fullName
      n: name,
      pr: prefix,
      p: plugin
    };
  };

  function makeConfig(name) {
    return function () {
      return (config && config.config && config.config[name]) || {};
    };
  }

  handlers = {
    require: function (name) {
      return makeRequire(name);
    },
    exports: function (name) {
      var e = defined[name];
      if (typeof e !== 'undefined') {
        return e;
      } else {
        return (defined[name] = {});
      }
    },
    module: function (name) {
      return {
        id: name,
        uri: '',
        exports: defined[name],
        config: makeConfig(name)
      };
    }
  };

  main = function (name, deps, callback, relName) {
    var cjsModule, depName, ret, map, i,
        args = [],
        usingExports;

    //Use name if no relName
    relName = relName || name;

    //Call the callback to define the module, if necessary.
    if (typeof callback === 'function') {

      //Pull out the defined dependencies and pass the ordered
      //values to the callback.
      //Default to [require, exports, module] if no deps
      deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
      for (i = 0; i < deps.length; i += 1) {
        map = makeMap(deps[i], relName);
        depName = map.f;

        //Fast path CommonJS standard dependencies.
        if (depName === "require") {
          args[i] = handlers.require(name);
        } else if (depName === "exports") {
          //CommonJS module spec 1.1
          args[i] = handlers.exports(name);
          usingExports = true;
        } else if (depName === "module") {
          //CommonJS module spec 1.1
          cjsModule = args[i] = handlers.module(name);
        } else if (hasProp(defined, depName) ||
            hasProp(waiting, depName) ||
            hasProp(defining, depName)) {
          args[i] = callDep(depName);
        } else if (map.p) {
          map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
          args[i] = defined[depName];
        } else {
          throw new Error(name + ' missing ' + depName);
        }
      }

      ret = callback.apply(defined[name], args);

      if (name) {
        //If setting exports via "module" is in play,
        //favor that over return value and exports. After that,
        //favor a non-undefined return value over exports use.
        if (cjsModule && cjsModule.exports !== undef &&
            cjsModule.exports !== defined[name]) {
          defined[name] = cjsModule.exports;
        } else if (ret !== undef || !usingExports) {
          //Use the return value from the function.
          defined[name] = ret;
        }
      }
    } else if (name) {
      //May just be an object definition for the module. Only
      //worry about defining if have a module name.
      defined[name] = callback;
    }
  };

  requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
    if (typeof deps === "string") {
      if (handlers[deps]) {
        //callback in this case is really relName
        return handlers[deps](callback);
      }
      //Just return the module wanted. In this scenario, the
      //deps arg is the module name, and second arg (if passed)
      //is just the relName.
      //Normalize module name, if it contains . or ..
      return callDep(makeMap(deps, callback).f);
    } else if (!deps.splice) {
      //deps is a config object, not an array.
      config = deps;
      if (callback.splice) {
        //callback is an array, which means it is a dependency list.
        //Adjust args if there are dependencies
        deps = callback;
        callback = relName;
        relName = null;
      } else {
        deps = undef;
      }
    }

    //Support require(['a'])
    callback = callback || function () {};

    //If relName is a function, it is an errback handler,
    //so remove it.
    if (typeof relName === 'function') {
      relName = forceSync;
      forceSync = alt;
    }

    //Simulate async callback;
    if (forceSync) {
      main(undef, deps, callback, relName);
    } else {
      setTimeout(function () {
        main(undef, deps, callback, relName);
      }, 15);
    }

    return req;
  };

  /**
   * Just drops the config on the floor, but returns req in case
   * the config return value is used.
   */
  req.config = function (cfg) {
    config = cfg;
    return req;
  };

  define = function (name, deps, callback) {

    //This module may not have dependencies
    if (!deps.splice) {
      //deps is not an array, so probably means
      //an object literal or factory function for
      //the value. Adjust args.
      callback = deps;
      deps = [];
    }

    if (!hasProp(defined, name) && !hasProp(waiting, name)) {
      waiting[name] = [name, deps, callback];
    }
  };

  define.amd = {
    jQuery: true
  };
}());
<commit_msg>Back to almond 0.2.3<commit_after>/**
 * almond 0.2.3 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/jrburke/almond for details
 */
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */

var requirejs, require, define;
(function (undef) {
  var main, req, makeMap, handlers,
      defined = {},
      waiting = {},
      config = {},
      defining = {},
      hasOwn = Object.prototype.hasOwnProperty,
      aps = [].slice;

  function hasProp(obj, prop) {
    return hasOwn.call(obj, prop);
  }

  /**
   * Given a relative module name, like ./something, normalize it to
   * a real name that can be mapped to a path.
   * @param {String} name the relative name
   * @param {String} baseName a real name that the name arg is relative
   * to.
   * @returns {String} normalized name
   */
  function normalize(name, baseName) {
    var nameParts, nameSegment, mapValue, foundMap,
        foundI, foundStarMap, starI, i, j, part,
        baseParts = baseName && baseName.split("/"),
        map = config.map,
        starMap = (map && map['*']) || {};

    //Adjust any relative paths.
    if (name && name.charAt(0) === ".") {
      //If have a base name, try to normalize against it,
      //otherwise, assume it is a top-level require that will
      //be relative to baseUrl in the end.
      if (baseName) {
        //Convert baseName to array, and lop off the last part,
        //so that . matches that "directory" and not name of the baseName's
        //module. For instance, baseName of "one/two/three", maps to
        //"one/two/three.js", but we want the directory, "one/two" for
        //this normalization.
        baseParts = baseParts.slice(0, baseParts.length - 1);

        name = baseParts.concat(name.split("/"));

        //start trimDots
        for (i = 0; i < name.length; i += 1) {
          part = name[i];
          if (part === ".") {
            name.splice(i, 1);
            i -= 1;
          } else if (part === "..") {
            if (i === 1 && (name[2] === '..' || name[0] === '..')) {
              //End of the line. Keep at least one non-dot
              //path segment at the front so it can be mapped
              //correctly to disk. Otherwise, there is likely
              //no path mapping for a path starting with '..'.
              //This can still fail, but catches the most reasonable
              //uses of ..
              break;
            } else if (i > 0) {
              name.splice(i - 1, 2);
              i -= 2;
            }
          }
        }
        //end trimDots

        name = name.join("/");
      } else if (name.indexOf('./') === 0) {
        // No baseName, so this is ID is resolved relative
        // to baseUrl, pull off the leading dot.
        name = name.substring(2);
      }
    }

    //Apply map config if available.
    if ((baseParts || starMap) && map) {
      nameParts = name.split('/');

      for (i = nameParts.length; i > 0; i -= 1) {
        nameSegment = nameParts.slice(0, i).join("/");

        if (baseParts) {
          //Find the longest baseName segment match in the config.
          //So, do joins on the biggest to smallest lengths of baseParts.
          for (j = baseParts.length; j > 0; j -= 1) {
            mapValue = map[baseParts.slice(0, j).join('/')];

            //baseName segment has  config, find if it has one for
            //this name.
            if (mapValue) {
              mapValue = mapValue[nameSegment];
              if (mapValue) {
                //Match, update name to the new value.
                foundMap = mapValue;
                foundI = i;
                break;
              }
            }
          }
        }

        if (foundMap) {
          break;
        }

        //Check for a star map match, but just hold on to it,
        //if there is a shorter segment match later in a matching
        //config, then favor over this star map.
        if (!foundStarMap && starMap && starMap[nameSegment]) {
          foundStarMap = starMap[nameSegment];
          starI = i;
        }
      }

      if (!foundMap && foundStarMap) {
        foundMap = foundStarMap;
        foundI = starI;
      }

      if (foundMap) {
        nameParts.splice(0, foundI, foundMap);
        name = nameParts.join('/');
      }
    }

    return name;
  }

  function makeRequire(relName, forceSync) {
    return function () {
      //A version of a require function that passes a moduleName
      //value for items that may need to
      //look up paths relative to the moduleName
      return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
    };
  }

  function makeNormalize(relName) {
    return function (name) {
      return normalize(name, relName);
    };
  }

  function makeLoad(depName) {
    return function (value) {
      defined[depName] = value;
    };
  }

  function callDep(name) {
    if (hasProp(waiting, name)) {
      var args = waiting[name];
      delete waiting[name];
      defining[name] = true;
      main.apply(undef, args);
    }

    if (!hasProp(defined, name) && !hasProp(defining, name)) {
      throw new Error('No ' + name);
    }
    return defined[name];
  }

  //Turns a plugin!resource to [plugin, resource]
  //with the plugin being undefined if the name
  //did not have a plugin prefix.
  function splitPrefix(name) {
    var prefix,
        index = name ? name.indexOf('!') : -1;
    if (index > -1) {
      prefix = name.substring(0, index);
      name = name.substring(index + 1, name.length);
    }
    return [prefix, name];
  }

  /**
   * Makes a name map, normalizing the name, and using a plugin
   * for normalization if necessary. Grabs a ref to plugin
   * too, as an optimization.
   */
  makeMap = function (name, relName) {
    var plugin,
        parts = splitPrefix(name),
        prefix = parts[0];

    name = parts[1];

    if (prefix) {
      prefix = normalize(prefix, relName);
      plugin = callDep(prefix);
    }

    //Normalize according
    if (prefix) {
      if (plugin && plugin.normalize) {
        name = plugin.normalize(name, makeNormalize(relName));
      } else {
        name = normalize(name, relName);
      }
    } else {
      name = normalize(name, relName);
      parts = splitPrefix(name);
      prefix = parts[0];
      name = parts[1];
      if (prefix) {
        plugin = callDep(prefix);
      }
    }

    //Using ridiculous property names for space reasons
    return {
      f: prefix ? prefix + '!' + name : name, //fullName
      n: name,
      pr: prefix,
      p: plugin
    };
  };

  function makeConfig(name) {
    return function () {
      return (config && config.config && config.config[name]) || {};
    };
  }

  handlers = {
    require: function (name) {
      return makeRequire(name);
    },
    exports: function (name) {
      var e = defined[name];
      if (typeof e !== 'undefined') {
        return e;
      } else {
        return (defined[name] = {});
      }
    },
    module: function (name) {
      return {
        id: name,
        uri: '',
        exports: defined[name],
        config: makeConfig(name)
      };
    }
  };

  main = function (name, deps, callback, relName) {
    var cjsModule, depName, ret, map, i,
        args = [],
        usingExports;

    //Use name if no relName
    relName = relName || name;

    //Call the callback to define the module, if necessary.
    if (typeof callback === 'function') {

      //Pull out the defined dependencies and pass the ordered
      //values to the callback.
      //Default to [require, exports, module] if no deps
      deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
      for (i = 0; i < deps.length; i += 1) {
        map = makeMap(deps[i], relName);
        depName = map.f;

        //Fast path CommonJS standard dependencies.
        if (depName === "require") {
          args[i] = handlers.require(name);
        } else if (depName === "exports") {
          //CommonJS module spec 1.1
          args[i] = handlers.exports(name);
          usingExports = true;
        } else if (depName === "module") {
          //CommonJS module spec 1.1
          cjsModule = args[i] = handlers.module(name);
        } else if (hasProp(defined, depName) ||
            hasProp(waiting, depName) ||
            hasProp(defining, depName)) {
          args[i] = callDep(depName);
        } else if (map.p) {
          map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
          args[i] = defined[depName];
        } else {
          throw new Error(name + ' missing ' + depName);
        }
      }

      ret = callback.apply(defined[name], args);

      if (name) {
        //If setting exports via "module" is in play,
        //favor that over return value and exports. After that,
        //favor a non-undefined return value over exports use.
        if (cjsModule && cjsModule.exports !== undef &&
            cjsModule.exports !== defined[name]) {
          defined[name] = cjsModule.exports;
        } else if (ret !== undef || !usingExports) {
          //Use the return value from the function.
          defined[name] = ret;
        }
      }
    } else if (name) {
      //May just be an object definition for the module. Only
      //worry about defining if have a module name.
      defined[name] = callback;
    }
  };

  requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
    if (typeof deps === "string") {
      if (handlers[deps]) {
        //callback in this case is really relName
        return handlers[deps](callback);
      }
      //Just return the module wanted. In this scenario, the
      //deps arg is the module name, and second arg (if passed)
      //is just the relName.
      //Normalize module name, if it contains . or ..
      return callDep(makeMap(deps, callback).f);
    } else if (!deps.splice) {
      //deps is a config object, not an array.
      config = deps;
      if (callback.splice) {
        //callback is an array, which means it is a dependency list.
        //Adjust args if there are dependencies
        deps = callback;
        callback = relName;
        relName = null;
      } else {
        deps = undef;
      }
    }

    //Support require(['a'])
    callback = callback || function () {};

    //If relName is a function, it is an errback handler,
    //so remove it.
    if (typeof relName === 'function') {
      relName = forceSync;
      forceSync = alt;
    }

    //Simulate async callback;
    if (forceSync) {
      main(undef, deps, callback, relName);
    } else {
      setTimeout(function () {
        main(undef, deps, callback, relName);
      }, 15);
    }

    return req;
  };

  /**
   * Just drops the config on the floor, but returns req in case
   * the config return value is used.
   */
  req.config = function (cfg) {
    config = cfg;
    return req;
  };

  define = function (name, deps, callback) {

    //This module may not have dependencies
    if (!deps.splice) {
      //deps is not an array, so probably means
      //an object literal or factory function for
      //the value. Adjust args.
      callback = deps;
      deps = [];
    }

    if (!hasProp(defined, name) && !hasProp(waiting, name)) {
      waiting[name] = [name, deps, callback];
    }
  };

  define.amd = {
    jQuery: true
  };
}());
<|endoftext|>


<commit_before>namespace('Sy.Storage');

/**
 * Default implementation of the entity repository
 *
 * @package Sy
 * @subpackage Storage
 * @implements {Sy.Storage.RepositoryInterface}
 * @class
 */

Sy.Storage.Repository = function () {

    this.engine = null;
    this.entityKey = null;
    this.entityConstructor = null;
    this.queue = null;
    this.generator = null;

};

Sy.Storage.Repository.prototype = Object.create(Sy.Storage.RepositoryInterface.prototype, {

    /**
     * Set a queue to handle modifications of entities
     *
     * @param {Sy.QueueInterface} queue
     *
     * @return {Sy.Storage.Repository}
     */

    setQueue: {
        value: function (queue) {

            if (!(queue instanceof Sy.QueueInterface)) {
                throw new TypeError('Invalid queue');
            }

            this.queue = queue;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setGenerator: {
        value: function (generator) {

            if (!(generator instanceof Sy.Lib.Generator.Interface)) {
                throw new TypeError('Invalid generator');
            }

            this.generator = generator;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setEngine: {
        value: function (engine) {

            if (!(engine instanceof Sy.Storage.EngineInterface)) {
                throw new TypeError('Invalid engine');
            }

            this.engine = engine;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setEntityKey: {
        value: function (key) {

            this.entityKey = key;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setEntityConstructor: {
        value: function (constructor) {

            var tmp = new constructor();

            if (!(tmp instanceof Sy.EntityInterface)) {
                throw new TypeError('Invalid entity constructor');
            }

            this.entityConstructor = constructor;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setIndexes: {
        value: function (indexes) {

            if (!(indexes instanceof Array)) {
                throw new TypeError('Invalid indexes definition');
            }

            this.indexes = indexes;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    persist: {
        value: function (entity) {

            if (!(entity instanceof this.entityConstructor)) {
                throw new TypeError('Entity not handled by the repository');
            }

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    remove: {
        value: function (entity) {

            if (!(entity instanceof this.entityConstructor)) {
                throw new TypeError('Entity not handled by the repository');
            }

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    flush: {
        value: function () {

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    findOneBy: {
        value: function (args) {

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    findBy: {
        value: function (args) {

            return this;

        }
    }

});<commit_msg>#6 implemented the persist and remove methods of the repository<commit_after>namespace('Sy.Storage');

/**
 * Default implementation of the entity repository
 *
 * @package Sy
 * @subpackage Storage
 * @implements {Sy.Storage.RepositoryInterface}
 * @class
 */

Sy.Storage.Repository = function () {

    this.engine = null;
    this.entityKey = null;
    this.entityConstructor = null;
    this.queue = null;
    this.generator = null;

};

Sy.Storage.Repository.prototype = Object.create(Sy.Storage.RepositoryInterface.prototype, {

    /**
     * Set a queue to handle modifications of entities
     *
     * @param {Sy.QueueInterface} queue
     *
     * @return {Sy.Storage.Repository}
     */

    setQueue: {
        value: function (queue) {

            if (!(queue instanceof Sy.QueueInterface)) {
                throw new TypeError('Invalid queue');
            }

            this.queue = queue;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setGenerator: {
        value: function (generator) {

            if (!(generator instanceof Sy.Lib.Generator.Interface)) {
                throw new TypeError('Invalid generator');
            }

            this.generator = generator;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setEngine: {
        value: function (engine) {

            if (!(engine instanceof Sy.Storage.EngineInterface)) {
                throw new TypeError('Invalid engine');
            }

            this.engine = engine;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setEntityKey: {
        value: function (key) {

            this.entityKey = key;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setEntityConstructor: {
        value: function (constructor) {

            var tmp = new constructor();

            if (!(tmp instanceof Sy.EntityInterface)) {
                throw new TypeError('Invalid entity constructor');
            }

            this.entityConstructor = constructor;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    setIndexes: {
        value: function (indexes) {

            if (!(indexes instanceof Array)) {
                throw new TypeError('Invalid indexes definition');
            }

            this.indexes = indexes;

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    persist: {
        value: function (entity) {

            if (!(entity instanceof this.entityConstructor)) {
                throw new TypeError('Entity not handled by the repository');
            }

            if (entity.get(this.entityKey) === undefined) {

                entity.set(this.entityKey, this.generator.generate());

                this.queue.set(
                    'create',
                    entity.get(this.entityKey),
                    entity
                );

            } else if (this.queue.has('create', entity.get(this.entityKey))) {

                this.queue.set(
                    'create',
                    entity.get(this.entityKey),
                    entity
                );

            } else {

                this.queue.set(
                    'update',
                    entity.get(this.entityKey),
                    entity
                );

            }

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    remove: {
        value: function (entity) {

            if (!(entity instanceof this.entityConstructor)) {
                throw new TypeError('Entity not handled by the repository');
            }

            var key = entity.get(this.entityKey);

            if (key) {

                if (this.queue.has('create', key)) {
                    this.queue.remove('create', key);
                } else if (this.queue.has('update', key)) {
                    this.queue.remove('update', key);
                } else {
                    this.queue.set(
                        'remove',
                        key,
                        key
                    );
                }

            }

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    flush: {
        value: function () {

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    findOneBy: {
        value: function (args) {

            return this;

        }
    },

    /**
     * @inheritDoc
     */

    findBy: {
        value: function (args) {

            return this;

        }
    }

});<|endoftext|>


<commit_before>/**
 * XadillaX created at 2014-12-24 14:48:57
 *
 * Copyright (c) 2014 XadillaX' Gensokyo, all rights
 * reserved
 */
var Tag = require("./tags/base");
var fs = require("fs");
var zlib = require("zlib");

/**
 * NBT Class
 * @constructor
 * @refer http://minecraft.gamepedia.com/NBT_Format
 */
var NBT = function() {
    this.root = {};
};

/**
 * load NBT structure from buffer
 * @param {Buffer} buff NBT buffer data
 * @param {Function} callback callback function
 */
NBT.prototype.loadFromBuffer = function(buff, callback) {
    try {
        this._buff = buff;
        var offset = 0;
        while(offset < buff.length) {
            var wrapper = Tag.getNextTag(buff, offset);
            var tag = wrapper.tag;
            var len = wrapper.length;

            this.root[tag.id] = tag;
            offset += len;
        }
    } catch(e) {
        return callback(e);
    }

    callback();
};

/**
 * write to compressed buffer
 * @param {Function} callback callback function
 * @param {String} [method] compress method (gzip|deflate)
 */
NBT.prototype.writeToCompressedBuffer = function(callback, method) {
    method = method || "gzip";
    try {
        var _buff = this.writeToBuffer();
        zlib[method](_buff, callback);
    } catch(e) {
        return callback(e);
    }
};

/**
 * write to buffer
 * @return {Buffer} the result buffer data
 */
NBT.prototype.writeToBuffer = function() {
    var buffLength = this.calcBufferLength();
    var buff = new Buffer(buffLength);

    var len = 0;
    for(var key in this.root) {
        if(!this.root.hasOwnProperty(key)) continue;

        var object = this.root[key];
        buff.writeUInt8(object.getTypeId(), len);

        var nameBuff = new Buffer(object.id, "utf8");
        nameBuff.copy(buff, len + 1 + 2);
        buff.writeUInt16BE(nameBuff.length, len + 1);

        len += object.writeBuffer(buff, len + 1 + 2 + nameBuff.length);
        len += (1 + 2 + nameBuff.length);
    }

    return buff;
};

/**
 * select a tag
 * @param {String} tagName the tag name in root
 * @return {Tag} the tag which matches `tagName`
 */
NBT.prototype.select = function(tagName) {
    if(!this.root || !Object.keys(this.root).length) return null;
    if(undefined === this.root[tagName]) return null;

    return this.root[tagName];
};

NBT.prototype.get = NBT.prototype.select;

/**
 * get root object's length
 * @return {Number} root length
 */
NBT.prototype.count = function() {
    if(!this.root) return null;
    return Object.keys(this.root).length;
};

/**
 * get root's keys
 * @return {Number} root's keys
 */
NBT.prototype.keys = function() {
    if(!this.root) return [];
    return Object.keys(this.root);
};

/**
 * inspect
 * @return {String}
 */
NBT.prototype.inspect = function() {
    return "<NBT " + JSON.stringify(this.keys()) + ">";
};

/**
 * toString
 * @return {String}
 */
NBT.prototype.toString = function() {
    return JSON.stringify(this.toJSON(), true, 2);
};

/**
 * load NBT structure from file
 * @param {String} filename NBT filename
 * @param {Function} callback callback function
 */
NBT.prototype.loadFromFile = function(filename, callback) {
    var self = this;
    fs.readFile(filename, function(err, buff) {
        if(err) {
            return callback(err);
        }

        self.loadFromBuffer(buff, callback);
    });
};

/**
 * load NBT structure from zlib compressed file
 * @param {String} filename file's name
 * @param {Function} callback callback function
 */
NBT.prototype.loadFromZlibCompressedFile = function(filename, callback) {
    var self = this;
    fs.readFile(filename, function(err, buff) {
        if(err) {
            return callback(err);
        }

        zlib.unzip(buff, function(err, buff) {
            if(err) {
                return callback(err);
            }

            self.loadFromBuffer(buff, callback);
        });
    });
};

/**
 * write to file
 * @param {String} filename file's name
 * @param {Function} callback callback function
 */
NBT.prototype.writeToFile = function(filename, callback) {
    try {
        fs.writeFile(filename, this.writeToBuffer(), callback);
    } catch(e) {
        return callback(e);
    }
};

/**
 * write to compressed file
 * @param {String} filename file's name
 * @param {Function} callback callback function
 * @param {String} [method] compress method (gzip|deflate)
 */
NBT.prototype.writeToCompressedFile = function(filename, callback, method) {
    this.writeToCompressedBuffer(function(err, buff) {
        if(err) return callback(err);
        fs.writeFile(filename, buff, callback);
    }, method);
};

/**
 * calculate buffer length
 * @return {Number} precalculated buffer length
 */
NBT.prototype.calcBufferLength = function() {
    var len = 0;
    for(var key in this.root) {
        if(!this.root.hasOwnProperty(key)) continue;

        // child type id for 1 byte, child name length for 2 bytes and child
        // name for (child name length) byte(s).
        len += 1;
        len += 2;
        len += Buffer.byteLength(this.root[key].id, "utf8");

        // add the child body's length
        len += this.root[key].calcBufferLength();
    }

    return len;
};

/**
 * toJSON
 * @return {Object} a JSON object
 */
NBT.prototype.toJSON = function() {
    var res = {};
    for(var key in this.root) {
        if(!this.root.hasOwnProperty(key)) continue;
        res[key] = this.root[key].toJSON();
    }

    return res;
};

module.exports = NBT;
<commit_msg>enter<commit_after>/**
 * XadillaX created at 2014-12-24 14:48:57
 *
 * Copyright (c) 2014 XadillaX' Gensokyo, all rights
 * reserved
 */
var Tag = require("./tags/base");
var fs = require("fs");
var zlib = require("zlib");

/**
 * NBT Class
 * @constructor
 * @refer http://minecraft.gamepedia.com/NBT_Format
 */
var NBT = function() {
    this.root = {};
};

/**
 * load NBT structure from buffer
 * @param {Buffer} buff NBT buffer data
 * @param {Function} callback callback function
 */
NBT.prototype.loadFromBuffer = function(buff, callback) {
    try {
        this._buff = buff;
        var offset = 0;
        while(offset < buff.length) {
            var wrapper = Tag.getNextTag(buff, offset);
            var tag = wrapper.tag;
            var len = wrapper.length;

            this.root[tag.id] = tag;
            offset += len;
        }
    } catch(e) {
        return callback(e);
    }

    callback();
};

/**
 * write to compressed buffer
 * @param {Function} callback callback function
 * @param {String} [method] compress method (gzip|deflate)
 */
NBT.prototype.writeToCompressedBuffer = function(callback, method) {
    method = method || "gzip";
    try {
        var _buff = this.writeToBuffer();
        zlib[method](_buff, callback);
    } catch(e) {
        return callback(e);
    }
};

/**
 * write to buffer
 * @return {Buffer} the result buffer data
 */
NBT.prototype.writeToBuffer = function() {
    var buffLength = this.calcBufferLength();
    var buff = new Buffer(buffLength);

    var len = 0;
    for(var key in this.root) {
        if(!this.root.hasOwnProperty(key)) continue;

        var object = this.root[key];
        buff.writeUInt8(object.getTypeId(), len);

        var nameBuff = new Buffer(object.id, "utf8");
        nameBuff.copy(buff, len + 1 + 2);
        buff.writeUInt16BE(nameBuff.length, len + 1);

        len += object.writeBuffer(buff, len + 1 + 2 + nameBuff.length);
        len += (1 + 2 + nameBuff.length);
    }

    return buff;
};

/**
 * select a tag
 * @param {String} tagName the tag name in root
 * @return {Tag} the tag which matches `tagName`
 */
NBT.prototype.select = function(tagName) {
    if(!this.root || !Object.keys(this.root).length) return null;
    if(undefined === this.root[tagName]) return null;

    return this.root[tagName];
};

NBT.prototype.get = NBT.prototype.select;

/**
 * get root object's length
 * @return {Number} root length
 */
NBT.prototype.count = function() {
    if(!this.root) return null;
    return Object.keys(this.root).length;
};

/**
 * get root's keys
 * @return {Number} root's keys
 */
NBT.prototype.keys = function() {
    if(!this.root) return [];
    return Object.keys(this.root);
};

/**
 * inspect
 * @return {String}
 */
NBT.prototype.inspect = function() {
    return "<NBT " + JSON.stringify(this.keys()) + ">";
};

/**
 * toString
 * @return {String}
 */
NBT.prototype.toString = function() {
    return JSON.stringify(this.toJSON(), true, 2);
};

/**
 * load NBT structure from file
 * @param {String} filename NBT filename
 * @param {Function} callback callback function
 */
NBT.prototype.loadFromFile = function(filename, callback) {
    var self = this;
    fs.readFile(filename, function(err, buff) {
        if(err) {
            return callback(err);
        }

        self.loadFromBuffer(buff, callback);
    });
};

/**
 * load NBT structure from zlib compressed file
 * @param {String} filename file's name
 * @param {Function} callback callback function
 */
NBT.prototype.loadFromZlibCompressedFile = function(filename, callback) {
    var self = this;
    fs.readFile(filename, function(err, buff) {
        if(err) {
            return callback(err);
        }

        zlib.unzip(buff, function(err, buff) {
            if(err) {
                return callback(err);
            }

            self.loadFromBuffer(buff, callback);
        });
    });
};

/**
 * write to file
 * @param {String} filename file's name
 * @param {Function} callback callback function
 */
NBT.prototype.writeToFile = function(filename, callback) {
    try {
        fs.writeFile(filename, this.writeToBuffer(), callback);
    } catch(e) {
        return callback(e);
    }
};

/**
 * write to compressed file
 * @param {String} filename file's name
 * @param {Function} callback callback function
 * @param {String} [method] compress method (gzip|deflate)
 */
NBT.prototype.writeToCompressedFile = function(filename, callback, method) {
    this.writeToCompressedBuffer(function(err, buff) {
        if(err) return callback(err);
        fs.writeFile(filename, buff, callback);
    }, method);
};

/**
 * calculate buffer length
 * @return {Number} precalculated buffer length
 */
NBT.prototype.calcBufferLength = function() {
    var len = 0;
    for(var key in this.root) {
        if(!this.root.hasOwnProperty(key)) continue;

        // child type id for 1 byte, child name length for 2 bytes and child
        // name for (child name length) byte(s).
        len += 1;
        len += 2;
        len += Buffer.byteLength(this.root[key].id, "utf8");

        // add the child body's length
        len += this.root[key].calcBufferLength();
    }

    return len;
};

/**
 * toJSON
 * @return {Object} a JSON object
 */
NBT.prototype.toJSON = function() {
    var res = {};
    for(var key in this.root) {
        if(!this.root.hasOwnProperty(key)) continue;
        res[key] = this.root[key].toJSON();
    }

    return res;
};

module.exports = NBT;

<|endoftext|>


<commit_before>/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			exports: {},
/******/ 			id: moduleId,
/******/ 			loaded: false
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.loaded = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}

/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports) {

	/* WEBPACK VAR INJECTION */(function(__dirname) {import path from 'path';
	import open from 'open';
	import compression from 'compression';
	import express from 'express';

	import app from '../server/app'; // express server

	/* eslint-disable no-console */

	const port = 4000;

	app.use(compression());
	app.use(express.static('dist'));

	app.get('*', (req, res) => {
	  res.sendFile(path.join(__dirname, '../dist/index.html'));
	});

	app.listen(port, (err) => {
	  if (err) {
	    console.log(err);
	  } else {
	    open(`http://localhost:${port}`);
	  }
	});

	/* WEBPACK VAR INJECTION */}.call(exports, "/"))

/***/ }
/******/ ]);<commit_msg>bug(test-coverage) remove new.js file<commit_after><|endoftext|>


<commit_before><commit_msg>stdout.write instead of console.log<commit_after><|endoftext|>