<commit_before>import {BasicModel} from "../src/index";

QUnit.module("Basic Models");

QUnit.test("Basic models constructor && proto are correctly defined", function (assert) {
	assert.ok(BasicModel instanceof Function, "BasicModel is defined");

	const NumberModel = BasicModel(Number);

	assert.ok(typeof NumberModel.extend === "function", "test model method extend");
	assert.ok(typeof NumberModel.assert === "function", "test model method assert");
	assert.ok(typeof NumberModel.test === "function", "test model method test");
	assert.ok(typeof NumberModel.validate === "function", "test model method validate");
	assert.ok(NumberModel.definition === Number, "test model prop definition");
	assert.ok(typeof NumberModel.assertions === "object", "test model prop assertions");

	const NumberModelThroughConstructor = new BasicModel(Number);

	assert.ok(typeof NumberModelThroughConstructor.extend === "function", "test new model method extend");
	assert.ok(typeof NumberModelThroughConstructor.assert === "function", "test new model method assert");
	assert.ok(typeof NumberModelThroughConstructor.test === "function", "test new model method test");
	assert.ok(typeof NumberModelThroughConstructor.validate === "function", "test new model method validate");
	assert.ok(NumberModelThroughConstructor.definition === Number, "test new model prop definition");
	assert.ok(typeof NumberModelThroughConstructor.assertions === "object", "test new model prop assertions");
});

QUnit.test("Basic Model with undefined or no definition", function (assert) {

	const UndefinedModel = BasicModel(undefined);
	assert.ok(UndefinedModel instanceof BasicModel, "Model can receive undefined as argument");
	assert.throws(function () {
		BasicModel()
	}, /Error.*Model definition is required/, "Model without definition throws")

});

QUnit.test("Basic model behaviour", function (assert) {

	const NumberModel = BasicModel(Number);
	NumberModel(0); // should not throw
	assert.ok(typeof NumberModel(42) === "number", "should return the original type");
	assert.ok(NumberModel(17) === 17, "should return the original value");
	assert.throws(function () {
		NumberModel("12")
	}, /TypeError/, "test invalid type");
	assert.throws(function () {
		NumberModel(NaN)
	}, /TypeError/, "test NaN is invalid");

})

QUnit.test("Optional basic model behaviour", function (assert) {

	const NumberModel = BasicModel(Number);
	const OptionalNumberModel = BasicModel([Number]);

	assert.throws(function () {
		NumberModel()
	}, /TypeError/, "test undefined value for optional model");

	OptionalNumberModel(); // should not throw

	const OptionalExtendedNumberModel = NumberModel.extend(undefined);

	OptionalExtendedNumberModel(); // should not throw

	assert.throws(function () {
		NumberModel()
	}, /TypeError/, "test undefined value on mandatory prop");

});

QUnit.test("Union basic model", function (assert) {

	const myModel = BasicModel([String, Boolean, Date]);
	myModel("test"); // should not throw
	myModel(true); // should not throw
	myModel(new Date()); // should not throw
	assert.throws(function () {
		myModel()
	}, /TypeError/, "test undefined value");
	assert.throws(function () {
		myModel(0)
	}, /TypeError/, "test invalid type");
	assert.throws(function () {
		myModel(new Date("x"))
	}, /TypeError/, "test invalid date");

	assert.ok(myModel.test("666") === true, "model.test 1/2");
	assert.ok(myModel.test(666) === false, "model.test 2/2");

	myModel.validate("666"); // should not throw
	assert.throws(function () {
		myModel.validate(666)
	}, /TypeError/, "test undefined value");

});

QUnit.test("Basic model default values", function (assert) {

	const myModel = BasicModel([String, Boolean, Date]);
	myModel.defaultTo("blob");
	assert.strictEqual(myModel.default, "blob", "basic model defaultTo store the value as default property")
	assert.strictEqual(myModel(), "blob", "basic model default property is applied when undefined is passed");
	myModel.default = 42;
	assert.throws(function () {
		myModel()
	}, /TypeError.*got Number 42/, "basic model invalid default property still throws TypeError");

});

QUnit.test("Basic model Assertions", function (assert) {

	function isOdd(n) {
		return n % 2 === 1;
	}

	const OddNumber = BasicModel(Number).assert(isOdd);
	assert.strictEqual(OddNumber(17), 17, "passing assertion on basic model 1/2");
	assert.throws(function () {
		OddNumber(18)
	}, /TypeError[\s\S]*isOdd/, "failing assertion on basic model 1/2");

	const RealNumber = BasicModel(Number).assert(isFinite);

	assert.equal(RealNumber(Math.sqrt(1)), 1, "passing assertion on basic model 2/2");
	assert.throws(function () {
		RealNumber(Math.sqrt(-1))
	}, /TypeError[\s\S]*isFinite/, "failing assertion on basic model 2/2");

	function isPrime(n) {
		for (var i = 2, m = Math.sqrt(n); i <= m; i++) {
			if (n % i === 0) return false;
		}
		return true;
	}

	const PrimeNumber = RealNumber
		.extend() // to not add next assertions to RealNumber model
		.assert(Number.isInteger)
		.assert(isPrime);

	PrimeNumber(83);
	assert.throws(function () {
		PrimeNumber(87);
	}, /TypeError[\s\S]*isPrime/, "test multiple assertions 1");
	assert.throws(function () {
		PrimeNumber(7.77);
	}, /TypeError[\s\S]*isInteger/, "test multiple assertions 2");

	const AssertBasic = BasicModel(Number).assert(function (v) {
		return +v.toString() == v
	}, "may throw exception")

	new AssertBasic(0);

	assert.throws(function () {
			new AssertBasic();
		},
		/assertion \"may throw exception\" returned TypeError.*for value undefined/,
		"assertions catch exceptions on Basic models");

});

QUnit.test("Custom error collectors for basic models", function(assert) {

	assert.expect(13);

	const defaultErrorCollector = BasicModel.prototype.errorCollector;
	assert.equal(typeof defaultErrorCollector, "function", "BasicModel has default errorCollector");

	BasicModel.prototype.errorCollector = function (errors) {
		assert.ok(errors.length === 1, "check errors.length global collector");
		const err = errors[0];
		assert.equal(err.expected, Number, "check error.expected global collector");
		assert.equal(err.received, "nope", "check error.received global collector");
		assert.equal(err.message, 'expecting Number, got String "nope"', "check error.message global collector");
	}

	BasicModel(Number)("nope");

	BasicModel.prototype.errorCollector = function (errors) {
		assert.ok(errors.length === 1, 'global custom collector assertion error catch 1/2');
		assert.equal(errors[0].message,
			'assertion \"shouldnt be nope\" returned false for value \"nope\"',
			'global custom collector assertion error catch 2/2');
	}

	BasicModel(String).assert(function (s) {
		return s !== "nope"
	}, "shouldnt be nope")("nope");

	BasicModel(Number).validate("nope", function(errors){
		assert.ok(errors.length === 1, "check errors.length custom collector");
		var err = errors[0];
		assert.equal(err.expected, Number, "check error.expected custom collector");
		assert.equal(err.received, "nope", "check error.received custom collector");
		assert.equal(err.message, 'expecting Number, got String "nope"', "check error.message custom collector");
	});

	BasicModel(String).assert(function(s){ return s !== "nope" }, "shouldnt be nope")
		.validate("nope", function(errors){
			assert.ok(errors.length === 1, 'local custom collector assertion error catch 1/2');
			assert.equal(errors[0].message,
				'assertion \"shouldnt be nope\" returned false for value \"nope\"',
				'local custom collector assertion error catch 2/2');
		});

	BasicModel.prototype.errorCollector = defaultErrorCollector;

});<commit_msg>added more tests for basic model extensions<commit_after>import {BasicModel} from "../src/index";

QUnit.module("Basic Models");

QUnit.test("Basic models constructor && proto are correctly defined", function (assert) {
	assert.ok(BasicModel instanceof Function, "BasicModel is defined");

	const NumberModel = BasicModel(Number);

	assert.ok(typeof NumberModel.extend === "function", "test model method extend");
	assert.ok(typeof NumberModel.assert === "function", "test model method assert");
	assert.ok(typeof NumberModel.test === "function", "test model method test");
	assert.ok(typeof NumberModel.validate === "function", "test model method validate");
	assert.ok(NumberModel.definition === Number, "test model prop definition");
	assert.ok(typeof NumberModel.assertions === "object", "test model prop assertions");

	const NumberModelThroughConstructor = new BasicModel(Number);

	assert.ok(typeof NumberModelThroughConstructor.extend === "function", "test new model method extend");
	assert.ok(typeof NumberModelThroughConstructor.assert === "function", "test new model method assert");
	assert.ok(typeof NumberModelThroughConstructor.test === "function", "test new model method test");
	assert.ok(typeof NumberModelThroughConstructor.validate === "function", "test new model method validate");
	assert.ok(NumberModelThroughConstructor.definition === Number, "test new model prop definition");
	assert.ok(typeof NumberModelThroughConstructor.assertions === "object", "test new model prop assertions");
});

QUnit.test("Basic Model with undefined or no definition", function (assert) {

	const UndefinedModel = BasicModel(undefined);
	assert.ok(UndefinedModel instanceof BasicModel, "Model can receive undefined as argument");
	assert.throws(function () {
		BasicModel()
	}, /Error.*Model definition is required/, "Model without definition throws")

});

QUnit.test("Basic model behaviour", function (assert) {

	const NumberModel = BasicModel(Number);
	NumberModel(0); // should not throw
	assert.ok(typeof NumberModel(42) === "number", "should return the original type");
	assert.ok(NumberModel(17) === 17, "should return the original value");
	assert.throws(function () {
		NumberModel("12")
	}, /TypeError/, "test invalid type");
	assert.throws(function () {
		NumberModel(NaN)
	}, /TypeError/, "test NaN is invalid");

})

QUnit.test("Optional basic model behaviour", function (assert) {

	const NumberModel = BasicModel(Number);
	const OptionalNumberModel = BasicModel([Number]);

	assert.throws(function () {
		NumberModel()
	}, /TypeError/, "test undefined value for optional model");

	OptionalNumberModel(); // should not throw

	const OptionalExtendedNumberModel = NumberModel.extend(undefined);

	OptionalExtendedNumberModel(); // should not throw

	assert.throws(function () {
		NumberModel()
	}, /TypeError/, "test undefined value on mandatory prop");

});

QUnit.test("Union basic model", function (assert) {

	const myModel = BasicModel([String, Boolean, Date]);
	myModel("test"); // should not throw
	myModel(true); // should not throw
	myModel(new Date()); // should not throw
	assert.throws(function () {
		myModel()
	}, /TypeError/, "test undefined value");
	assert.throws(function () {
		myModel(0)
	}, /TypeError/, "test invalid type");
	assert.throws(function () {
		myModel(new Date("x"))
	}, /TypeError/, "test invalid date");

	assert.ok(myModel.test("666") === true, "model.test 1/2");
	assert.ok(myModel.test(666) === false, "model.test 2/2");

	myModel.validate("666"); // should not throw
	assert.throws(function () {
		myModel.validate(666)
	}, /TypeError/, "test undefined value");

});

QUnit.test("Basic model default values", function (assert) {

	const myModel = BasicModel([String, Boolean, Date]);
	myModel.defaultTo("blob");
	assert.strictEqual(myModel.default, "blob", "basic model defaultTo store the value as default property")
	assert.strictEqual(myModel(), "blob", "basic model default property is applied when undefined is passed");
	myModel.default = 42;
	assert.throws(function () {
		myModel()
	}, /TypeError.*got Number 42/, "basic model invalid default property still throws TypeError");

});

QUnit.test("Basic model Assertions", function (assert) {

	function isOdd(n) {
		return n % 2 === 1;
	}

	const OddNumber = BasicModel(Number).assert(isOdd);
	assert.strictEqual(OddNumber(17), 17, "passing assertion on basic model 1/2");
	assert.throws(function () {
		OddNumber(18)
	}, /TypeError[\s\S]*isOdd/, "failing assertion on basic model 1/2");

	const RealNumber = BasicModel(Number).assert(isFinite);

	assert.equal(RealNumber(Math.sqrt(1)), 1, "passing assertion on basic model 2/2");
	assert.throws(function () {
		RealNumber(Math.sqrt(-1))
	}, /TypeError[\s\S]*isFinite/, "failing assertion on basic model 2/2");

	function isPrime(n) {
		for (var i = 2, m = Math.sqrt(n); i <= m; i++) {
			if (n % i === 0) return false;
		}
		return true;
	}

	const PrimeNumber = RealNumber
		.extend() // to not add next assertions to RealNumber model
		.assert(Number.isInteger)
		.assert(isPrime);

	PrimeNumber(83);
	assert.throws(function () {
		PrimeNumber(87);
	}, /TypeError[\s\S]*isPrime/, "test multiple assertions 1");
	assert.throws(function () {
		PrimeNumber(7.77);
	}, /TypeError[\s\S]*isInteger/, "test multiple assertions 2");

	const AssertBasic = BasicModel(Number).assert(function (v) {
		return +v.toString() == v
	}, "may throw exception")

	new AssertBasic(0);

	assert.throws(function () {
			new AssertBasic();
		},
		/assertion \"may throw exception\" returned TypeError.*for value undefined/,
		"assertions catch exceptions on Basic models");

});

QUnit.test("Custom error collectors for basic models", function(assert) {

	assert.expect(13);

	const defaultErrorCollector = BasicModel.prototype.errorCollector;
	assert.equal(typeof defaultErrorCollector, "function", "BasicModel has default errorCollector");

	BasicModel.prototype.errorCollector = function (errors) {
		assert.ok(errors.length === 1, "check errors.length global collector");
		const err = errors[0];
		assert.equal(err.expected, Number, "check error.expected global collector");
		assert.equal(err.received, "nope", "check error.received global collector");
		assert.equal(err.message, 'expecting Number, got String "nope"', "check error.message global collector");
	}

	BasicModel(Number)("nope");

	BasicModel.prototype.errorCollector = function (errors) {
		assert.ok(errors.length === 1, 'global custom collector assertion error catch 1/2');
		assert.equal(errors[0].message,
			'assertion \"shouldnt be nope\" returned false for value \"nope\"',
			'global custom collector assertion error catch 2/2');
	}

	BasicModel(String).assert(function (s) {
		return s !== "nope"
	}, "shouldnt be nope")("nope");

	BasicModel(Number).validate("nope", function(errors){
		assert.ok(errors.length === 1, "check errors.length custom collector");
		var err = errors[0];
		assert.equal(err.expected, Number, "check error.expected custom collector");
		assert.equal(err.received, "nope", "check error.received custom collector");
		assert.equal(err.message, 'expecting Number, got String "nope"', "check error.message custom collector");
	});

	BasicModel(String).assert(function(s){ return s !== "nope" }, "shouldnt be nope")
		.validate("nope", function(errors){
			assert.ok(errors.length === 1, 'local custom collector assertion error catch 1/2');
			assert.equal(errors[0].message,
				'assertion \"shouldnt be nope\" returned false for value \"nope\"',
				'local custom collector assertion error catch 2/2');
		});

	BasicModel.prototype.errorCollector = defaultErrorCollector;

});

QUnit.test("Extensions", function(assert){

	var PositiveInteger = BasicModel(Number)
		.assert(Number.isInteger)
		.assert(n => n >= 0, "should be greater or equal to zero")

	function isPrime(n) {
		for (var i=2, m=Math.sqrt(n); i <= m ; i++){
			if(n%i === 0) return false;
		}
		return n > 1;
	}

	var PrimeNumber = PositiveInteger.extend().assert(isPrime);

	assert.equal(PrimeNumber.definition, Number, "Extension retrieve original definition");
	assert.equal(PrimeNumber.assertions.length, 3, "Extension can add assertions");
	assert.equal(PositiveInteger.assertions.length, 2, "Extension assertions are not added to original model");

})<|endoftext|>


<commit_before>/**
 *      ioBroker.ctrl (ioBroker Controller)
 *
 *      Controls Adapter-Processes
 *
 *
 */

// Change version in io-package.json and start grunt task to modify the version
var version = '0.0.13';
var title = 'io.controller';
process.title = title;

var schedule =      require('node-schedule');
var os =            require('os');
var fs =            require('fs');
var cp =            require('child_process');
var ObjectsCouch =  require(__dirname + '/lib/couch.js');
var StatesRedis =   require(__dirname + '/lib/redis.js');

var logger;
var isDaemon;
var hostname =      os.hostname();

if (process.argv === 'start') {
    isDaemon =      true;
    logger =        require(__dirname + '/lib/logger.js')('info', ['iobroker.log'], true);
} else {
    logger =        require(__dirname + '/lib/logger.js')('info', ['iobroker.log']);
}

var config;
if (!fs.existsSync(__dirname + '/conf/iobroker.json')) {
    logger.error('controller conf/iobroker.json missing - call node iobroker.js setup');
    process.exit(1);
} else {
    config = JSON.parse(fs.readFileSync(__dirname + '/conf/iobroker.json'));
}

// Find first non-loopback ip
var ifaces = os.networkInterfaces();
var ipArr = [];
for (var dev in ifaces) {
    /*jshint loopfunc:true */
    ifaces[dev].forEach(function (details) {
        if (!details.internal) ipArr.push(details.address);
    });
}
var firstIp = ipArr[0];

logger.info('ioBroker.nodejs version ' + version);
logger.info('copyright 2014 hobbyquaker, bluefox');
logger.info(title + ' starting');
logger.info('controller ip: ' + ipArr.join(' '));

var procs = {};

var states = new StatesRedis({

    redis: {
        host: config.redis.host,
        port: config.redis.port,
        options: config.redis.options
    },
    logger: logger,
    change: function (id, state) {
        //console.log('state ' + id + ' = ' + state);
    }
});
//states.subscribe('*');

var objects = new ObjectsCouch({
    host: config.couch.host,
    port: config.couch.port,
    user: config.couch.user,
    pass: config.couch.pass,
    logger: logger,
    connected: function () {
        logger.info('controller couchdb connected');
        setMeta();
        getInstances();
        startAliveInterval();
    },
    change: function (id, obj) {
        if (!id.match(/^system\.adapter\.[a-zA-Z0-9-_]+\.[0-9]+$/)) return;
        logger.info('controller object change ' + id);
        if (procs[id]) {
            // known adapter
            procs[id].config = obj;
            if (procs[id].process) {
                stopInstance(id, function () {
                    if (ipArr.indexOf(obj.common.host) !== -1) {
                        if (obj.common.enabled) startInstance(id);
                    } else {
                        delete procs[id];
                    }
                });
            } else {
                if (ipArr.indexOf(obj.common.host) !== -1) {
                    if (obj.common.enabled) startInstance(id);
                } else {
                    delete procs[id];
                }
            }

        } else {
            // unknown adapter
            if (ipArr.indexOf(obj.common.host) !== -1) {
                procs[id] = {config: obj};
                if (obj.common.enabled) startInstance(id);
            }
        }
    }

});

objects.subscribe('system.adapter.*');

function startAliveInterval() {
    reportStatus();
    setInterval(function () {
        reportStatus();
    }, 15000);
}

function reportStatus() {
    var id = 'system.host.' + hostname;
    states.setState(id + '.alive', {val: true, ack: true, expire: 30});
    states.setState(id + '.load', {val: os.loadavg()[0].toFixed(2), ack: true});
    states.setState(id + '.mem', {val: (100 * os.freemem() / os.totalmem()).toFixed(0), ack: true});

}

function setMeta() {
    var id = 'system.host.' + hostname;
    var obj = {
        _id: id,
        type: 'host',
        common: {
            name:       id,
            process:    title,
            version:    version,
            platform:   'javascript/Node.js',
            cmd:        process.argv[0] + ' ' + process.execArgv.join(' ') + ' ' + process.argv.slice(1).join(' '),
            hostname:   hostname,
            address:    ipArr,
            children: [
                id + '.alive',
                id + '.load',
                id + '.mem'
            ]
        },
        native: {
            process: {
                title:      process.title,
                pid:        process.pid,
                versions:   process.versions,
                env:        process.env
            },
            os: {
                hostname:   hostname,
                type:       os.type(),
                platform:   os.platform(),
                arch:       os.arch(),
                release:    os.release(),
                uptime:     os.uptime(),
                endianness: os.endianness(),
                tmpdir:     os.tmpdir()
            },
            hardware: {
                cpus:       os.cpus(),
                totalmem:   os.totalmem(),
                networkInterfaces: os.networkInterfaces()
            }
        }
    };
    objects.setObject(id, obj);
    var idMem = id + ".mem";
    obj = {
        _id: idMem,
        type: 'state',
        parent: id,
        common: {
            type: 'number',
            name: 'Memory usage',
            unit: '%',
            min: 0,
            max: 100
        },
        native: {}
    };
    objects.setObject(idMem, obj);
    var idLoad = id + ".load";
    obj = {
        _id: idLoad,
        type: 'state',
        parent: id,
        common: {
            unit: '',
            type: 'number',
            name: 'Load Average 1min'
        },
        native: {}
    };
    objects.setObject(idLoad, obj);
    var idAlive = id + ".alive";
    obj = {
        _id: idAlive,
        type: 'state',
        parent: id,
        common: {
            name: 'Host alive',
            type: 'boolean'
        },
        native: {}
    };
    objects.setObject(idAlive, obj);
}

function getInstances() {

    objects.getObjectView('system', 'instance', {}, function (err, doc) {
        if (err && err.status_code === 404) {
            logger.error('controller _design/system missing - call node iobroker.js setup');
            process.exit(1);
            return;
        } else if (doc.rows.length === 0) {
            logger.info('controller no instances found');
        } else {
            logger.info('controller ' + doc.rows.length + ' instance' + (doc.rows.length === 1 ? '' : 's') + ' found');
            var count = 0;
            for (var i = 0; i < doc.rows.length; i++) {

                var instance = doc.rows[i].value;
                logger.info('controller check instance "' + doc.rows[i].id  + '" for host "' + instance.common.host + '"');

                if (ipArr.indexOf(instance.common.host) !== -1) {
                    procs[instance._id] = {config: instance};
                    count++;
                }
            }
            if (count > 0) {
                logger.info('controller starting ' + count + ' instances');
            } else {
                logger.warn('controller does not start any instances on this host');
            }

        }

        initInstances();
    });
}

function initInstances() {
    var c = 0;
    for (var id in procs) {
        if (procs[id].config.common.enabled) {

            setTimeout(function (_id) {
                startInstance(_id);
            }, 2000 * c++, id);

            c += 1;
        }
    }
}

function startInstance(id) {
    var instance = procs[id].config;
    var name = id.split('.')[2];

    switch (instance.common.mode) {
        case 'daemon':
            if (!procs[id].process) {
                allInstancesStopped = false;
                var args = [instance._id.split('.').pop(), instance.common.loglevel || 'info'];
                procs[id].process = cp.fork(__dirname + '/adapter/' + name + '/' + name + '.js', args);
                procs[id].process.on('exit', function (code, signal) {
                    states.setState(id + '.alive', {val: false, ack: true});
                    states.setState(id + '.connected', {val: false, ack: true});
                    if (signal) {
                        logger.warn('controller instance ' + id + ' terminated due to ' + signal);
                    } else if (code === null) {
                        logger.error('controller instance ' + id + ' terminated abnormally');
                    } else {
                        if (procs[id].stopping || isStopping) {
                            logger.info('controller instance ' + id + ' terminated with code ' + code);
                            delete procs[id].stopping;
                            delete procs[id].process;
                            if (isStopping) {
                                for (var i in procs) {
                                    if (procs[i].process) {
                                        return;
                                    }
                                }
                                allInstancesStopped = true;
                            }
                            return;
                        } else {
                            logger.error('controller instance ' + id + ' terminated with code ' + code);
                        }
                    }
                    delete procs[id].process;
                    setTimeout(function (_id) {
                        startInstance(_id);
                    }, 30000, id);
                });
                logger.info('controller instance ' + instance._id + ' started with pid ' + procs[id].process.pid);
            } else {
                logger.warn('controller instance ' + instance._id + ' already running with pid ' + procs[id].process.pid);
            }
            break;
        case 'schedule':
            if (!instance.common.schedule) {
                logger.error(instance._id + ' schedule attribute missing');
                break;
            }
            if (procs[id].schedule) {
                procs[id].schedule.cancel();
                logger.info('controller instance canceled schedule ' + instance._id);
            }
            procs[id].schedule = schedule.scheduleJob(instance.common.schedule, function () {

                var args = [instance._id.split('.').pop(), instance.common.loglevel || 'info'];
                procs[id].process = cp.fork(__dirname + '/adapter/' + name + '/' + name + '.js', args);
                logger.info('controller instance ' + instance._id + ' started with pid ' + procs[id].process.pid);

                procs[id].process.on('exit', function (code, signal) {
                    states.setState(id + '.alive', {val: false, ack: true});
                    if (signal) {
                        logger.warn('controller instance ' + id + ' terminated due to ' + signal);
                    } else if (code === null) {
                        logger.error('controller instance ' + id + ' terminated abnormally');
                    } else {
                        if (code === 0 || code === '0') {
                            logger.info('controller instance ' + id + ' terminated with code ' + code);
                            return;
                        } else {
                            logger.error('controller instance ' + id + ' terminated with code ' + code);
                        }
                    }
                    delete procs[id].process;
                });

            });
            logger.info('controller instance scheduled ' + instance._id + ' ' + instance.common.schedule);

            break;
        case 'subscribe':
            // TODO
            break;
        default:
            logger.error(instance._id + ' invalid mode');

    }

}

function stopInstance(id, callback) {
    var instance = procs[id].config;
    switch (instance.common.mode) {
        case 'daemon':
            if (!procs[id].process) {
                logger.warn('controller instance ' + instance._id + ' not running');
                if (typeof callback === 'function') callback();
            } else {
                logger.info('controller instance ' + instance._id + ' stopping with pid ' + procs[id].process.pid);
                procs[id].stopping = true;
                procs[id].process.kill();
                delete(procs[id].process);
                if (typeof callback === 'function') callback();
            }
            break;
        case 'schedule':
            if (!procs[id].schedule) {
                logger.warn('controller instance ' + instance._id + ' not scheduled');
            } else {
                procs[id].schedule.cancel();
                delete procs[id].schedule;
                logger.info('controller instance canceled schedule ' + instance._id);
            }
            if (typeof callback === 'function') callback();
            break;
        default:
    }
}

function restartInstance(id) {
    stopInstance(id, function () {
        startInstance(id);
    });
}

var isStopping = false;
var stopArr = [];
var allInstancesStopped = false;

function stop() {
    if (isStopping) {

        states.setState('system.host.' + hostname + '.alive', {val: false, ack: true}, function () {
            logger.info('controller force terminating');
            process.exit(1);
            return;
        });

    }

    isStopping = true;

    function waitForInstances() {
        if (!allInstancesStopped) {
            setTimeout(waitForInstances, 100);
        } else {
            states.setState('system.host.' + hostname + '.alive', {val: false, ack: true}, function () {
                logger.info('controller terminated');
                process.exit(0);
            });

        }
    }

    waitForInstances();

    // force after 5s
    setTimeout(function () {
        states.setState('system.host.' + hostname + '.alive', {val: false, ack: true}, function () {
            logger.info('controller force terminated after 5s');
            process.exit(1);
        });
    }, 5000);
}

process.on('SIGINT', function () {
    logger.info('controller received SIGINT');
    stop();
});
process.on('SIGTERM', function () {
    logger.info('controller received SIGTERM');
    stop();
});
<commit_msg>fix stopInstance if isDaemon - Issue #56<commit_after>/**
 *      ioBroker.ctrl (ioBroker Controller)
 *
 *      Controls Adapter-Processes
 *
 *
 */

// Change version in io-package.json and start grunt task to modify the version
var version = '0.0.13';
var title = 'io.controller';
process.title = title;

var schedule =      require('node-schedule');
var os =            require('os');
var fs =            require('fs');
var cp =            require('child_process');
var ObjectsCouch =  require(__dirname + '/lib/couch.js');
var StatesRedis =   require(__dirname + '/lib/redis.js');

var logger;
var isDaemon;
var hostname =      os.hostname();

if (process.argv.indexOf('start') !== -1) {
    isDaemon =      true;
    logger =        require(__dirname + '/lib/logger.js')('info', ['iobroker.log'], true);
} else {
    logger =        require(__dirname + '/lib/logger.js')('info', ['iobroker.log']);
}

var config;
if (!fs.existsSync(__dirname + '/conf/iobroker.json')) {
    logger.error('controller conf/iobroker.json missing - call node iobroker.js setup');
    process.exit(1);
} else {
    config = JSON.parse(fs.readFileSync(__dirname + '/conf/iobroker.json'));
}

// Find first non-loopback ip
var ifaces = os.networkInterfaces();
var ipArr = [];
for (var dev in ifaces) {
    /*jshint loopfunc:true */
    ifaces[dev].forEach(function (details) {
        if (!details.internal) ipArr.push(details.address);
    });
}
var firstIp = ipArr[0];

logger.info('ioBroker.nodejs version ' + version);
logger.info('copyright 2014 hobbyquaker, bluefox');
logger.info(title + ' starting');
logger.info('controller ip: ' + ipArr.join(' '));

var procs = {};

var states = new StatesRedis({

    redis: {
        host: config.redis.host,
        port: config.redis.port,
        options: config.redis.options
    },
    logger: logger,
    change: function (id, state) {
        //console.log('state ' + id + ' = ' + state);
    }
});
//states.subscribe('*');

var objects = new ObjectsCouch({
    host: config.couch.host,
    port: config.couch.port,
    user: config.couch.user,
    pass: config.couch.pass,
    logger: logger,
    connected: function () {
        logger.info('controller couchdb connected');
        setMeta();
        getInstances();
        startAliveInterval();
    },
    change: function (id, obj) {
        if (!id.match(/^system\.adapter\.[a-zA-Z0-9-_]+\.[0-9]+$/)) return;
        logger.info('controller object change ' + id);
        if (procs[id]) {
            // known adapter
            procs[id].config = obj;
            if (procs[id].process) {
                stopInstance(id, function () {
                    if (ipArr.indexOf(obj.common.host) !== -1) {
                        if (obj.common.enabled) startInstance(id);
                    } else {
                        delete procs[id];
                    }
                });
            } else {
                if (ipArr.indexOf(obj.common.host) !== -1) {
                    if (obj.common.enabled) startInstance(id);
                } else {
                    delete procs[id];
                }
            }

        } else {
            // unknown adapter
            if (ipArr.indexOf(obj.common.host) !== -1) {
                procs[id] = {config: obj};
                if (obj.common.enabled) startInstance(id);
            }
        }
    }

});

objects.subscribe('system.adapter.*');

function startAliveInterval() {
    reportStatus();
    setInterval(function () {
        reportStatus();
    }, 15000);
}

function reportStatus() {
    var id = 'system.host.' + hostname;
    states.setState(id + '.alive', {val: true, ack: true, expire: 30});
    states.setState(id + '.load', {val: os.loadavg()[0].toFixed(2), ack: true});
    states.setState(id + '.mem', {val: (100 * os.freemem() / os.totalmem()).toFixed(0), ack: true});

}

function setMeta() {
    var id = 'system.host.' + hostname;
    var obj = {
        _id: id,
        type: 'host',
        common: {
            name:       id,
            process:    title,
            version:    version,
            platform:   'javascript/Node.js',
            cmd:        process.argv[0] + ' ' + process.execArgv.join(' ') + ' ' + process.argv.slice(1).join(' '),
            hostname:   hostname,
            address:    ipArr,
            children: [
                id + '.alive',
                id + '.load',
                id + '.mem'
            ]
        },
        native: {
            process: {
                title:      process.title,
                pid:        process.pid,
                versions:   process.versions,
                env:        process.env
            },
            os: {
                hostname:   hostname,
                type:       os.type(),
                platform:   os.platform(),
                arch:       os.arch(),
                release:    os.release(),
                uptime:     os.uptime(),
                endianness: os.endianness(),
                tmpdir:     os.tmpdir()
            },
            hardware: {
                cpus:       os.cpus(),
                totalmem:   os.totalmem(),
                networkInterfaces: os.networkInterfaces()
            }
        }
    };
    objects.setObject(id, obj);
    var idMem = id + ".mem";
    obj = {
        _id: idMem,
        type: 'state',
        parent: id,
        common: {
            type: 'number',
            name: 'Memory usage',
            unit: '%',
            min: 0,
            max: 100
        },
        native: {}
    };
    objects.setObject(idMem, obj);
    var idLoad = id + ".load";
    obj = {
        _id: idLoad,
        type: 'state',
        parent: id,
        common: {
            unit: '',
            type: 'number',
            name: 'Load Average 1min'
        },
        native: {}
    };
    objects.setObject(idLoad, obj);
    var idAlive = id + ".alive";
    obj = {
        _id: idAlive,
        type: 'state',
        parent: id,
        common: {
            name: 'Host alive',
            type: 'boolean'
        },
        native: {}
    };
    objects.setObject(idAlive, obj);
}

function getInstances() {

    objects.getObjectView('system', 'instance', {}, function (err, doc) {
        if (err && err.status_code === 404) {
            logger.error('controller _design/system missing - call node iobroker.js setup');
            process.exit(1);
            return;
        } else if (doc.rows.length === 0) {
            logger.info('controller no instances found');
        } else {
            logger.info('controller ' + doc.rows.length + ' instance' + (doc.rows.length === 1 ? '' : 's') + ' found');
            var count = 0;
            for (var i = 0; i < doc.rows.length; i++) {

                var instance = doc.rows[i].value;
                logger.info('controller check instance "' + doc.rows[i].id  + '" for host "' + instance.common.host + '"');

                if (ipArr.indexOf(instance.common.host) !== -1) {
                    procs[instance._id] = {config: instance};
                    count++;
                }
            }
            if (count > 0) {
                logger.info('controller starting ' + count + ' instances');
            } else {
                logger.warn('controller does not start any instances on this host');
            }

        }

        initInstances();
    });
}

function initInstances() {
    var c = 0;
    for (var id in procs) {
        if (procs[id].config.common.enabled) {

            setTimeout(function (_id) {
                startInstance(_id);
            }, 2000 * c++, id);

            c += 1;
        }
    }
}

function startInstance(id) {
    var instance = procs[id].config;
    var name = id.split('.')[2];

    switch (instance.common.mode) {
        case 'daemon':
            if (!procs[id].process) {
                allInstancesStopped = false;
                var args = [instance._id.split('.').pop(), instance.common.loglevel || 'info'];
                logger.info('controller startInstance ' + name + '.' + args[0] + ' loglevel=' + args[1]);
                procs[id].process = cp.fork(__dirname + '/adapter/' + name + '/' + name + '.js', args);
                procs[id].process.on('exit', function (code, signal) {
                    states.setState(id + '.alive', {val: false, ack: true});
                    states.setState(id + '.connected', {val: false, ack: true});
                    if (signal) {
                        logger.warn('controller instance ' + id + ' terminated due to ' + signal);
                    } else if (code === null) {
                        logger.error('controller instance ' + id + ' terminated abnormally');
                    } else {
                        if (procs[id].stopping || isStopping) {
                            logger.info('controller instance ' + id + ' terminated with code ' + code);
                            delete procs[id].stopping;
                            delete procs[id].process;
                            if (isStopping) {
                                for (var i in procs) {
                                    if (procs[i].process) {
                                        return;
                                    }
                                }
                                allInstancesStopped = true;
                            }
                            return;
                        } else {
                            logger.error('controller instance ' + id + ' terminated with code ' + code);
                        }
                    }
                    delete procs[id].process;
                    setTimeout(function (_id) {
                        startInstance(_id);
                    }, 30000, id);
                });
                logger.info('controller instance ' + instance._id + ' started with pid ' + procs[id].process.pid);
            } else {
                logger.warn('controller instance ' + instance._id + ' already running with pid ' + procs[id].process.pid);
            }
            break;
        case 'schedule':
            if (!instance.common.schedule) {
                logger.error(instance._id + ' schedule attribute missing');
                break;
            }
            if (procs[id].schedule) {
                procs[id].schedule.cancel();
                logger.info('controller instance canceled schedule ' + instance._id);
            }
            procs[id].schedule = schedule.scheduleJob(instance.common.schedule, function () {

                var args = [instance._id.split('.').pop(), instance.common.loglevel || 'info'];
                procs[id].process = cp.fork(__dirname + '/adapter/' + name + '/' + name + '.js', args);
                logger.info('controller instance ' + instance._id + ' started with pid ' + procs[id].process.pid);

                procs[id].process.on('exit', function (code, signal) {
                    states.setState(id + '.alive', {val: false, ack: true});
                    if (signal) {
                        logger.warn('controller instance ' + id + ' terminated due to ' + signal);
                    } else if (code === null) {
                        logger.error('controller instance ' + id + ' terminated abnormally');
                    } else {
                        if (code === 0 || code === '0') {
                            logger.info('controller instance ' + id + ' terminated with code ' + code);
                            return;
                        } else {
                            logger.error('controller instance ' + id + ' terminated with code ' + code);
                        }
                    }
                    delete procs[id].process;
                });

            });
            logger.info('controller instance scheduled ' + instance._id + ' ' + instance.common.schedule);

            break;
        case 'subscribe':
            // TODO
            break;
        default:
            logger.error(instance._id + ' invalid mode');

    }

}

function stopInstance(id, callback) {
    var instance = procs[id].config;
    switch (instance.common.mode) {
        case 'daemon':
            if (!procs[id].process) {
                logger.warn('controller stopInstance ' + instance._id + ' not running');
                if (typeof callback === 'function') callback();
            } else {
                logger.info('controller stopInstance ' + instance._id + ' killing pid ' + procs[id].process.pid);
                procs[id].stopping = true;
                procs[id].process.kill();
                delete(procs[id].process);
                if (typeof callback === 'function') callback();
            }
            break;
        case 'schedule':
            if (!procs[id].schedule) {
                logger.warn('controller stopInstance ' + instance._id + ' not scheduled');
            } else {
                procs[id].schedule.cancel();
                delete procs[id].schedule;
                logger.info('controller stopInstance canceled schedule ' + instance._id);
            }
            if (typeof callback === 'function') callback();
            break;
        default:
    }
}

function restartInstance(id) {
    stopInstance(id, function () {
        startInstance(id);
    });
}

var isStopping = false;
var stopArr = [];
var allInstancesStopped = false;

function stop() {
    logger.info('stop isStopping=' + isStopping + ' isDaemon=' + isDaemon + ' allInstancesStopped=' + allInstancesStopped);
    if (isStopping) {

        states.setState('system.host.' + hostname + '.alive', {val: false, ack: true}, function () {
            logger.info('controller force terminating');
            process.exit(1);
            return;
        });

    }

    isStopping = true;

    if (isDaemon) {
        // send instances SIGTERM, only needed if running in backround (isDaemon)
        for (var id in procs) {
            stopInstance(id);
        }
    }

    function waitForInstances() {
        if (!allInstancesStopped) {
            setTimeout(waitForInstances, 100);
        } else {
            states.setState('system.host.' + hostname + '.alive', {val: false, ack: true}, function () {
                logger.info('controller terminated');
                process.exit(0);
            });

        }
    }

    waitForInstances();

    // force after 10s
    setTimeout(function () {
        states.setState('system.host.' + hostname + '.alive', {val: false, ack: true}, function () {
            logger.info('controller force terminated after 10s');
            process.exit(1);
        });
    }, 10000);
}

process.on('SIGINT', function () {
    logger.info('controller received SIGINT');
    stop();
});
process.on('SIGTERM', function () {
    logger.info('controller received SIGTERM');
    stop();
});
<|endoftext|>


<commit_before>import test from 'ava';
const mockie = require('../lib/mockie');
const utils = require('../lib/utils');
const thinkCluster = require('think-cluster');
const path = require('path');

function getApplication() {
  return mock.reRequire('../../lib/application');
}

const defaultOption = {
  ROOT_PATH: __dirname,
  // APP_PATH: path.resolve(__dirname,'../runtime')
}

test.afterEach.always(() => {
  mockie.stop();
});

test.serial('normal test', t => {
  mockie.mockThinkMockHttp();
  const App = getApplication();
  let app = new App(defaultOption);
  app.run();
  t.is(think.isCli, true)
})

test.serial('runInMaster', t => {
  mockie.mockCluster(true);
  mockie.mockThinkMockHttp();
  const App = getApplication();
  let app = new App(defaultOption);
  app.parseArgv = ()=>{return {}}
  app.run();
  t.is(app.masterInstance instanceof thinkCluster.Master,true)
})

test.serial('runInAgent', t => {
  mockie.mockCluster(false);
  mockie.mockThinkCluster({agent:true});
  mockie.mockThinkMockHttp();
  const App = getApplication();
  let app = new App(defaultOption);
  app.parseArgv = ()=>{return {}};
  app.run();
  t.is(require('think-cluster').createdServer,true)
})

test.serial('runInWorker', async t => {
  mockie.mockCluster(false);
  mockie.mockThinkCluster({isFirstWorker:()=>{return true}});
  const App = getApplication();
  let app = new App(defaultOption);
  app.parseArgv = ()=>{return {}};
  app.run();
  await utils.sleep(1000)
  t.is(require('think-cluster').capturedEvents,true)
})

test.serial('runInWorker with createServerFn ', async t => {
  mockie.mockCluster(false);
  mockie.mockThinkCluster({isFirstWorker:()=>{return true}});
  const App = getApplication();
  const option = Object.assign({},defaultOption,{APP_PATH: path.resolve(__dirname,'../runtime')});
  let app = new App(option);
  app.parseArgv = ()=>{return {}};
  app.run();
  await utils.sleep(1000)
  t.is(require('think-cluster').capturedEvents,true)
})

test.serial('watcher', t => {
  mockie.mockThinkMockHttp();
  const App = getApplication();
  const watcher = class Watcher {
    constructor(options,cb){
      this.options = options;
      this.cb = cb;
    }
    watch(){
      console.log('start watch');
      this.cb({});
    }
  };
  const option = Object.assign({}, defaultOption, {watcher});
  let app = new App(option);
  app.run();
  t.is(think.isCli, true)
})<commit_msg>fix test case<commit_after>import test from 'ava';
const mock = require('mock-require');
const mockie = require('../lib/mockie');
const utils = require('../lib/utils');
const thinkCluster = require('think-cluster');
const path = require('path');

function getApplication() {
  return mock.reRequire('../../lib/application');
}

const defaultOption = {
  ROOT_PATH: __dirname,
  // APP_PATH: path.resolve(__dirname,'../runtime')
}

test.afterEach.always(() => {
  mockie.stop();
});

test.serial('normal test', t => {
  mockie.mockThinkMockHttp();
  const App = getApplication();
  let app = new App(defaultOption);
  app.run();
  t.is(think.isCli, true)
})

test.serial('runInMaster', t => {
  mockie.mockCluster(true);
  mockie.mockThinkMockHttp();
  const App = getApplication();
  let app = new App(defaultOption);
  app.parseArgv = ()=>{return {}}
  app.run();
  t.is(app.masterInstance instanceof thinkCluster.Master,true)
})

test.serial('runInAgent', t => {
  mockie.mockCluster(false);
  mockie.mockThinkCluster({agent:true});
  mockie.mockThinkMockHttp();
  const App = getApplication();
  let app = new App(defaultOption);
  app.parseArgv = ()=>{return {}};
  app.run();
  t.is(require('think-cluster').createdServer,true)
})

test.serial('runInWorker', async t => {
  mockie.mockCluster(false);
  mockie.mockThinkCluster({isFirstWorker:()=>{return true}});
  const App = getApplication();
  let app = new App(defaultOption);
  app.parseArgv = ()=>{return {}};
  app.run();
  await utils.sleep(1000)
  t.is(require('think-cluster').capturedEvents,true)
})

test.serial('runInWorker with createServerFn ', async t => {
  mockie.mockCluster(false);
  mockie.mockThinkCluster({isFirstWorker:()=>{return true}});
  const App = getApplication();
  const option = Object.assign({},defaultOption,{APP_PATH: path.resolve(__dirname,'../runtime')});
  let app = new App(option);
  app.parseArgv = ()=>{return {}};
  app.run();
  await utils.sleep(1000)
  t.is(require('think-cluster').capturedEvents,true)
})

test.serial('watcher', t => {
  mockie.mockThinkMockHttp();
  const App = getApplication();
  const watcher = class Watcher {
    constructor(options,cb){
      this.options = options;
      this.cb = cb;
    }
    watch(){
      console.log('start watch');
      this.cb({});
    }
  };
  const option = Object.assign({}, defaultOption, {watcher});
  let app = new App(option);
  app.run();
  t.is(think.isCli, true)
})<|endoftext|>


<commit_before>// BASE SETUP
// =============================================================================

// call the packages we need
var express    = require('express'); 		// call express
var app        = express(); 				// define our app using express
var bodyParser = require('body-parser');
var mqtt = require('mqtt');

var mqttClient = mqtt.createClient(1883, '192.168.0.109');

// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

var port = process.env.PORT || 8080; 		// set our port

// ROUTES FOR OUR API
// =============================================================================
var router = express.Router(); 				// get an instance of the express Router

var serialport = require("serialport");
var serial = new serialport.SerialPort("/dev/ttyUSB1", { baudrate : 115200, parser: serialport.parsers.readline("\n") });

serial.on("data", function (data) {
  var status = '';
  if (data.indexOf(' OPEN ') != -1)  status = 'Open';
  if (data.indexOf(' CLOSED ') != -1) status = 'Closed';
  if (data.indexOf(' OPENING ') != -1) status = 'Opening';
  if (data.indexOf(' CLOSING ') != -1) status = 'Closing';
  if (data.indexOf(' UNKNOWN ') != -1) status = 'Unknown';

  if (data.length > 0) {
    if (data.indexOf('Pulsecount') > -1) {
      //console.log('Will publish pulse count data: ' + data);
      mqttClient.publish('wsn/electric_meter', data, function() {
        //console.log("Message has been published");
      });
    }
  }
});

router.put('/switches/:switchID?', function(req, res) {
	console.log('Received command for switch ' + req.params.switchID + ': ' + req.body.state);
	serial.write('SWITCH' + req.params.switchID + req.body.state + '\r');
	res.json({ message: 'sent the command to switch ' + req.params.switchID });
});

router.put('/projector', function(req, res) {
	console.log('Received command for projector: ' + req.body.state);
	serial.write('PROJECTOR' + req.body.state + '\r');
	res.json({ message: 'sent the command to projector'});
});

// more routes for our API will happen here

// REGISTER OUR ROUTES -------------------------------
// all of our routes will be prefixed with /api
app.use('/api', router);

// START THE SERVER
// =============================================================================
app.listen(port);
console.log('Magic happens on port ' + port);

<commit_msg>Added support for mqtt bases command queue<commit_after>// BASE SETUP
// =============================================================================

// call the packages we need
var express    = require('express'); 		// call express
var app        = express(); 				// define our app using express
var bodyParser = require('body-parser');
var mqtt = require('mqtt');

var mqttClient = mqtt.createClient(1883, '192.168.0.109');

// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

var port = process.env.PORT || 8080; 		// set our port

// ROUTES FOR OUR API
// =============================================================================
var router = express.Router(); 				// get an instance of the express Router

var serialport = require("serialport");
var serial = new serialport.SerialPort("/dev/ttyUSB0", { baudrate : 9600, parser: serialport.parsers.readline("\n") });

mqttClient.subscribe('wsn/+/command');

mqttClient.on('message', function(topic, message) {
  //sample message: Switch21_BTN0:ON

  var topicTokens = topic.split('/');
  var item = topicTokens[1];
  console.log(item + ':' + message);
  var indexOfBtn = item.indexOf('_BTN');
  var indexOfSwitch = item.indexOf('Switch');
  if (indexOfSwitch == 0) {
   if (indexOfBtn > 0) {
      var switchId = item.substring(6, indexOfBtn);
      var buttonId = item.substring(indexOfBtn + 4);
      var buttonState = '0';
      if (message == 'ON') buttonState = '1';
      console.log('Received command for light switch ' + switchId + '-' + buttonId + ':' +  message);
      serial.write('LSWITCH' + switchId + ':' + buttonId + ':' + buttonState + '\r');
    } else {
      var switchId = item.substring(6).toUpperCase();
      console.log('Received command for switch ' + switchId + ': ' + message);
      if (switchId == 'PROJECTOR') {
        serial.write(switchId + message + '\r');
      } else {
        serial.write('SWITCH' + switchId + message + '\r');
      }
    }
  }
});

serial.on("data", function (data) {
  var status = '';
  if (data.indexOf(' OPEN ') != -1)  status = 'Open';
  if (data.indexOf(' CLOSED ') != -1) status = 'Closed';
  if (data.indexOf(' OPENING ') != -1) status = 'Opening';
  if (data.indexOf(' CLOSING ') != -1) status = 'Closing';
  if (data.indexOf(' UNKNOWN ') != -1) status = 'Unknown';

  if (data.length > 0) {
    console.log('Data: ' + data);

    var indexOfPulseCount = data.indexOf('Pulsecount');
    var indexOfBtn = data.indexOf('BTN');
    if (indexOfPulseCount > -1) {
      data = data.trim();
      var indexOfPower =  data.indexOf('Power');
      var pulseCount = data.substring(indexOfPulseCount + 12, indexOfPower - 1);
      var power = data.substring(indexOfPower + 7);

      //console.log('Will publish pulse count: "' + pulseCount + '"');
      //console.log('Will publish power: "' + power + '"');

      mqttClient.publish('wsn/electric_meter/pulseCount', pulseCount, function() {
        //console.log("Message has been published");
      });

      mqttClient.publish('wsn/electric_meter/power', power, function() {
        //console.log("Message has been published");
      });
    } else if (indexOfBtn > 0 && data.indexOf('Command') < 0) {
      data = data.trim();
      var switchId = data.substring(data.indexOf('[') + 1, data.indexOf(']'));
      var indexOfColon = data.indexOf(':');
      var buttonId = data.substring(indexOfBtn + 3, indexOfColon);
      var buttonState = data.substring(indexOfColon + 1);
      console.log('Will publish to topic ' + 'wsn/Switch' + switchId + '_BTN' + buttonId + '/state', buttonState == '0' ? 'OFF' : 'ON');
      mqttClient.publish('wsn/Switch' + switchId + '_BTN' + buttonId + '/state', buttonState == '0' ? 'OFF' : 'ON', function() {
        //console.log("Message has been published");
      });
    }
  }
});

router.put('/switches/:switchID?', function(req, res) {
	console.log('Received command for switch ' + req.params.switchID + ': ' + req.body.state);
	serial.write('SWITCH' + req.params.switchID + req.body.state + '\r');
	res.json({ message: 'sent the command to switch ' + req.params.switchID });
});

router.put('/lightSwitches/:switchID?/buttons/:buttonID?', function(req, res) {
	console.log('Received command for light switch ' + req.params.switchID + ': ' + req.body.state);
	serial.write('LSWITCH' + req.params.switchID + ':' + req.params.buttonID + ':' + req.body.state + '\r');
	res.json({ message: 'sent the command to switch ' + req.params.switchID });
});

router.put('/projector', function(req, res) {
	console.log('Received command for projector: ' + req.body.state);
	serial.write('PROJECTOR' + req.body.state + '\r');
	res.json({ message: 'sent the command to projector'});
});

// more routes for our API will happen here

// REGISTER OUR ROUTES -------------------------------
// all of our routes will be prefixed with /api
app.use('/api', router);

// START THE SERVER
// =============================================================================
app.listen(port);
console.log('Magic happens on port ' + port);

<|endoftext|>


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

/* Set by main.js */
var model;
exports.setModel = function(m) {
    model = m;
};

/**
 * Transactions with result data better callback with an Array, so we
 * can apply Result Set Management easily.
 */
var FEATURES = {
    'create-nodes': {
	create: {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		step(function() {
			 t.createNode(req.node, this);
		     }, function(err) {
			 if (err) throw err;

			 t.setAffiliation(req.node, req.from, 'owner', this);
		     }, function(err) {
			 if (err) throw err;

			 t.setSubscription(req.node, req.from, 'subscribed', this);
		     }, cb);
	    }
	}
    },
    subscribe: {
	subscribe: {
	    requiredAffiliation: 'member',
	    transaction: function(req, t, cb) {
		var subscription;
		step(function() {
		    t.getConfig(req.node, this);
		}, function(err, config) {
		    if (err) throw err;

		    subscription = (config.accessModel == 'authorize') ?
			'pending' : 'subscribed';
		    t.setSubscription(req.node, req.from, subscription, this);
		}, function(err, config) {
		    if (err) throw err;

		    req.subscription = subscription;
		    this(null, subscription);
		}, cb);
	    },
	    /* TODO: get owners only if subscription approval pending */
	    ownerNotification: function(req, owners) {
		if (subscription === 'pending') {
		    owners.forEach(function(owner) {
			callFrontend('approve', owner, req.node, req.from);
		    });
		}
	    }
	}
    },
    publish: {
	publish: {
	    requiredAffiliation: 'publisher',
	    transaction: function(req, t, cb) {
		var subscribers;

		step(function() {
			 if (objectIsEmpty(req.items))
			     this(null, []);
			 else {
			     var g = this.group();
			     for(var id in req.items) {
				 if (req.items.hasOwnProperty(id))
				     t.writeItem(req.from, req.node, id, req.items[id], g());
			     }
			 }
		     }, cb);
	    },
	    subscriberNotification: function(req, subscribers) {
		subscribers.forEach(function(subscriber) {
		    callFrontend('notify', subscriber.user, req.node, req.items);
		});
	    }
	}
    },
    'retract-items': {
	retract: {
	    requiredAffiliation: 'publisher',
	    transaction: function(req, t, cb) {
		var subscribers;

		/* TODO: check perms */
		step(function() {
			 if (req.itemIds.length < 1)
			     this(null, []);
			 else {
			     var g = this.group();
			     req.itemIds.forEach(function(itemId) {
				 t.deleteItem(req.node, itemId, g());
			     });
			 }
		     }, cb);
	    },
	    subscriberNotification: function(req, subscribers) {
		subscribers.forEach(function(subscriber) {
		    callFrontend('retracted', subscriber.user, req.node, req.itemIds);
		});
	    }
	}
    },
    'retrieve-items': {
	retrieve: {
	    requiredAffiliation: 'member',
	    transaction: function(req, t, cb) {
		var ids, items;
		step(function() {
			 t.getItemIds(req.node, this);
		     }, function(err, ids_) {
			 if (err) throw err;

			 ids = ids_;
			 if (ids.length < 1)
			     this(null, []);
			 else {
			     var g = this.group();
			     ids.forEach(function(id) {
				 t.getItem(req.node, id, g());
			     });
			 }
		     }, function(err, items) {
			 if (err) throw err;

			 /* Assemble ids & items lists into result dictionary */
			 var result = {};
			 var id, item;
			 while((id = ids.shift()) && (item = items.shift())) {
			     result[id] = item;
			 }
			 this(null, result);
		     }, cb);
	    }
	}
    },
    'retrieve-subscriptions': {
	retrieve: {
	    transaction: function(req, t, cb) {
		t.getSubscriptions(req.from, cb);
	    }
	}
    },
    'retrieve-affiliations': {
	retrieve: {
	    transaction: function(req, t, cb) {
		t.getAffiliations(req.from, cb);
	    }
	}
    },
    'manage-subscriptions': {
	retrieve: {
	    requiredAffiliation: 'moderator',
	    transaction: function(req, t, cb) {
		t.getSubscribers(req.node, cb);
	    }
	},
	modify: {
	    requiredAffiliation: 'moderator',
	    /* TODO: only let owner subscribe users who intended to */
	    transaction: function(req, t, cb) {
		step(function() {
			 if (objectIsEmpty(req.subscriptions)) {
			     this(null);
			     return;
			 }

			 var g = this.group();
			 for(var user in req.subscriptions) {
			     var subscription = req.subscriptions[user];
			     switch(subscription) {
			     case 'subscribed':
				     t.setSubscription(req.node, user, g());
				     break;
			     case 'none':
				     t.setSubscription(req.node, user, g());
				     break;
			     default:
				 throw new errors.BadRequest(subscription + ' is no subscription type');
			     }
			 }
		     }, cb);
	    },
	    subscriberNotification: function(req, subscribers) {
		/* TODO: send out if subscriber !== req.from */
	    }
	}
    },
    'modify-affiliations': {
	retrieve: {
	    requiredAffiliation: 'owner',
	    /* TODO: outcast only if req.affiliation == 'owner' or 'publisher' */
	    transaction: function(req, t, cb) {
		t.getAffiliated(req.node, cb);
	    }
	},
	modify: {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		if (objectIsEmpty(req.affiliations)) {
		    this(null);
		    return;
		}

		step(function() {
			 var g = this.group();
			 for(var user in req.affiliations) {
			     var affiliation = req.affiliations[user];
			     /* TODO: validate affiliation */
			     t.setAffiliation(req.node, user, affiliation, g());
			 }
		     }, cb);
	    }
	}
    },
    'config-node': {
	retrieve: {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		t.getConfig(req.node, cb);
	    }
	},
	modify: {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		t.setConfig(req.node, { title: req.title,
					accessModel: req.accessModel,
					publishModel: req.publishModel
				      }, cb);
	    }
	}
    },
    'get-pending': {
	'list-nodes': {
	    transaction: function(req, t, cb) {
		t.getPendingNodes(req.from, cb);
	    }
	},
	'get-for-node': {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		step(function() {
		    t.getPending(req.node, this);
		}, function(err, users) {
		    if (err) throw err;

		    req.pendingUsers = users;
		    this(null);
		}, cb);
	    },
	    /* TODO: actually doesn't need owners */
	    ownerNotification: function(req, owners) {
		req.pendingUsers.forEach(function(user) {
		    callFrontend('approve', req.from, req.node, user);
		});
	    }
	}
    },
    /* Actually no pubsub feature but fits here snugly */
    register: {
	register: {
	    transaction: function(req, t, cb) {
		var m, user = req.from;
		if ((m = user.match(/^.+:(.+)$/)))
		    user = m[1]; /* strip proto prefix */

		/* TODO: make configurable */
		var nodes = ['channel', 'mood', 'geo/current',
			     'geo/future', 'geo/previous'].map(function(name) {
		    return '/user/' + user + '/' + name;
		});

		step(function() {
		    var g = this.group();
		    nodes.forEach(function(node) {
			t.createNode(node, g());
		    });
		}, function(err) {
		    if (err) throw err;

		    var g = this.group();
		    nodes.forEach(function(node) {
			t.setAffiliation(node, req.from, 'owner', g());
			t.setSubscription(node, req.from, 'subscribed', g());
		    });
		}, cb);
	    }
	}
    },
    /* Actually no pubsub feature but fits here snugly */
    'browse-nodes': {
	list: {
	    transaction: function(req, t, cb) {
		t.listNodes(cb);
	    }
	}
    }
};

exports.pubsubFeatures = function() {
    var result = [];
    for(var f in FEATURES)
	result.push(f);
    return result;
};

exports.request = function(req) {
    var feature = FEATURES[req.feature];
    var operation = feature && feature[req.operation];
    req.affiliation = 'none';

    if (!operation) {
	req.callback(new errors.FeatureNotImplemented('Operation not yet supported'));
	return;
    }
    var debug = function(s) {
	console.log(req.from + ' >> ' + req.feature + '/' + req.operation + ': ' + s);
    };

    /* TODO: no underscores */
    if (req.node && req.from) {
	var nodeM = req.node.match(/^\/user\/(.+?)\/([a-zA-Z0-9\/\-]+)$/);
	var userM = req.from.match(/^(.+?):(.+)$/);
	if (nodeM && nodeM[1] === userM[2])
	    req.affiliation = 'owner';
    }

    model.transaction(function(err, t) {
	if (err) {
	    req.callback(err);
	    return;
	}

	var steps = [function(err) {
			 /* Unfortunately, step starts with err = [] */
			 this(null);
		     }];

	/* Retrieve affiliation if needed */
	if (operation.requiredAffiliation &&
	    !isAffiliationSubset(operation.requiredAffiliation, req.affiliation)) {

	    var config;
	    steps.push(function(err) {
		if (err) throw err;

		t.getConfig(req.node, this);
	    }, function(err, config_) {
		if (err) throw err;

		config = config_;

		t.getAffiliation(req.node, req.from, this);
	    }, function(err, affiliation) {
		if (err) throw err;

		req.affiliation = affiliation || req.affiliation;

		t.getSubscription(req.node, req.from, this);
	    }, function(err, subscription) {
		if (err) throw err;

		if (req.affiliation === 'none' &&
		    (!config.accessModel || config.accessModel === 'open')) {
		    /* 'open' model: members don't need to be approved */
		    req.affiliation = 'member';
		} else if (req.affiliation === 'member' &&
			   config.publishModel === 'publisher' &&
			   subscription === 'subscribed') {
		    /* set affiliation = 'publisher' only if user subscribed */
		    req.affiliation = 'publisher';
		}

		if (isAffiliationSubset(operation.requiredAffiliation, req.affiliation))
		    this();
		else
		    this(new errors.Forbidden(operation.requiredAffiliation + ' required'));
	    });
	}

	/* Run operation transaction first */
	var transactionResults;
	steps.push(function(err) {
	    if (err) throw err;
	    debug('transaction');

	    operation.transaction(req, t, this);
	}, function(err) {
	    if (err) throw err;
	    debug('transaction done');

	    /* Regardless of the following steps, we pass
	     * the operation's transaction result to the
	     * final callback.
	     */
	    transactionResults = arguments;
	    /* And continue:
	     */
	    this(null);
	});
	var owners;
	if (operation.ownerNotification) {
	    /* For owner notification, get the list of owners while
	     * still inside transaction.
	     */
	    steps.push(function(err) {
		if (err) throw err;

		t.getOwners(req.node, this);
	    }, function(err, owners_) {
		if (err) throw err;

		owners = owners_;
		this(null);
	    });
	}
        var subscribers;
	if (operation.subscriberNotification) {
	    /* For subscriber notification, get the list of subscribers
	     * while still inside transaction.
	     */
	    steps.push(function(err) {
		if (err) throw err;

		t.getSubscribers(req.node, this);
	    }, function(err, subscribers_) {
		if (err) throw err;

		subscribers = subscribers_;
		this(null);
	    });
	}
	/* Finalize transaction
	 */
	steps.push(function(err) {
	    if (err) {
		var that = this;
		debug('transaction rollback: ' + (err.message || JSON.stringify(err)));
		t.rollback(function() {
		    /* Keep error despite successful rollback */
		    that(err);
		});
	    } else {
		debug('transaction commit');
		t.commit(this);
	    }
	});
	if (operation.ownerNotification) {
	    steps.push(function(err) {
		if (err) throw err;

		operation.ownerNotification(req, owners);
		this(null);
	    });
	}
	if (operation.subscriberNotification) {
	    /* Transaction successful? Call subscriberNotification. */
	    steps.push(function(err) {
		if (err) throw err;

		operation.subscriberNotification(req, subscribers);
		this(null);
	    });
	}
	/* Last step: return to caller (view) */
	steps.push(function(err) {
	    debug('callback');
	    if (err && req.callback)
		req.callback(err);
	    else if (req.callback)
		req.callback.apply(req, transactionResults);
	});

	/* Finally, run all the steps we assembled above */
	step.apply(null, steps);
    });
};

/*
 * This is not a request, but used internally to send out presence
 * probes at startup.
 */
exports.getAllSubscribers = function(cb) {
    model.transaction(function(err, t) {
	if (err) {
	    cb(err);
	    return;
	}

	t.getAllSubscribers(function(err, subscribers) {
	    if (err) {
		t.rollback(function() {
			       cb(err);
			   });
		return;
	    }

	    t.commit(function() {
		cb(null, subscribers);
	    });
	});
    });

};

/**
 * Affiliations comparison
 */

var AFFILIATION_SUBSETS = {
    owner: ['moderator', 'publisher', 'member'],
    moderator: ['publisher', 'member'],
    publisher: ['member']
};
function isAffiliationSubset(subset, affiliation) {
    return subset === affiliation ||
	   (AFFILIATION_SUBSETS.hasOwnProperty(affiliation) &&
	    AFFILIATION_SUBSETS[affiliation].indexOf(subset) >= 0);
}

/**
 * Frontend hooking
 */

var frontends = {};

/**
 * Hook frontend for uri prefix
 */
exports.hookFrontend = function(proto, hooks) {
    frontends[proto] = hooks;
};

/**
 * Call named hook by uri prefix
 */
function callFrontend(hook, uri) {
    var colonPos = uri.indexOf(':');
    if (colonPos > 0) {
	var proto = uri.substr(0, colonPos);
	uri = uri.substr(colonPos + 1);
    } else
	return;

    var args = Array.prototype.slice.call(arguments, 1);
    var frontend = frontends.hasOwnProperty(proto) && frontends[proto];
    var hookFun = frontend && frontend.hasOwnProperty(hook) && frontend[hook];
console.log({callFrontend:arguments,frontent:frontend,hookFun:hookFun,args:args});

    if (hookFun) {
	return hookFun.apply(frontend, args);
    }
};

/* A helper */

function objectIsEmpty(o) {
    for(var k in o) {
	if (o.hasOwnProperty(k))
	    return false;
    }
    return true;
}
<commit_msg>controller: replace ownerNotification with afterTransaction<commit_after>var step = require('step');
var errors = require('./errors');

/* Set by main.js */
var model;
exports.setModel = function(m) {
    model = m;
};

/**
 * Transactions with result data better callback with an Array, so we
 * can apply Result Set Management easily.
 */
var FEATURES = {
    'create-nodes': {
	create: {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		step(function() {
			 t.createNode(req.node, this);
		     }, function(err) {
			 if (err) throw err;

			 t.setAffiliation(req.node, req.from, 'owner', this);
		     }, function(err) {
			 if (err) throw err;

			 t.setSubscription(req.node, req.from, 'subscribed', this);
		     }, cb);
	    }
	}
    },
    subscribe: {
	subscribe: {
	    requiredAffiliation: 'member',
	    transaction: function(req, t, cb) {
		var subscription;
		step(function() {
		    t.getConfig(req.node, this);
		}, function(err, config) {
		    if (err) throw err;

		    subscription = (config.accessModel == 'authorize') ?
			'pending' : 'subscribed';
		    t.setSubscription(req.node, req.from, subscription, this);
		}, function(err, config) {
		    if (err) throw err;

		    req.subscription = subscription;
		    /* get owners only if subscription approval pending */
		    if (subscription === 'pending') {
			step(function() {
				 t.getOwners(req.node, this);
			     }, function(err, owners) {
				 if (err) throw err;

				 req.owners = owners;
			     }, this);
		    } else
			this(null, subscription);
		}, cb);
	    },
	    afterTransaction: function(req) {
		if (req.subscription === 'pending' && req.owners)
		    req.owners.forEach(function(owner) {
			callFrontend('approve', owner, req.node, req.from);
		    });
	    }
	}
    },
    publish: {
	publish: {
	    requiredAffiliation: 'publisher',
	    transaction: function(req, t, cb) {
		var subscribers;

		step(function() {
			 if (objectIsEmpty(req.items))
			     this(null, []);
			 else {
			     var g = this.group();
			     for(var id in req.items) {
				 if (req.items.hasOwnProperty(id))
				     t.writeItem(req.from, req.node, id, req.items[id], g());
			     }
			 }
		     }, cb);
	    },
	    subscriberNotification: function(req, subscribers) {
		subscribers.forEach(function(subscriber) {
		    callFrontend('notify', subscriber.user, req.node, req.items);
		});
	    }
	}
    },
    'retract-items': {
	retract: {
	    requiredAffiliation: 'publisher',
	    transaction: function(req, t, cb) {
		var subscribers;

		/* TODO: check perms */
		step(function() {
			 if (req.itemIds.length < 1)
			     this(null, []);
			 else {
			     var g = this.group();
			     req.itemIds.forEach(function(itemId) {
				 t.deleteItem(req.node, itemId, g());
			     });
			 }
		     }, cb);
	    },
	    subscriberNotification: function(req, subscribers) {
		subscribers.forEach(function(subscriber) {
		    callFrontend('retracted', subscriber.user, req.node, req.itemIds);
		});
	    }
	}
    },
    'retrieve-items': {
	retrieve: {
	    requiredAffiliation: 'member',
	    transaction: function(req, t, cb) {
		var ids, items;
		step(function() {
			 t.getItemIds(req.node, this);
		     }, function(err, ids_) {
			 if (err) throw err;

			 ids = ids_;
			 if (ids.length < 1)
			     this(null, []);
			 else {
			     var g = this.group();
			     ids.forEach(function(id) {
				 t.getItem(req.node, id, g());
			     });
			 }
		     }, function(err, items) {
			 if (err) throw err;

			 /* Assemble ids & items lists into result dictionary */
			 var result = {};
			 var id, item;
			 while((id = ids.shift()) && (item = items.shift())) {
			     result[id] = item;
			 }
			 this(null, result);
		     }, cb);
	    }
	}
    },
    'retrieve-subscriptions': {
	retrieve: {
	    transaction: function(req, t, cb) {
		t.getSubscriptions(req.from, cb);
	    }
	}
    },
    'retrieve-affiliations': {
	retrieve: {
	    transaction: function(req, t, cb) {
		t.getAffiliations(req.from, cb);
	    }
	}
    },
    'manage-subscriptions': {
	retrieve: {
	    requiredAffiliation: 'moderator',
	    transaction: function(req, t, cb) {
		t.getSubscribers(req.node, cb);
	    }
	},
	modify: {
	    requiredAffiliation: 'owner',
	    /* TODO: only let owner subscribe users who intended to */
	    transaction: function(req, t, cb) {
		step(function() {
			 if (objectIsEmpty(req.subscriptions)) {
			     this(null);
			     return;
			 }

			 var g = this.group();
			 for(var user in req.subscriptions) {
			     var subscription = req.subscriptions[user];
			     switch(subscription) {
			     case 'subscribed':
				     t.setSubscription(req.node, user, g());
				     break;
			     case 'none':
				     t.setSubscription(req.node, user, g());
				     break;
			     default:
				 throw new errors.BadRequest(subscription + ' is no subscription type');
			     }
			 }
		     }, cb);
	    },
	    subscriberNotification: function(req, subscribers) {
		/* TODO: send out if subscriber !== req.from */
	    }
	}
    },
    'modify-affiliations': {
	retrieve: {
	    requiredAffiliation: 'owner',
	    /* TODO: outcast only if req.affiliation == 'owner' or 'publisher' */
	    transaction: function(req, t, cb) {
		t.getAffiliated(req.node, cb);
	    }
	},
	modify: {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		if (objectIsEmpty(req.affiliations)) {
		    this(null);
		    return;
		}

		step(function() {
			 var g = this.group();
			 for(var user in req.affiliations) {
			     var affiliation = req.affiliations[user];
			     /* TODO: validate affiliation */
			     t.setAffiliation(req.node, user, affiliation, g());
			 }
		     }, cb);
	    }
	}
    },
    'config-node': {
	retrieve: {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		t.getConfig(req.node, cb);
	    }
	},
	modify: {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		t.setConfig(req.node, { title: req.title,
					accessModel: req.accessModel,
					publishModel: req.publishModel
				      }, cb);
	    }
	}
    },
    'get-pending': {
	'list-nodes': {
	    transaction: function(req, t, cb) {
		t.getPendingNodes(req.from, cb);
	    }
	},
	'get-for-node': {
	    requiredAffiliation: 'owner',
	    transaction: function(req, t, cb) {
		step(function() {
		    t.getPending(req.node, this);
		}, function(err, users) {
		    if (err) throw err;

		    req.pendingUsers = users;
		    this(null);
		}, cb);
	    },
	    afterTransaction: function(req) {
		req.pendingUsers.forEach(function(user) {
		    callFrontend('approve', req.from, req.node, user);
		});
	    }
	}
    },
    /* Actually no pubsub feature but fits here snugly */
    register: {
	register: {
	    transaction: function(req, t, cb) {
		var m, user = req.from;
		if ((m = user.match(/^.+:(.+)$/)))
		    user = m[1]; /* strip proto prefix */

		/* TODO: make configurable */
		var nodes = ['channel', 'mood', 'geo/current',
			     'geo/future', 'geo/previous'].map(function(name) {
		    return '/user/' + user + '/' + name;
		});

		step(function() {
		    var g = this.group();
		    nodes.forEach(function(node) {
			t.createNode(node, g());
		    });
		}, function(err) {
		    if (err) throw err;

		    var g = this.group();
		    nodes.forEach(function(node) {
			t.setAffiliation(node, req.from, 'owner', g());
			t.setSubscription(node, req.from, 'subscribed', g());
		    });
		}, cb);
	    }
	}
    },
    /* Actually no pubsub feature but fits here snugly */
    'browse-nodes': {
	list: {
	    transaction: function(req, t, cb) {
		t.listNodes(cb);
	    }
	}
    }
};

exports.pubsubFeatures = function() {
    var result = [];
    for(var f in FEATURES)
	result.push(f);
    return result;
};

exports.request = function(req) {
    var feature = FEATURES[req.feature];
    var operation = feature && feature[req.operation];
    req.affiliation = 'none';

    if (!operation) {
	req.callback(new errors.FeatureNotImplemented('Operation not yet supported'));
	return;
    }
    var debug = function(s) {
	console.log(req.from + ' >> ' + req.feature + '/' + req.operation + ': ' + s);
    };

    /* TODO: no underscores */
    if (req.node && req.from) {
	var nodeM = req.node.match(/^\/user\/(.+?)\/([a-zA-Z0-9\/\-]+)$/);
	var userM = req.from.match(/^(.+?):(.+)$/);
	if (nodeM && nodeM[1] === userM[2])
	    req.affiliation = 'owner';
    }

    model.transaction(function(err, t) {
	if (err) {
	    req.callback(err);
	    return;
	}

	var steps = [function(err) {
			 /* Unfortunately, step starts with err = [] */
			 this(null);
		     }];

	/* Retrieve affiliation if needed */
	if (operation.requiredAffiliation &&
	    !isAffiliationSubset(operation.requiredAffiliation, req.affiliation)) {

	    var config;
	    steps.push(function(err) {
		if (err) throw err;

		t.getConfig(req.node, this);
	    }, function(err, config_) {
		if (err) throw err;

		config = config_;

		t.getAffiliation(req.node, req.from, this);
	    }, function(err, affiliation) {
		if (err) throw err;

		req.affiliation = affiliation || req.affiliation;

		t.getSubscription(req.node, req.from, this);
	    }, function(err, subscription) {
		if (err) throw err;

		if (req.affiliation === 'none' &&
		    (!config.accessModel || config.accessModel === 'open')) {
		    /* 'open' model: members don't need to be approved */
		    req.affiliation = 'member';
		} else if (req.affiliation === 'member' &&
			   config.publishModel === 'publisher' &&
			   subscription === 'subscribed') {
		    /* set affiliation = 'publisher' only if user subscribed */
		    req.affiliation = 'publisher';
		}

		if (isAffiliationSubset(operation.requiredAffiliation, req.affiliation))
		    this();
		else
		    this(new errors.Forbidden(operation.requiredAffiliation + ' required'));
	    });
	}

	/* Run operation transaction first */
	var transactionResults;
	steps.push(function(err) {
	    if (err) throw err;
	    debug('transaction');

	    operation.transaction(req, t, this);
	}, function(err) {
	    if (err) throw err;
	    debug('transaction done');

	    /* Regardless of the following steps, we pass
	     * the operation's transaction result to the
	     * final callback.
	     */
	    transactionResults = arguments;
	    /* And continue:
	     */
	    this(null);
	});
        var subscribers;
	if (operation.subscriberNotification) {
	    /* For subscriber notification, get the list of subscribers
	     * while still inside transaction.
	     */
	    steps.push(function(err) {
		if (err) throw err;

		t.getSubscribers(req.node, this);
	    }, function(err, subscribers_) {
		if (err) throw err;

		subscribers = subscribers_;
		this(null);
	    });
	}
	/* Finalize transaction
	 */
	steps.push(function(err) {
	    if (err) {
		var that = this;
		debug('transaction rollback: ' + (err.message || JSON.stringify(err)));
		t.rollback(function() {
		    /* Keep error despite successful rollback */
		    that(err);
		});
	    } else {
		debug('transaction commit');
		t.commit(this);
	    }
	});
	if (operation.afterTransaction) {
	    steps.push(function(err) {
		if (err) throw err;

		operation.afterTransaction(req);
		this(null);
	    });
	}
	if (operation.subscriberNotification) {
	    /* Transaction successful? Call subscriberNotification. */
	    steps.push(function(err) {
		if (err) throw err;

		operation.subscriberNotification(req, subscribers);
		this(null);
	    });
	}
	/* Last step: return to caller (view) */
	steps.push(function(err) {
	    debug('callback');
	    if (err && req.callback)
		req.callback(err);
	    else if (req.callback)
		req.callback.apply(req, transactionResults);
	});

	/* Finally, run all the steps we assembled above */
	step.apply(null, steps);
    });
};

/*
 * This is not a request, but used internally to send out presence
 * probes at startup.
 */
exports.getAllSubscribers = function(cb) {
    model.transaction(function(err, t) {
	if (err) {
	    cb(err);
	    return;
	}

	t.getAllSubscribers(function(err, subscribers) {
	    if (err) {
		t.rollback(function() {
			       cb(err);
			   });
		return;
	    }

	    t.commit(function() {
		cb(null, subscribers);
	    });
	});
    });

};

/**
 * Affiliations comparison
 */

var AFFILIATION_SUBSETS = {
    owner: ['moderator', 'publisher', 'member'],
    moderator: ['publisher', 'member'],
    publisher: ['member']
};
function isAffiliationSubset(subset, affiliation) {
    return subset === affiliation ||
	   (AFFILIATION_SUBSETS.hasOwnProperty(affiliation) &&
	    AFFILIATION_SUBSETS[affiliation].indexOf(subset) >= 0);
}

/**
 * Frontend hooking
 */

var frontends = {};

/**
 * Hook frontend for uri prefix
 */
exports.hookFrontend = function(proto, hooks) {
    frontends[proto] = hooks;
};

/**
 * Call named hook by uri prefix
 */
function callFrontend(hook, uri) {
    var colonPos = uri.indexOf(':');
    if (colonPos > 0) {
	var proto = uri.substr(0, colonPos);
	uri = uri.substr(colonPos + 1);
    } else
	return;

    var args = Array.prototype.slice.call(arguments, 1);
    var frontend = frontends.hasOwnProperty(proto) && frontends[proto];
    var hookFun = frontend && frontend.hasOwnProperty(hook) && frontend[hook];
console.log({callFrontend:arguments,frontent:frontend,hookFun:hookFun,args:args});

    if (hookFun) {
	return hookFun.apply(frontend, args);
    }
};

/* A helper */

function objectIsEmpty(o) {
    for(var k in o) {
	if (o.hasOwnProperty(k))
	    return false;
    }
    return true;
}
<|endoftext|>


<commit_before>
var express = require("express");
var app = express.createServer();

app.configure('development', function(){
  app.use(express.static(__dirname + '/public'));
});

app.listen(3000);
<commit_msg>Attach ShareJS to the example server.<commit_after>
var express = require("express");
var sharejs = require("share").server;

var app = express.createServer();

sharejs.attach(app, {
  db:{ type: "none" }
});

app.configure('development', function(){
  app.use(express.static(__dirname + '/public'));
});

app.listen(3000, function() {
  console.log("Now listening on http://localhost:3000/");
});
<|endoftext|>


<commit_before>var nodegit = require("../"),
    path = require("path");

// This code walks the history of the master branch and prints results
// that look very similar to calling `git log` from the command line

nodegit.Repository.open(path.resolve(__dirname, "../.git"))
  .then(function(repo) {
    return repo.getMasterCommit();
  })
  .then(function(firstCommitOnMaster){
    // History returns an event.
    var history = firstCommitOnMaster.history(nodegit.Revwalk.SORT.Time);

    // History emits "commit" event for each commit in the branch's history
    history.on("commit", function(commit) {
      console.log("commit " + commit.sha());
      console.log("Author:", commit.author().name() +
        " <" + commit.author().email() + ">");
      console.log("Date:", commit.date());
      console.log("\n    " + commit.message());
    });

    // Don't forget to call `start()`!
    history.start();
  })
  .done();
<commit_msg>Fix incorrect api usage, not Time rather TIME<commit_after>var nodegit = require("../"),
    path = require("path");

// This code walks the history of the master branch and prints results
// that look very similar to calling `git log` from the command line

nodegit.Repository.open(path.resolve(__dirname, "../.git"))
  .then(function(repo) {
    return repo.getMasterCommit();
  })
  .then(function(firstCommitOnMaster){
    // History returns an event.
    var history = firstCommitOnMaster.history(nodegit.Revwalk.SORT.TIME);

    // History emits "commit" event for each commit in the branch's history
    history.on("commit", function(commit) {
      console.log("commit " + commit.sha());
      console.log("Author:", commit.author().name() +
        " <" + commit.author().email() + ">");
      console.log("Date:", commit.date());
      console.log("\n    " + commit.message());
    });

    // Don't forget to call `start()`!
    history.start();
  })
  .done();
<|endoftext|>