<commit_before>/**
 *      Admin backend
 *
 *      Controls Adapter-Processes
 *
 *      Copyright 2014-2019 bluefox <dogafox@gmail.com>,
 *      MIT License
 *
 */

/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';

const adapterName = require('./package.json').name.split('.').pop();
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
const tools 	  = require(utils.controllerDir + '/lib/tools.js');
const SocketIO    = require('./lib/socket');
const Web         = require('./lib/web');

let socket      = null;
let webServer   = null;

let objects     = {};
let states      = {};
let secret      = 'Zgfr56gFe87jJOM'; // Will be generated by first start
let adapter;

function startAdapter(options) {
    options = options || {};
	Object.assign(options, {
	    name:           adapterName, // adapter name
	    dirname:        __dirname,   // say own position
	    logTransporter: true,        // receive the logs
	    systemConfig:   true,
	    install:        callback => typeof callback === 'function' && callback()
	});

    adapter = new utils.Adapter(options);

    adapter.on('objectChange', (id, obj) => {
        if (obj) {
            //console.log('objectChange: ' + id);
            objects[id] = obj;

            if (id === 'system.repositories') {
                writeUpdateInfo();
            }
        } else {
            //console.log('objectDeleted: ' + id);
            if (objects[id]) {
                delete objects[id];
            }
        }

        // TODO Build in some threshold of messages
        if (socket) {
            socket.objectChange(id, obj);
        }
    });

    adapter.on('stateChange', (id, state) => {
        if (!state) {
            if (states[id]) {
                delete states[id];
            }
        } else {
            states[id] = state;
        }
        if (socket) {
            socket.stateChange(id, state);
        }
    });

    adapter.on('ready', () => {
        adapter.getForeignObject('system.config', (err, obj) => {
            if (!err && obj) {
                obj.native = obj.native || {};
                if (!obj.native.secret) {
                    require('crypto').randomBytes(24, (ex, buf) => {
                        adapter.config.secret = buf.toString('hex');
                        adapter.extendForeignObject('system.config', {native: {secret: adapter.config.secret}});
                        main();
                    });
                } else {
                    adapter.config.secret = obj.native.secret;
                    main();
                }
            } else {
                adapter.config.secret = secret;
                adapter.log.error('Cannot find object system.config');
            }
        });
    });

    adapter.on('message', obj => {
        if (!obj || !obj.message) {
            return false;
        }

        if (socket) {
            socket.sendCommand(obj);
        }

        return true;
    });

    adapter.on('unload', callback => {
        if (socket) {
            // unsubscribe all
            socket.unsubscribeAll();
        }

        try {
            adapter.log.info('terminating http' + (adapter.config.secure ? 's' : '') + ' server on port ' + adapter.config.port);
            webServer.close();
            callback();
        } catch (e) {
            callback();
        }
    });

// obj = {message: msg, severity: level, from: this.namespace, ts: (new Date()).getTime()}
    adapter.on('log', obj => socket && socket.sendLog(obj));

    return adapter;
}

function createUpdateInfo() {
    // create connected object and state
    let updatesNumberObj = objects[adapter.namespace + '.info.updatesNumber'];

    if (!updatesNumberObj || !updatesNumberObj.common || updatesNumberObj.common.type !== 'number') {
        let obj = {
            _id:  'info.updatesNumber',
            type: 'state',
            common: {
                role:  'indicator.updates',
                name:  'Number of adapters to update',
                type:  'number',
                read:  true,
                write: false,
                def:   0
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }

    let updatesListObj = objects[adapter.namespace + '.info.updatesList'];

    if (!updatesListObj || !updatesListObj.common || updatesListObj.common.type !== 'string') {
        let obj = {
            _id:  'info.updatesList',
            type: 'state',
            common: {
                role:  'indicator.updates',
                name:  'List of adapters to update',
                type:  'string',
                read:  true,
                write: false,
                def:   ''
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }

    let newUpdatesObj = objects[adapter.namespace + '.info.newUpdates'];

    if (!newUpdatesObj || !newUpdatesObj.common || newUpdatesObj.common.type !== 'boolean') {
        let obj = {
            _id:  'info.newUpdates',
            type: 'state',
            common: {
                role:  'indicator.updates',
                name:  'Indicator if new adapter updates are available',
                type:  'boolean',
                read:  true,
                write: false,
                def:   false
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }

    let updatesJsonObj = objects[adapter.namespace + '.info.updatesJson'];

    if (!updatesJsonObj || !updatesJsonObj.common || updatesJsonObj.common.type !== 'string') {
        let obj = {
            _id:  'info.updatesJson',
            type: 'state',
            common: {
                role:  'indicator.updates',
                name:  'JSON string with adapter update information',
                type:  'string',
                read:  true,
                write: false,
                def:   '{}'
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }

    let lastUpdateCheckObj = objects[adapter.namespace + '.info.lastUpdateCheck'];

    if (!lastUpdateCheckObj || !lastUpdateCheckObj.common || lastUpdateCheckObj.common.type !== 'string') {
        let obj = {
            _id:  'info.lastUpdateCheck',
            type: 'state',
            common: {
                role:  'value.datetime',
                name:  'Timestamp of last update check',
                type:  'string',
                read:  true,
                write: false,
                def:   '{}'
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }
}

// Helper methods
function upToDate(a, b) {
    a = a.split('.');
    b = b.split('.');
    a[0] = parseInt(a[0], 10);
    b[0] = parseInt(b[0], 10);
    if (a[0] > b[0]) {
        return false;
    } else if (a[0] < b[0]) {
        return true;
    } else if (a[0] === b[0]) {
        a[1] = parseInt(a[1], 10);
        b[1] = parseInt(b[1], 10);
        if (a[1] > b[1]) {
            return false;
        } else if (a[1] < b[1]) {
            return true;
        } else if (a[1] === b[1]) {
            a[2] = parseInt(a[2], 10);
            b[2] = parseInt(b[2], 10);
            return a[2] <= b[2];
        }
    } else {
        return true;
    }
}

function writeUpdateInfo(sources) {
    if (!sources) {
        let obj = objects['system.repositories'];
        if (!objects['system.config'] || !objects['system.config'].common) {
            adapter.log.warn('Repository cannot be read. Invalid "system.config" object.');
            return;
        }

        const activeRepo = objects['system.config'].common.activeRepo;

        if (obj && obj.native && obj.native.repositories && obj.native.repositories[activeRepo] &&
            obj.native.repositories[activeRepo].json) {
            sources = obj.native.repositories[activeRepo].json;
        } else {
            adapter.setState('info.updatesNumber', 0, true);
            adapter.setState('info.updatesList',  '', true);
            adapter.setState('info.newUpdates', false, true);
            adapter.setState('info.updatesJson', '{}', true);
            let updateTime = new Date();
            adapter.setState('info.lastUpdateCheck', new Date(updateTime - updateTime.getTimezoneOffset() * 60000).toISOString(), true);
            if (obj && obj.native && obj.native.repositories && obj.native.repositories[activeRepo]) {
                adapter.log.warn('Repository cannot be read');
            } else {
                adapter.log.warn('No repository source configured');
            }
            return;
        }
    }

    let installed = tools.getInstalledInfo();
    let list  = [];
    let updatesJson = {};
    let newUpdateIndicator = false;
    adapter.getState('info.updatesJson', (err, state) => {
        let oldUpdates;
        if (state && state.val) oldUpdates = JSON.parse(state.val) || {};
        else oldUpdates = {};
        for (let name in sources) {
            if (!sources.hasOwnProperty(name)) continue;
            if (installed[name] && installed[name].version && sources[name].version) {
                if (sources[name].version !== installed[name].version &&
                    !upToDate(sources[name].version, installed[name].version)) {
                    // Check if updates are new or already known to user
                    if (!oldUpdates || !oldUpdates[name] || oldUpdates[name].availableVersion !== sources[name].version) {
                        newUpdateIndicator = true;
                    } // endIf
                    updatesJson[name] = {
                        availableVersion: sources[name].version,
                        installedVersion: installed[name].version
                    };
                    // remove first part of the name
                    const n = name.indexOf('.');
                    list.push(n === -1 ? name : name.substring(n + 1));
                }
            }
        }
        adapter.setState('info.updatesNumber', list.length, true);
        adapter.setState('info.updatesList', list.join(', '), true);
        adapter.setState('info.newUpdates', newUpdateIndicator, true);
        adapter.setState('info.updatesJson', JSON.stringify(updatesJson), true);
        let updateTime = new Date();
        adapter.setState('info.lastUpdateCheck', new Date(updateTime - updateTime.getTimezoneOffset() * 60000).toISOString(), true);
    });

}

// to do => remove it later, when all repositories patched.
function patchRepos(callback) {
    return callback && callback();
    // do not patch any more. Delete it later 2018.04.23
    /*
    adapter.getForeignObject('system.repositories', (err, obj) => {
        let changed = false;
        if (obj && obj.native && obj.native.repositories) {
            // default link should point to stable
            if (!obj.native.repositories.default || obj.native.repositories.default.link !== 'http://download.iobroker.net/sources-dist.json') {
                changed = true;
                obj.native.repositories.default = {
                    link: 'http://download.iobroker.net/sources-dist.json'
                };
            }
            // latest link should point to latest
            if (!obj.native.repositories.latest) {
                obj.native.repositories.latest = {
                    link: 'http://download.iobroker.net/sources-dist-latest.json'
                };
                changed = true;
            }

            // change URL of raw sources from ioBroker.js-controller to ioBroker.repositories
            for (let r in obj.native.repositories) {
                if (obj.native.repositories.hasOwnProperty(r) &&
                    obj.native.repositories[r].link === 'https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/conf/sources-dist.json') {
                    obj.native.repositories[r].link = 'https://raw.githubusercontent.com/ioBroker/ioBroker.repositories/master/sources-dist.json';
                    changed = true;
                }
            }
        }
        if (changed) {
            adapter.setForeignObject(obj._id, obj, function () {
                callback && callback();
            });
        } else {
            callback && callback();
        }
    });*/
}

function initSocket(server, store) {
    socket = new SocketIO(server, adapter.config, adapter, objects, states, store);
    socket.subscribe(null, 'objectChange', '*');
}

function main() {
    // adapter.subscribeForeignStates('*');
    // adapter.subscribeForeignObjects('*');

    adapter.config.defaultUser = adapter.config.defaultUser || 'admin';
    if (!adapter.config.defaultUser.match(/^system\.user\./)) {
        adapter.config.defaultUser = 'system.user.' + adapter.config.defaultUser;
    }

    if (adapter.config.secure) {
        // Load certificates
        adapter.getCertificates((err, certificates, leConfig) => {
            adapter.config.certificates = certificates;
            adapter.config.leConfig     = leConfig;

            getData(() => webServer = new Web(adapter.config, adapter, initSocket));
        });
    } else {
        getData(() => webServer = new Web(adapter.config, adapter, initSocket));
    }

    patchRepos(() => {
        // By default update repository every 24 hours
        if (adapter.config.autoUpdate === undefined) {
            adapter.config.autoUpdate = 24;
        }
        adapter.config.autoUpdate = parseInt(adapter.config.autoUpdate, 10) || 0;
        if (adapter.config.autoUpdate) {
            setInterval(() => updateRegister(), adapter.config.autoUpdate * 3600000);
            updateRegister();
        }
    });
}

function getData(callback) {
    adapter.log.info('requesting all states');
    let tasks = 0;
    tasks++;
    adapter.getForeignStates('*', (err, res) => {
        adapter.log.info('received all states');
        states = res;
        if (!--tasks && callback) callback();
    });
    adapter.log.info('requesting all objects');
    tasks++;
    adapter.objects.getObjectList({include_docs: true}, (err, res) => {
        adapter.log.info('received all objects');
        res = res.rows;
        objects = {};
        let tmpPath = '';
        for (let i = 0; i < res.length; i++) {
            objects[res[i].doc._id] = res[i].doc;
            if (res[i].doc.type === 'instance' && res[i].doc.common && res[i].doc.common.tmpPath) {
                if (tmpPath) {
                    adapter.log.warn('tmpPath has multiple definitions!!');
                }
                tmpPath = res[i].doc.common.tmpPath;
            }
        }

        // Some adapters want access on specified tmp directory
        if (tmpPath) {
            adapter.config.tmpPath = tmpPath;
            adapter.config.tmpPathAllow = true;
        }

        createUpdateInfo();
        writeUpdateInfo();
        if (!--tasks && callback) callback();
    });
}

// read repository information from active repository
function updateRegister() {
    adapter.log.info('Request actual repository...');
    adapter.getForeignObject('system.config', (err, data) => {
        if (data && data.common) {

            adapter.sendToHost(adapter.host, 'getRepository', {
                repo:   data.common.activeRepo,
                update: true
            }, _repository => {
                if (_repository === 'permissionError') {
                    adapter.log.error('May not read "getRepository"');
                } else {
                    adapter.log.info('Repository received successfully.');

                    if (socket) {
                        socket.repoUpdated();
                    }
                }
            });
        }
    });
}

// If started as allInOne mode => return function to create instance
if (module && module.parent) {
    module.exports = startAdapter;
} else {
    // or start the instance directly
    startAdapter();
}
<commit_msg>Formatting<commit_after>/**
 *      Admin backend
 *
 *      Controls Adapter-Processes
 *
 *      Copyright 2014-2019 bluefox <dogafox@gmail.com>,
 *      MIT License
 *
 */

/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';

const adapterName = require('./package.json').name.split('.').pop();
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
const tools 	  = require(utils.controllerDir + '/lib/tools.js');
const SocketIO    = require('./lib/socket');
const Web         = require('./lib/web');

let socket      = null;
let webServer   = null;

let objects     = {};
let states      = {};
let secret      = 'Zgfr56gFe87jJOM'; // Will be generated by first start
let adapter;

function startAdapter(options) {
    options = options || {};
	Object.assign(options, {
	    name:           adapterName, // adapter name
	    dirname:        __dirname,   // say own position
	    logTransporter: true,        // receive the logs
	    systemConfig:   true,
	    install:        callback => typeof callback === 'function' && callback()
	});

    adapter = new utils.Adapter(options);

    adapter.on('objectChange', (id, obj) => {
        if (obj) {
            //console.log('objectChange: ' + id);
            objects[id] = obj;

            if (id === 'system.repositories') {
                writeUpdateInfo();
            }
        } else {
            //console.log('objectDeleted: ' + id);
            if (objects[id]) {
                delete objects[id];
            }
        }

        // TODO Build in some threshold of messages
        if (socket) {
            socket.objectChange(id, obj);
        }
    });

    adapter.on('stateChange', (id, state) => {
        if (!state) {
            if (states[id]) {
                delete states[id];
            }
        } else {
            states[id] = state;
        }
        if (socket) {
            socket.stateChange(id, state);
        }
    });

    adapter.on('ready', () => {
        adapter.getForeignObject('system.config', (err, obj) => {
            if (!err && obj) {
                obj.native = obj.native || {};
                if (!obj.native.secret) {
                    require('crypto').randomBytes(24, (ex, buf) => {
                        adapter.config.secret = buf.toString('hex');
                        adapter.extendForeignObject('system.config', {native: {secret: adapter.config.secret}});
                        main();
                    });
                } else {
                    adapter.config.secret = obj.native.secret;
                    main();
                }
            } else {
                adapter.config.secret = secret;
                adapter.log.error('Cannot find object system.config');
            }
        });
    });

    adapter.on('message', obj => {
        if (!obj || !obj.message) {
            return false;
        }

        if (socket) {
            socket.sendCommand(obj);
        }

        return true;
    });

    adapter.on('unload', callback => {
        if (socket) {
            // unsubscribe all
            socket.unsubscribeAll();
        }

        try {
            adapter.log.info('terminating http' + (adapter.config.secure ? 's' : '') + ' server on port ' + adapter.config.port);
            webServer.close();
            callback();
        } catch (e) {
            callback();
        }
    });

// obj = {message: msg, severity: level, from: this.namespace, ts: (new Date()).getTime()}
    adapter.on('log', obj => socket && socket.sendLog(obj));

    return adapter;
}

function createUpdateInfo() {
    // create connected object and state
    let updatesNumberObj = objects[adapter.namespace + '.info.updatesNumber'];

    if (!updatesNumberObj || !updatesNumberObj.common || updatesNumberObj.common.type !== 'number') {
        let obj = {
            _id:  'info.updatesNumber',
            type: 'state',
            common: {
                role:  'indicator.updates',
                name:  'Number of adapters to update',
                type:  'number',
                read:  true,
                write: false,
                def:   0
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }

    let updatesListObj = objects[adapter.namespace + '.info.updatesList'];

    if (!updatesListObj || !updatesListObj.common || updatesListObj.common.type !== 'string') {
        let obj = {
            _id:  'info.updatesList',
            type: 'state',
            common: {
                role:  'indicator.updates',
                name:  'List of adapters to update',
                type:  'string',
                read:  true,
                write: false,
                def:   ''
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }

    let newUpdatesObj = objects[adapter.namespace + '.info.newUpdates'];

    if (!newUpdatesObj || !newUpdatesObj.common || newUpdatesObj.common.type !== 'boolean') {
        let obj = {
            _id:  'info.newUpdates',
            type: 'state',
            common: {
                role:  'indicator.updates',
                name:  'Indicator if new adapter updates are available',
                type:  'boolean',
                read:  true,
                write: false,
                def:   false
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }

    let updatesJsonObj = objects[adapter.namespace + '.info.updatesJson'];

    if (!updatesJsonObj || !updatesJsonObj.common || updatesJsonObj.common.type !== 'string') {
        let obj = {
            _id:  'info.updatesJson',
            type: 'state',
            common: {
                role:  'indicator.updates',
                name:  'JSON string with adapter update information',
                type:  'string',
                read:  true,
                write: false,
                def:   '{}'
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }

    let lastUpdateCheckObj = objects[adapter.namespace + '.info.lastUpdateCheck'];

    if (!lastUpdateCheckObj || !lastUpdateCheckObj.common || lastUpdateCheckObj.common.type !== 'string') {
        let obj = {
            _id:  'info.lastUpdateCheck',
            type: 'state',
            common: {
                role:  'value.datetime',
                name:  'Timestamp of last update check',
                type:  'string',
                read:  true,
                write: false,
                def:   '{}'
            },
            native: {}
        };

        adapter.setObject(obj._id, obj);
    }
}

// Helper methods
function upToDate(a, b) {
    a = a.split('.');
    b = b.split('.');
    a[0] = parseInt(a[0], 10);
    b[0] = parseInt(b[0], 10);
    if (a[0] > b[0]) {
        return false;
    } else if (a[0] < b[0]) {
        return true;
    } else if (a[0] === b[0]) {
        a[1] = parseInt(a[1], 10);
        b[1] = parseInt(b[1], 10);
        if (a[1] > b[1]) {
            return false;
        } else if (a[1] < b[1]) {
            return true;
        } else if (a[1] === b[1]) {
            a[2] = parseInt(a[2], 10);
            b[2] = parseInt(b[2], 10);
            return a[2] <= b[2];
        }
    } else {
        return true;
    }
}

function writeUpdateInfo(sources) {
    if (!sources) {
        let obj = objects['system.repositories'];
        if (!objects['system.config'] || !objects['system.config'].common) {
            adapter.log.warn('Repository cannot be read. Invalid "system.config" object.');
            return;
        }

        const activeRepo = objects['system.config'].common.activeRepo;

        if (obj && obj.native && obj.native.repositories && obj.native.repositories[activeRepo] &&
            obj.native.repositories[activeRepo].json) {
            sources = obj.native.repositories[activeRepo].json;
        } else {
            adapter.setState('info.updatesNumber', 0, true);
            adapter.setState('info.updatesList',  '', true);
            adapter.setState('info.newUpdates', false, true);
            adapter.setState('info.updatesJson', '{}', true);
            let updateTime = new Date();
            adapter.setState('info.lastUpdateCheck', new Date(updateTime - updateTime.getTimezoneOffset() * 60000).toISOString(), true);
            if (obj && obj.native && obj.native.repositories && obj.native.repositories[activeRepo]) {
                adapter.log.warn('Repository cannot be read');
            } else {
                adapter.log.warn('No repository source configured');
            }
            return;
        }
    }

    let installed = tools.getInstalledInfo();
    let list  = [];
    let updatesJson = {};
    let newUpdateIndicator = false;
    adapter.getState('info.updatesJson', (err, state) => {
        let oldUpdates;
        if (state && state.val) oldUpdates = JSON.parse(state.val) || {};
        else oldUpdates = {};
        for (let name in sources) {
            if (!sources.hasOwnProperty(name)) continue;
            if (installed[name] && installed[name].version && sources[name].version) {
                if (sources[name].version !== installed[name].version &&
                    !upToDate(sources[name].version, installed[name].version)) {
                    // Check if updates are new or already known to user
                    if (!oldUpdates || !oldUpdates[name] || oldUpdates[name].availableVersion !== sources[name].version) {
                        newUpdateIndicator = true;
                    } // endIf
                    updatesJson[name] = {
                        availableVersion: sources[name].version,
                        installedVersion: installed[name].version
                    };
                    // remove first part of the name
                    const n = name.indexOf('.');
                    list.push(n === -1 ? name : name.substring(n + 1));
                }
            }
        }
        adapter.setState('info.updatesNumber', list.length, true);
        adapter.setState('info.updatesList', list.join(', '), true);
        adapter.setState('info.newUpdates', newUpdateIndicator, true);
        adapter.setState('info.updatesJson', JSON.stringify(updatesJson), true);
        let updateTime = new Date();
        adapter.setState('info.lastUpdateCheck', new Date(updateTime - updateTime.getTimezoneOffset() * 60000).toISOString(), true);
    });

}

// to do => remove it later, when all repositories patched.
function patchRepos(callback) {
    return callback && callback();
    // do not patch any more. Delete it later 2018.04.23
    /*
    adapter.getForeignObject('system.repositories', (err, obj) => {
        let changed = false;
        if (obj && obj.native && obj.native.repositories) {
            // default link should point to stable
            if (!obj.native.repositories.default || obj.native.repositories.default.link !== 'http://download.iobroker.net/sources-dist.json') {
                changed = true;
                obj.native.repositories.default = {
                    link: 'http://download.iobroker.net/sources-dist.json'
                };
            }
            // latest link should point to latest
            if (!obj.native.repositories.latest) {
                obj.native.repositories.latest = {
                    link: 'http://download.iobroker.net/sources-dist-latest.json'
                };
                changed = true;
            }

            // change URL of raw sources from ioBroker.js-controller to ioBroker.repositories
            for (let r in obj.native.repositories) {
                if (obj.native.repositories.hasOwnProperty(r) &&
                    obj.native.repositories[r].link === 'https://raw.githubusercontent.com/ioBroker/ioBroker.js-controller/master/conf/sources-dist.json') {
                    obj.native.repositories[r].link = 'https://raw.githubusercontent.com/ioBroker/ioBroker.repositories/master/sources-dist.json';
                    changed = true;
                }
            }
        }
        if (changed) {
            adapter.setForeignObject(obj._id, obj, function () {
                callback && callback();
            });
        } else {
            callback && callback();
        }
    });*/
}

function initSocket(server, store) {
    socket = new SocketIO(server, adapter.config, adapter, objects, states, store);
    socket.subscribe(null, 'objectChange', '*');
}

function main() {
    // adapter.subscribeForeignStates('*');
    // adapter.subscribeForeignObjects('*');

    adapter.config.defaultUser = adapter.config.defaultUser || 'admin';
    if (!adapter.config.defaultUser.match(/^system\.user\./)) {
        adapter.config.defaultUser = 'system.user.' + adapter.config.defaultUser;
    }

    if (adapter.config.secure) {
        // Load certificates
        adapter.getCertificates((err, certificates, leConfig) => {
            adapter.config.certificates = certificates;
            adapter.config.leConfig     = leConfig;

            getData(() => webServer = new Web(adapter.config, adapter, initSocket));
        });
    } else {
        getData(() => webServer = new Web(adapter.config, adapter, initSocket));
    }

    patchRepos(() => {
        // By default update repository every 24 hours
        if (adapter.config.autoUpdate === undefined) {
            adapter.config.autoUpdate = 24;
        }
        adapter.config.autoUpdate = parseInt(adapter.config.autoUpdate, 10) || 0;
        if (adapter.config.autoUpdate) {
            setInterval(() => updateRegister(), adapter.config.autoUpdate * 3600000);
            updateRegister();
        }
    });
}

function getData(callback) {
    adapter.log.info('requesting all states');
    let tasks = 0;
    tasks++;
    adapter.getForeignStates('*', (err, res) => {
        adapter.log.info('received all states');
        states = res;
        !--tasks && callback && callback();
    });
    adapter.log.info('requesting all objects');
    tasks++;
    adapter.objects.getObjectList({include_docs: true}, (err, res) => {
        adapter.log.info('received all objects');
        if (res) {
            res = res.rows;
            objects = {};
            let tmpPath = '';
            for (let i = 0; i < res.length; i++) {
                objects[res[i].doc._id] = res[i].doc;
                if (res[i].doc.type === 'instance' && res[i].doc.common && res[i].doc.common.tmpPath) {
                    tmpPath && adapter.log.warn('tmpPath has multiple definitions!!');
                    tmpPath = res[i].doc.common.tmpPath;
                }
            }

            // Some adapters want access on specified tmp directory
            if (tmpPath) {
                adapter.config.tmpPath = tmpPath;
                adapter.config.tmpPathAllow = true;
            }

            createUpdateInfo();
            writeUpdateInfo();
        }

        !--tasks && callback && callback();
    });
}

// read repository information from active repository
function updateRegister() {
    adapter.log.info('Request actual repository...');
    adapter.getForeignObject('system.config', (err, data) => {
        if (data && data.common) {
            adapter.sendToHost(adapter.host, 'getRepository', {
                repo:   data.common.activeRepo,
                update: true
            }, _repository => {
                if (_repository === 'permissionError') {
                    adapter.log.error('May not read "getRepository"');
                } else {
                    adapter.log.info('Repository received successfully.');

                    socket && socket.repoUpdated();
                }
            });
        }
    });
}

// If started as allInOne mode => return function to create instance
if (module && module.parent) {
    module.exports = startAdapter;
} else {
    // or start the instance directly
    startAdapter();
}
<|endoftext|>


<commit_before>var element, data, axisSpace;

$(function() {

	var streamElement = "#masterSVG";
	var radarElement = "#detailSVG";
	axisSpace = 15;

	var sampleData = [

		{"key" : "key1", "xyz" : {"A" : 3,"B" : 2, "C" : 4, "D" : 5}},
		{"key" : "key2", "xyz" : {"A" : 2,"B" : 4, "C" : 3, "D" : 1}},
        {"key" : "key3", "xyz" : {"A" : 4,"B" : 3, "C" : 1, "D" : 4}},
        {"key" : "key4", "xyz" : {"A" : 1,"B" : 5, "C" : 2, "D" : 3}},
        {"key" : "key5", "xyz" : {"A" : 5,"B" : 4, "C" : 3, "D" : 2}},
        {"key" : "key6", "xyz" : {"A" : 1,"B" : 2, "C" : 2, "D" : 5}},
		{"key" : "key7", "xyz" : {"A" : 2,"B" : 3, "C" : 4, "D" : 3}},
        {"key" : "key8", "xyz" : {"A" : 4,"B" : 1, "C" : 5, "D" : 2}},
        {"key" : "key9", "xyz" : {"A" : 3,"B" : 3, "C" : 1, "D" : 4}}
	];

	data = sampleData;
	element = radarElement;

	renderStreamgraph(streamElement, sampleData);
	updateRadar();

	$("#uploadLink").on("click", function(event){

    	event.preventDefault();
        $("#uploadInput").trigger("click");

    });

    $("#uploadInput").change(function() {

		var file = $("#uploadInput")[0].files[0];

		if (validateXLSX(file)) {

			showSpinner();
			parseXLSX(file);

		} else {

			showError();

		}

	});

});

function validateXLSX(file) {

	console.log(file.name);

	var extension = file.name.split(".").slice(-1)[0];

	return extension == ".xlsx" || extension == ".xls";

}

function parseXLSX(XLSX) {

	console.log("parsing", XLSX);

	var infoColumnCorrespondenceDict = {
		"chromosome" : ["CHROM", "Chromosome"],
		"ref" : ["REF", "Reference Allele", "Reference Nucleotide"],
		"alt" : ["ALT", "Sample Allele", "Variant Nucleotide"],
		"pos" : ["POS", "Position", "Start"]
	};

	var numericColumnCorrespondenceDict = {
		"fullExac" : ["hg19_popfreq_all_20150413_exac_all", "ExAC (AF%)", "ExAC (%)", "ExAC"],
		"europeExac": ["ExAC European", "hg19_popfreq_all_20150413_exac_nfe"],
		"1kgenomes": ["1000 Genomes", "hg19_popfreq_all_20150413_1000g_all"]
	};

	var stringColumnCorrespondenceDict = {
		"clinvar" : ["clinvar_clinical_significance"]
	};

	function readTSV(TSV) {

		data = [];
		lines = tsv.split("\n");
		columns = lines[0].split("\t");

		for (var line in lines) {

			data.append(line.split("\t"));

		}

		console.log(columns)
		console.log(data)

		return [columns, data];

	}

	function getVariantInfo(variantLine, idxDict, variantRecord) {

		var placeholder = [];

		for (var column in placeholder) {

			value = variantLine[idxDict[column]] || "na";

			if (column == "Gene_Summar") {
				choices = ["OR2T35", "BRCA1", "AFF3", "MYO7B", "ZNF806", "NEB", "SP100", "SYN2"];
				value = choices[Math.floor(Math.random() * choices.length)];
			}

		}

	}

	function writeFile(columns, lines, pos) {

	}

	var infoFieldTemplate = {
		"value" : "",
		"includeInDrawing" : false
	};

	var stringAnnotationTemplate = {
		"value": "",
		"drawingValue": "",
		"includeInDrawing": false,
		"associatedValues": []
	};

	var otherFieldTemplate = {
		"value": "",
		"drawingValue": "",
		"includeInDrawing": false,
		"associatedValues": []
	};

	function rowToJSON(row, curDf) {

		var variant = {};
		var coreAnnotationFields = {
			"infoFields" : "",
			"numericAnnotations" : "",
			"stringAnnotations" : "",
			"otherFields" : ""
		};

		variant["coreAnnotationFields"] = coreAnnotationFields;

		var infoFields = {};
		var numericAnnotations = {};
		var stringAnnotations = {};
		var otherFields = {};

		columns = curDf.columns;

		for (var column in columnames) {
		}

		variant["coreAnnotationFields"]["infoFields"] = infoFields;
		variant["coreAnnotationFields"]["numericAnnotations"] = numericAnnotations;
		variant["coreAnnotationFields"]["stringAnnotations"] = stringAnnotations;

		return variant;

	}

	function initalizeVariantMetadata(variant, sheetName) {
		variantMetadataStruct = {"metadata" : "", "workflow" : ""};
		metricsDict = {"numTimesClicked" : ""};
		workflowDict = {"curationMode" : sheetName, "freeTextNotes" : "enter any notes here"}
		variantMetadataStruct["metrics"] = metricsDict
		variantMetadataStruct["workflow"] = workflowDict

		return variantMetadataStruct;
	}

	function structToJSON(JSON) {
		parsed = json.loads(jsonFile);
		console.log(json.dumps(parsed))
	}

	function writeJSON(fileName, parsedJSON) {
		jsonFile = open(file, "w+")
		jsonFile.write(json.dumps(parsedJSON));
	}

}

function renderStreamgraph(element, data) {

	//add baseline data so no variants are on the egde of the graph
	data.unshift({"key" : "keyX", "xyz" : {"A" : 0,"B" : 0, "C" : 0, "D" : 0}});
	data.push({"key" : "keyX", "xyz" : {"A" : 0,"B" : 0, "C" : 0, "D" : 0}});

	var h = $(element).height();
   	var w = $(element).width();

	var flattened = $.map(data, function(element, index) {
		return element.xyz;
	});

	var stacker = d3.stack().keys(["A", "B", "C", "D"]).offset(d3.stackOffsetNone);
	var stacked = stacker(flattened);

	var xScale = d3.scaleLinear()
   		.domain([0, flattened.length - 1])
   		.range([0, w])

	var yScale = d3.scaleLinear()
   		.domain([
   			d3.min(stacked, function(layer) { return d3.min(layer, function(d) { return d[0]; }); }),
   			d3.max(stacked, function(layer) { return d3.max(layer, function(d) { return d[1]; }); })
   		]).range([h - axisSpace, 10]); //leave space for axis //leave space at top?

	d3.select(element)
		.selectAll("path")
		.data(stacked)
		.enter()
		.append("path")
		.attr("d", function(datum, index) {

			var area = d3.area()
				.curve(d3.curveCardinal)
				.x(function(d, i) {
					return xScale(i);
				}).y0(function(d, i) {
					return yScale(d[0]);
				}).y1(function(d, i) {
					return yScale(d[1]); }
				);

			return area(datum, index);

		}).attr("fill", getRandomColor);

	d3.select(element)
		.append("g")
		.attr("class", "xAxis")
		.attr("transform", "translate(0," + (h - axisSpace) + ")")
		.call(xAxis());

	var clipTicks = true;

	function xAxis() {
    	return d3.axisBottom(xScale)
    		.tickSize(0) //custom resize later
			.tickFormat(function(datum, index) {

    			return data[index].key;

    		});
    }

    customResizeTicks(data, yScale);

    d3.selectAll(".tick line")
    	.attr("id", function(datum, index) {

    		return "axisLine" + index;

    	}).on("click", function(datum, index) {

    		d3.select(this).classed("selectedForRadar", !d3.select(this).classed("selectedForRadar")); //toggle the class
    		updateRadar();

    	});

    // haze(element);

}

function customResizeTicks(data, yScale) {

	console.log("custom resizing");
	console.log(d3.selectAll("g.xAxis g.tick line").size());
	console.log(yScale.domain());

	d3.selectAll("g.xAxis g.tick line")
		.attr("y2", function(datum, index) {

			var streamValues = data[index].xyz;
			var total = 0;

			for (var key in streamValues) {
				total += streamValues[key];
			}

			console.log("index: " + index + "; total: " + total);

			return (0 - yScale(15 - total + .1)); //why 15? //.1 so ends don't protrude

		});

}

function haze(element) {

	var nVariants = 9;
	var buffer = 4;

	var h = $(element).height();
	var w = $(element).width() / (nVariants - 1);

	var startXs = [...Array(nVariants - 1).keys()];
	startXs = $.map(startXs, e => e * w + buffer);

	d3.select(element)
		.append("g")
		.attr("class", "hazeContainer")
		.selectAll("rect.haze")
		.data(startXs)
		.enter()
		.append("rect")
		.attr("class", "haze")
		.attr("x", d => d)
		.attr("y", 0)
		.attr("width", w - (buffer * 2))
		.attr("height", h)
		.attr("opacity", .8)
		.attr("stroke", "black");

}

function updateRadar() {

	var selectedVariants = [];

	d3.selectAll(".selectedForRadar").each(function(element, index){

		var id = d3.select(this).attr("id");
		selectedVariants.push(parseInt(id.substring(id.indexOf("e") + 1))); //TODO: actually get key

	});

	var selectedData = $.map(selectedVariants, function(element, index) {
		return data[element];
	});

	// renderRadar(element, selectedData);

}

function renderRadar(element, data) {

	data = data.slice(0, 3)

	var formatted = $.map(data, function(variant, index) {
		var toPlot = [];
		for (var key in variant.xyz) {
			toPlot.push({
				"axis" : key,
				"value" : variant.xyz[key]
			});
		}

		return [toPlot];
	});

	var color = d3.scaleLinear()
				.range(["#EDC951","#CC333F","#00A0B0"]);

	var margin = {top: 50, right: 100, bottom: 100, left: 50};

	var radarChartOptions = {
		w: 200,
		h: 200,
		margin: margin,
		maxValue: 6,
	  	levels: 5,
	  	roundStrokes: true,
	  	color: color
	};

	RadarChart(element, formatted, radarChartOptions);

}

function sortOnKeys(data, keys, increasing) {

	return data.sort(function(a, b) {

		for (i = 0; i < keys.length; i++) {
			a = a[keys[i]];
			b = b[keys[i]];
		}

		return increasing ? d3.ascending(a, b) : d3.descending(a, b);

	});

}

function getRandomColor() {

	return "#" + Math.floor(Math.random() * 16777215).toString(16);
}

function renderJSON(JSON) {

}

function showSpinner() {

	console.log("showing");

	$("#spinnerContainer").show()
	$("#inputContainer").hide()

}

function hideSpinner() {

	console.log("hiding");

	$("#spinnerContainer").hide()
	$("#inputContainer").show()

}

function scrollToElement(element) {

	$("html, body").animate({
        scrollTop: $(element).offset().top
    }, 2000);

}<commit_msg>hid tick labels for ticks on ends<commit_after>var element, data, axisSpace;

$(function() {

	var streamElement = "#masterSVG";
	var radarElement = "#detailSVG";
	axisSpace = 15;

	var sampleData = [

		{"key" : "key1", "xyz" : {"A" : 3,"B" : 2, "C" : 4, "D" : 5}},
		{"key" : "key2", "xyz" : {"A" : 2,"B" : 4, "C" : 3, "D" : 1}},
        {"key" : "key3", "xyz" : {"A" : 4,"B" : 3, "C" : 1, "D" : 4}},
        {"key" : "key4", "xyz" : {"A" : 1,"B" : 5, "C" : 2, "D" : 3}},
        {"key" : "key5", "xyz" : {"A" : 5,"B" : 4, "C" : 3, "D" : 2}},
        {"key" : "key6", "xyz" : {"A" : 1,"B" : 2, "C" : 2, "D" : 5}},
		{"key" : "key7", "xyz" : {"A" : 2,"B" : 3, "C" : 4, "D" : 3}},
        {"key" : "key8", "xyz" : {"A" : 4,"B" : 1, "C" : 5, "D" : 2}},
        {"key" : "key9", "xyz" : {"A" : 3,"B" : 3, "C" : 1, "D" : 4}}

	];

	data = sampleData;
	element = radarElement;

	renderStreamgraph(streamElement, sampleData);
	updateRadar();

	$("#uploadLink").on("click", function(event){

    	event.preventDefault();
        $("#uploadInput").trigger("click");

    });

    $("#uploadInput").change(function() {

		var file = $("#uploadInput")[0].files[0];

		if (validateXLSX(file)) {

			showSpinner();
			parseXLSX(file);

		} else {

			showError();

		}

	});

});

function validateXLSX(file) {

	console.log(file.name);

	var extension = file.name.split(".").slice(-1)[0];

	return extension == ".xlsx" || extension == ".xls";

}

function parseXLSX(XLSX) {

	console.log("parsing", XLSX);

	var infoColumnCorrespondenceDict = {
		"chromosome" : ["CHROM", "Chromosome"],
		"ref" : ["REF", "Reference Allele", "Reference Nucleotide"],
		"alt" : ["ALT", "Sample Allele", "Variant Nucleotide"],
		"pos" : ["POS", "Position", "Start"]
	};

	var numericColumnCorrespondenceDict = {
		"fullExac" : ["hg19_popfreq_all_20150413_exac_all", "ExAC (AF%)", "ExAC (%)", "ExAC"],
		"europeExac": ["ExAC European", "hg19_popfreq_all_20150413_exac_nfe"],
		"1kgenomes": ["1000 Genomes", "hg19_popfreq_all_20150413_1000g_all"]
	};

	var stringColumnCorrespondenceDict = {
		"clinvar" : ["clinvar_clinical_significance"]
	};

	function readTSV(TSV) {

		data = [];
		lines = tsv.split("\n");
		columns = lines[0].split("\t");

		for (var line in lines) {

			data.append(line.split("\t"));

		}

		console.log(columns)
		console.log(data)

		return [columns, data];

	}

	function getVariantInfo(variantLine, idxDict, variantRecord) {

		var placeholder = [];

		for (var column in placeholder) {

			value = variantLine[idxDict[column]] || "na";

			if (column == "Gene_Summar") {
				choices = ["OR2T35", "BRCA1", "AFF3", "MYO7B", "ZNF806", "NEB", "SP100", "SYN2"];
				value = choices[Math.floor(Math.random() * choices.length)];
			}

		}

	}

	function writeFile(columns, lines, pos) {

	}

	var infoFieldTemplate = {
		"value" : "",
		"includeInDrawing" : false
	};

	var stringAnnotationTemplate = {
		"value": "",
		"drawingValue": "",
		"includeInDrawing": false,
		"associatedValues": []
	};

	var otherFieldTemplate = {
		"value": "",
		"drawingValue": "",
		"includeInDrawing": false,
		"associatedValues": []
	};

	function rowToJSON(row, curDf) {

		var variant = {};
		var coreAnnotationFields = {
			"infoFields" : "",
			"numericAnnotations" : "",
			"stringAnnotations" : "",
			"otherFields" : ""
		};

		variant["coreAnnotationFields"] = coreAnnotationFields;

		var infoFields = {};
		var numericAnnotations = {};
		var stringAnnotations = {};
		var otherFields = {};

		columns = curDf.columns;

		for (var column in columnames) {
		}

		variant["coreAnnotationFields"]["infoFields"] = infoFields;
		variant["coreAnnotationFields"]["numericAnnotations"] = numericAnnotations;
		variant["coreAnnotationFields"]["stringAnnotations"] = stringAnnotations;

		return variant;

	}

	function initalizeVariantMetadata(variant, sheetName) {
		variantMetadataStruct = {"metadata" : "", "workflow" : ""};
		metricsDict = {"numTimesClicked" : ""};
		workflowDict = {"curationMode" : sheetName, "freeTextNotes" : "enter any notes here"}
		variantMetadataStruct["metrics"] = metricsDict
		variantMetadataStruct["workflow"] = workflowDict

		return variantMetadataStruct;
	}

	function structToJSON(JSON) {
		parsed = json.loads(jsonFile);
		console.log(json.dumps(parsed))
	}

	function writeJSON(fileName, parsedJSON) {
		jsonFile = open(file, "w+")
		jsonFile.write(json.dumps(parsedJSON));
	}

}

function renderStreamgraph(element, data) {

	//add baseline data so no variants are on the egde of the graph
	data.unshift({"key" : "keyX", "xyz" : {"A" : 0,"B" : 0, "C" : 0, "D" : 0}});
	data.push({"key" : "keyX", "xyz" : {"A" : 0,"B" : 0, "C" : 0, "D" : 0}});

	var h = $(element).height();
   	var w = $(element).width();

	var flattened = $.map(data, function(element, index) {
		return element.xyz;
	});

	var stacker = d3.stack().keys(["A", "B", "C", "D"]).offset(d3.stackOffsetNone);
	var stacked = stacker(flattened);

	var xScale = d3.scaleLinear()
   		.domain([0, flattened.length - 1])
   		.range([0, w])

	var yScale = d3.scaleLinear()
   		.domain([
   			d3.min(stacked, function(layer) { return d3.min(layer, function(d) { return d[0]; }); }),
   			d3.max(stacked, function(layer) { return d3.max(layer, function(d) { return d[1]; }); })
   		]).range([h - axisSpace, 10]); //leave space for axis //leave space at top?

	d3.select(element)
		.selectAll("path")
		.data(stacked)
		.enter()
		.append("path")
		.attr("d", function(datum, index) {

			var area = d3.area()
				.curve(d3.curveCardinal)
				.x(function(d, i) {
					return xScale(i);
				}).y0(function(d, i) {
					return yScale(d[0]);
				}).y1(function(d, i) {
					return yScale(d[1]); }
				);

			return area(datum, index);

		}).attr("fill", getRandomColor);

	d3.select(element)
		.append("g")
		.attr("class", "xAxis")
		.attr("transform", "translate(0," + (h - axisSpace) + ")")
		.call(xAxis());

	var clipTicks = true;

	function xAxis() {
    	return d3.axisBottom(xScale)
    		.tickSize(0) //custom resize later
			.tickFormat(function(datum, index) {

    			return index == 0 || index == data.length - 1 ? "" : data[datum].key;

    		});
    }

    customResizeTicks(data, yScale);

    d3.selectAll(".tick line")
    	.attr("id", function(datum, index) {

    		return "axisLine" + index;

    	}).on("click", function(datum, index) {

    		d3.select(this).classed("selectedForRadar", !d3.select(this).classed("selectedForRadar")); //toggle the class
    		updateRadar();

    	});

    // haze(element);

}

function customResizeTicks(data, yScale) {

	console.log("custom resizing");
	console.log(d3.selectAll("g.xAxis g.tick line").size());
	console.log(yScale.domain());

	d3.selectAll("g.xAxis g.tick line")
		.attr("y2", function(datum, index) {

			var streamValues = data[index].xyz;
			var total = 0;

			for (var key in streamValues) {
				total += streamValues[key];
			}

			console.log("index: " + index + "; total: " + total);

			return (0 - yScale(15 - total + .1)); //why 15? //.1 so ends don't protrude

		});

}

function haze(element) {

	var nVariants = 9;
	var buffer = 4;

	var h = $(element).height();
	var w = $(element).width() / (nVariants - 1);

	var startXs = [...Array(nVariants - 1).keys()];
	startXs = $.map(startXs, e => e * w + buffer);

	d3.select(element)
		.append("g")
		.attr("class", "hazeContainer")
		.selectAll("rect.haze")
		.data(startXs)
		.enter()
		.append("rect")
		.attr("class", "haze")
		.attr("x", d => d)
		.attr("y", 0)
		.attr("width", w - (buffer * 2))
		.attr("height", h)
		.attr("opacity", .8)
		.attr("stroke", "black");

}

function updateRadar() {

	var selectedVariants = [];

	d3.selectAll(".selectedForRadar").each(function(element, index){

		var id = d3.select(this).attr("id");
		selectedVariants.push(parseInt(id.substring(id.indexOf("e") + 1))); //TODO: actually get key

	});

	var selectedData = $.map(selectedVariants, function(element, index) {
		return data[element];
	});

	// renderRadar(element, selectedData);

}

function renderRadar(element, data) {

	data = data.slice(0, 3)

	var formatted = $.map(data, function(variant, index) {
		var toPlot = [];
		for (var key in variant.xyz) {
			toPlot.push({
				"axis" : key,
				"value" : variant.xyz[key]
			});
		}

		return [toPlot];
	});

	var color = d3.scaleLinear()
				.range(["#EDC951","#CC333F","#00A0B0"]);

	var margin = {top: 50, right: 100, bottom: 100, left: 50};

	var radarChartOptions = {
		w: 200,
		h: 200,
		margin: margin,
		maxValue: 6,
	  	levels: 5,
	  	roundStrokes: true,
	  	color: color
	};

	RadarChart(element, formatted, radarChartOptions);

}

function sortOnKeys(data, keys, increasing) {

	return data.sort(function(a, b) {

		for (i = 0; i < keys.length; i++) {
			a = a[keys[i]];
			b = b[keys[i]];
		}

		return increasing ? d3.ascending(a, b) : d3.descending(a, b);

	});

}

function getRandomColor() {

	return "#" + Math.floor(Math.random() * 16777215).toString(16);
}

function renderJSON(JSON) {

}

function showSpinner() {

	console.log("showing");

	$("#spinnerContainer").show()
	$("#inputContainer").hide()

}

function hideSpinner() {

	console.log("hiding");

	$("#spinnerContainer").hide()
	$("#inputContainer").show()

}

function scrollToElement(element) {

	$("html, body").animate({
        scrollTop: $(element).offset().top
    }, 2000);

}<|endoftext|>


<commit_before>var app = require('app')
var BrowserWindow = require('browser-window')
var path = require('path');
var cp = require('child_process');
var fs = require('fs-plus');
var winston = require('winston');
var Menu = require('menu')
var menu, template

require('electron-debug')()
require('crash-reporter').start()

var logger = new winston.Logger({
    transports: [
        new winston.transports.File({
            level: 'info',
            filename: './file_experiment.log',
            handleExceptions: true,
            colorize: false
        }),
        new winston.transports.Console({
            level: 'info',
            handleExceptions: true,
            json: false,
            colorize: true
        })
    ],
    exitOnError: false
});

logger.info('STARTING MAIN.JS');

var exeName = path.basename(process.execPath);

var mainWindow = null

var handleSquirrelEvent = function() {
  if (process.platform != 'win32') {
    return false;
  }

  var squirrelCommand = process.argv[1];

  function spawnUpdate(args, callback) {
    var updateDotExe = path.resolve(path.dirname(process.execPath), '..', 'update.exe');
    var child = cp.spawn(updateDotExe, args);
    child.on('close', function(code) {
      callback();
    });
  };

  function removeShortcuts(callback) {
    spawnUpdate(['--removeShortcut', exeName], callback);
  };

  function createShortcuts(callback) {
    spawnUpdate(['--createShortcut', exeName], callback);
  };

  function updateShortcuts(callback) {
    logger.info('update shortcuts');
    var homeDir = fs.getHomeDirectory();
    logger.info(homeDir);
    if (homeDir) {
      desktopShortcutPath = path.join(homeDir, 'Desktop', 'Kimochi.lnk');
      logger.info(desktopShortcutPath);
      fs.exists(desktopShortcutPath, function(exists) {
        logger.info('does shortcut exist: ', exists);
        createShortcuts(function() {
          logger.info('within createShortcuts, does shortcut exist: ', exists);
          if(exists) {
            callback();
          } else {
            logger.info('trying to unlink: ', desktopShortcutPath);
            fs.unlink(desktopShortcutPath, callback);
          }
        });
      });
    } else {
      logger.info('no home dir, just create shortcuts');
      createShortcuts(callback);
    }
  };
  logger.info('SQUIRREL COMMAND: ', squirrelCommand);
  switch (squirrelCommand) {
    case '--squirrel-install':
      createShortcuts(function() {
        app.quit();
      });
      return true;
    case '--squirrel-updated':
      updateShortcuts(function() {
        app.quit();
      });
      return true;
    case '--squirrel-obsolete':
      app.quit();
      return true;
    case '--squirrel-uninstall':
      removeShortcuts(function() {
        app.quit();
      });
      return true;
    default:
      return false;
  }
};

if (handleSquirrelEvent()) {
   return;
}

if (process.platform == 'win32') {
  logger.info('UPDATE START');
  var updateDotExe = path.resolve(path.dirname(process.execPath),
   '..', 'update.exe');
  var child = cp.spawn(updateDotExe,
      ["--update", "https://blob.kimochi.co"]);
  child.on('close', function(code) {
    logger.info('UPDATE FINISHED');
      // anything you need to do when update is done.
  });
}

app.on('window-all-closed', function() {
  if (process.platform !== 'darwin') app.quit()
})

app.on('ready', function() {
  mainWindow = new BrowserWindow({ width: 1360, height: 900 });

  if (process.env.HOT) {
    if (process.env.STAGING) {
      console.log('staging hot dev');
      mainWindow.loadUrl('file://' + __dirname + '/app/hot-dev-app-staging.html')
    } else {
      console.log('hot dev');
      mainWindow.loadUrl('file://' + __dirname + '/app/hot-dev-app.html')
    }
  } else {
    if (process.env.STAGING) {
      console.log('staging not hot ');
      mainWindow.loadUrl('file://' + __dirname + '/app/app-staging.html')
    } else {
      console.log('not hot');
      mainWindow.loadUrl('file://' + __dirname + '/app/app.html')
    }
  }

  mainWindow.on('closed', function() {
    mainWindow = null
  })

  if (process.env.NODE_ENV === 'development') {
    mainWindow.openDevTools()
  }
  if (process.platform === 'darwin') {
    template = [{
      label: 'Kimochi',
      submenu: [{
        label: 'About [{',
        selector: 'orderFrontStandardAboutPanel:'
      }, {
        type: 'separator'
      }, {
        label: 'Services',
        submenu: []
      }, {
        type: 'separator'
      }, {
        label: 'Hide [{',
        accelerator: 'Command+H',
        selector: 'hide:'
      }, {
        label: 'Hide Others',
        accelerator: 'Command+Shift+H',
        selector: 'hideOtherApplications:'
      }, {
        label: 'Show All',
        selector: 'unhideAllApplications:'
      }, {
        type: 'separator'
      }, {
        label: 'Quit',
        accelerator: 'Command+Q',
        click: function() {
          app.quit()
        }
      }]
    }, {
      label: 'Edit',
      submenu: [{
        label: 'Undo',
        accelerator: 'Command+Z',
        selector: 'undo:'
      }, {
        label: 'Redo',
        accelerator: 'Shift+Command+Z',
        selector: 'redo:'
      }, {
        type: 'separator'
      }, {
        label: 'Cut',
        accelerator: 'Command+X',
        selector: 'cut:'
      }, {
        label: 'Copy',
        accelerator: 'Command+C',
        selector: 'copy:'
      }, {
        label: 'Paste',
        accelerator: 'Command+V',
        selector: 'paste:'
      }, {
        label: 'Select All',
        accelerator: 'Command+A',
        selector: 'selectAll:'
      }]
    }, {
      label: 'View',
      submenu: [{
        label: 'Reload',
        accelerator: 'Command+R',
        click: function() {
          mainWindow.restart()
        }
      }, {
        label: 'Toggle Full Screen',
        accelerator: 'Ctrl+Command+F',
        click: function() {
          mainWindow.setFullScreen(!mainWindow.isFullScreen())
        }
      }, {
        label: 'Toggle Developer Tools',
        accelerator: 'Alt+Command+I',
        click: function() {
          mainWindow.toggleDevTools()
        }
      }]
    }, {
      label: 'Window',
      submenu: [{
        label: 'Minimize',
        accelerator: 'Command+M',
        selector: 'performMiniaturize:'
      }, {
        label: 'Close',
        accelerator: 'Command+W',
        selector: 'performClose:'
      }, {
        type: 'separator'
      }, {
        label: 'Bring All to Front',
        selector: 'arrangeInFront:'
      }]
    }, {
      label: 'Help',
      submenu: [{
        label: 'Learn More',
        click: function() {
          require('shell').openExternal('http://electron.atom.io')
        }
      }, {
        label: 'Documentation',
        click: function() {
          require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme')
        }
      }, {
        label: 'Community Discussions',
        click: function() {
          require('shell').openExternal('https://discuss.atom.io/c/electron')
        }
      }, {
        label: 'Search Issues',
        click: function() {
          require('shell').openExternal('https://github.com/atom/electron/issues')
        }
      }]
    }]

    menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  } else {

    template = [{
      label: '&File',
      submenu: [{
        label: '&Open',
        accelerator: 'Ctrl+O'
      }, {
        label: '&Close',
        accelerator: 'Ctrl+W',
        click: function() {
          mainWindow.close()
        }
      }]
    }, {
      label: '&View',
      submenu: [{
        label: '&Reload',
        accelerator: 'Ctrl+R',
        click: function() {
          mainWindow.restart()
        }
      }, {
        label: 'Toggle &Full Screen',
        accelerator: 'F11',
        click: function() {
          mainWindow.setFullScreen(!mainWindow.isFullScreen())
        }
      }, {
        label: 'Toggle &Developer Tools',
        accelerator: 'Alt+Ctrl+I',
        click: function() {
          mainWindow.toggleDevTools()
        }
      }]
    }, {
      label: 'Help',
      submenu: [{
        label: 'Learn More',
        click: function() {
          require('shell').openExternal('http://electron.atom.io')
        }
      }, {
        label: 'Documentation',
        click: function() {
          require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme')
        }
      }, {
        label: 'Community Discussions',
        click: function() {
          require('shell').openExternal('https://discuss.atom.io/c/electron')
        }
      }, {
        label: 'Search Issues',
        click: function() {
          require('shell').openExternal('https://github.com/atom/electron/issues')
        }
      }]
    }]
    menu = Menu.buildFromTemplate(template)
    mainWindow.setMenu(menu);
    mainWindow.setMenuBarVisibility(false);
  }
})
<commit_msg>checkin 3<commit_after>var app = require('app')
var BrowserWindow = require('browser-window')
var path = require('path');
var cp = require('child_process');
var fs = require('fs-plus');
var winston = require('winston');
var Menu = require('menu')
var menu, template

require('electron-debug')()
require('crash-reporter').start()

var logger = new winston.Logger({
    transports: [
        new winston.transports.File({
            level: 'info',
            filename: './file_experiment.log',
            handleExceptions: true,
            colorize: false
        }),
        new winston.transports.Console({
            level: 'info',
            handleExceptions: true,
            json: false,
            colorize: true
        })
    ],
    exitOnError: false
});

logger.info('STARTING MAIN.JS');

var exeName = path.basename(process.execPath);

var mainWindow = null

var handleSquirrelEvent = function() {
  if (process.platform != 'win32') {
    return false;
  }

  var squirrelCommand = process.argv[1];

  function spawnUpdate(args, callback) {
    var updateDotExe = path.resolve(path.dirname(process.execPath), '..', 'update.exe');
    var child = cp.spawn(updateDotExe, args);
    child.on('close', function(code) {
      callback();
    });
  };

  function removeShortcuts(callback) {
    spawnUpdate(['--removeShortcut', exeName], callback);
  };

  function createShortcuts(callback) {
    spawnUpdate(['--createShortcut', exeName], callback);
  };

  function updateShortcuts(callback) {
    logger.info('update shortcuts');
    var homeDir = fs.getHomeDirectory();
    logger.info(homeDir);
    if (homeDir) {
      desktopShortcutPath = path.join(homeDir, 'Desktop', 'Kimochi.lnk');
      logger.info(desktopShortcutPath);
      fs.exists(desktopShortcutPath, function(exists) {
        logger.info('does shortcut exist: ', exists);
        createShortcuts(function() {
          logger.info('within createShortcuts, does shortcut exist: ', exists);
          if(exists) {
            callback();
          } else {
            logger.info('trying to unlink: ', desktopShortcutPath);
            fs.unlink(desktopShortcutPath, callback);
          }
        });
      });
    } else {
      logger.info('no home dir, just create shortcuts');
      createShortcuts(callback);
    }
  };
  logger.info('SQUIRREL COMMAND: ', squirrelCommand);
  switch (squirrelCommand) {
    case '--squirrel-install':
      createShortcuts(function() {
        app.quit();
      });
      return true;
    case '--squirrel-updated':
      updateShortcuts(function() {
        app.quit();
      });
      return true;
    case '--squirrel-obsolete':
      app.quit();
      return true;
    case '--squirrel-uninstall':
      removeShortcuts(function() {
        app.quit();
      });
      return true;
    default:
      return false;
  }
};

if (handleSquirrelEvent()) {
   return;
}

if (process.platform == 'win32') {
  logger.info('UPDATE START');
  var homeDir = fs.getHomeDirectory();
  if (homeDir) {
    desktopShortcutPath = path.join(homeDir, 'Desktop', 'Kimochi.lnk');
    fs.exists(desktopShortcutPath, function(exists) {
      var updateDotExe = path.resolve(path.dirname(process.execPath),
       '..', 'update.exe');
      var child = cp.spawn(updateDotExe,
          ["--update", "https://blob.kimochi.co"]);
      child.on('close', function(code) {
        if(exists) {
          logger.info('UNLINK: ', desktopShortcutPath);
          fs.unlink(desktopShortcutPath);
        }
        logger.info('UPDATE FINISHED');
      });
    });
  }
}

app.on('window-all-closed', function() {
  if (process.platform !== 'darwin') app.quit()
})

app.on('ready', function() {
  mainWindow = new BrowserWindow({ width: 1360, height: 900 });

  if (process.env.HOT) {
    if (process.env.STAGING) {
      console.log('staging hot dev');
      mainWindow.loadUrl('file://' + __dirname + '/app/hot-dev-app-staging.html')
    } else {
      console.log('hot dev');
      mainWindow.loadUrl('file://' + __dirname + '/app/hot-dev-app.html')
    }
  } else {
    if (process.env.STAGING) {
      console.log('staging not hot ');
      mainWindow.loadUrl('file://' + __dirname + '/app/app-staging.html')
    } else {
      console.log('not hot');
      mainWindow.loadUrl('file://' + __dirname + '/app/app.html')
    }
  }

  mainWindow.on('closed', function() {
    mainWindow = null
  })

  if (process.env.NODE_ENV === 'development') {
    mainWindow.openDevTools()
  }
  if (process.platform === 'darwin') {
    template = [{
      label: 'Kimochi',
      submenu: [{
        label: 'About [{',
        selector: 'orderFrontStandardAboutPanel:'
      }, {
        type: 'separator'
      }, {
        label: 'Services',
        submenu: []
      }, {
        type: 'separator'
      }, {
        label: 'Hide [{',
        accelerator: 'Command+H',
        selector: 'hide:'
      }, {
        label: 'Hide Others',
        accelerator: 'Command+Shift+H',
        selector: 'hideOtherApplications:'
      }, {
        label: 'Show All',
        selector: 'unhideAllApplications:'
      }, {
        type: 'separator'
      }, {
        label: 'Quit',
        accelerator: 'Command+Q',
        click: function() {
          app.quit()
        }
      }]
    }, {
      label: 'Edit',
      submenu: [{
        label: 'Undo',
        accelerator: 'Command+Z',
        selector: 'undo:'
      }, {
        label: 'Redo',
        accelerator: 'Shift+Command+Z',
        selector: 'redo:'
      }, {
        type: 'separator'
      }, {
        label: 'Cut',
        accelerator: 'Command+X',
        selector: 'cut:'
      }, {
        label: 'Copy',
        accelerator: 'Command+C',
        selector: 'copy:'
      }, {
        label: 'Paste',
        accelerator: 'Command+V',
        selector: 'paste:'
      }, {
        label: 'Select All',
        accelerator: 'Command+A',
        selector: 'selectAll:'
      }]
    }, {
      label: 'View',
      submenu: [{
        label: 'Reload',
        accelerator: 'Command+R',
        click: function() {
          mainWindow.restart()
        }
      }, {
        label: 'Toggle Full Screen',
        accelerator: 'Ctrl+Command+F',
        click: function() {
          mainWindow.setFullScreen(!mainWindow.isFullScreen())
        }
      }, {
        label: 'Toggle Developer Tools',
        accelerator: 'Alt+Command+I',
        click: function() {
          mainWindow.toggleDevTools()
        }
      }]
    }, {
      label: 'Window',
      submenu: [{
        label: 'Minimize',
        accelerator: 'Command+M',
        selector: 'performMiniaturize:'
      }, {
        label: 'Close',
        accelerator: 'Command+W',
        selector: 'performClose:'
      }, {
        type: 'separator'
      }, {
        label: 'Bring All to Front',
        selector: 'arrangeInFront:'
      }]
    }, {
      label: 'Help',
      submenu: [{
        label: 'Learn More',
        click: function() {
          require('shell').openExternal('http://electron.atom.io')
        }
      }, {
        label: 'Documentation',
        click: function() {
          require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme')
        }
      }, {
        label: 'Community Discussions',
        click: function() {
          require('shell').openExternal('https://discuss.atom.io/c/electron')
        }
      }, {
        label: 'Search Issues',
        click: function() {
          require('shell').openExternal('https://github.com/atom/electron/issues')
        }
      }]
    }]

    menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  } else {

    template = [{
      label: '&File',
      submenu: [{
        label: '&Open',
        accelerator: 'Ctrl+O'
      }, {
        label: '&Close',
        accelerator: 'Ctrl+W',
        click: function() {
          mainWindow.close()
        }
      }]
    }, {
      label: '&View',
      submenu: [{
        label: '&Reload',
        accelerator: 'Ctrl+R',
        click: function() {
          mainWindow.restart()
        }
      }, {
        label: 'Toggle &Full Screen',
        accelerator: 'F11',
        click: function() {
          mainWindow.setFullScreen(!mainWindow.isFullScreen())
        }
      }, {
        label: 'Toggle &Developer Tools',
        accelerator: 'Alt+Ctrl+I',
        click: function() {
          mainWindow.toggleDevTools()
        }
      }]
    }, {
      label: 'Help',
      submenu: [{
        label: 'Learn More',
        click: function() {
          require('shell').openExternal('http://electron.atom.io')
        }
      }, {
        label: 'Documentation',
        click: function() {
          require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme')
        }
      }, {
        label: 'Community Discussions',
        click: function() {
          require('shell').openExternal('https://discuss.atom.io/c/electron')
        }
      }, {
        label: 'Search Issues',
        click: function() {
          require('shell').openExternal('https://github.com/atom/electron/issues')
        }
      }]
    }]
    menu = Menu.buildFromTemplate(template)
    mainWindow.setMenu(menu);
    mainWindow.setMenuBarVisibility(false);
  }
})
<|endoftext|>


<commit_before>var five = require("johnny-five");
var board = new five.Board();

var stdin = process.stdin;
stdin.setRawMode(true);
stdin.resume();

board.on("ready", function () {
	//led stuff
	// var led = new five.Led(13);

	// this.repl.inject({
	// led: led
	// });

    var wheels = {
        left: new five.Servo({ pin: 9, type: 'continuous' }),
        right: new five.Servo({ pin: 10, type: 'continuous' }),
        stop: function () {
            wheels.left.center();
            wheels.right.center();
        },
        forward: function () {
            wheels.left.ccw();
            wheels.right.cw();
            console.log("goForward");
        },
        pivotLeft: function () {
            wheels.left.cw();
            wheels.right.cw();
            console.log("turnLeft");
        },
        pivotRight: function () {
            wheels.left.ccw();
            wheels.right.ccw();
            console.log("turnRight");
        },
        back: function () {
            wheels.left.cw();
            wheels.right.ccw();
        }
    };

    wheels.stop();
    console.log("Use the cursor keys or ASWD to move your bot. Hit escape or the spacebar to stop.");

    stdin.on("keypress", function(chunk, key) {
        if (!key) return;

        switch (key.name) {
        case 'up':
        case 'w':
            wheels.forward();
            break;

        case 'down':
        case 's':
            wheels.back();
            break;

        case 'left':
        case 'a':
            wheels.pivotLeft();
            break;

        case 'right':
        case 'd':
            wheels.pivotRight();
            break;

        case 'space':
        case 'escape':
            wheels.stop();
            break;
        }
    });
});<commit_msg>make it go very slow for testing<commit_after>var five = require("johnny-five");
var board = new five.Board();

var stdin = process.stdin;
stdin.setRawMode(true);
stdin.resume();

board.on("ready", function () {
	//led stuff
	// var led = new five.Led(13);

	// this.repl.inject({
	// led: led
	// });

    var wheels = {
        left: new five.Servo({ pin: 9, type: 'continuous' }),
        right: new five.Servo({ pin: 10, type: 'continuous' }),
        stop: function () {
            wheels.left.center();
            wheels.right.center();
        },
        forward: function () {
            wheels.left.ccw(0.1);
            wheels.right.cw(0.1);
            console.log("goForward");
        },
        pivotLeft: function () {
            wheels.left.cw(0.1);
            wheels.right.cw(0.1);
            console.log("turnLeft");
        },
        pivotRight: function () {
            wheels.left.ccw(0.1);
            wheels.right.ccw(0.1);
            console.log("turnRight");
        },
        back: function () {
            wheels.left.cw(0.1);
            wheels.right.ccw(0.1);
        }
    };

    wheels.stop();
    console.log("Use the cursor keys or ASWD to move your bot. Hit escape or the spacebar to stop.");

    stdin.on("keypress", function(chunk, key) {
        if (!key) return;

        switch (key.name) {
        case 'up':
        case 'w':
            wheels.forward();
            break;

        case 'down':
        case 's':
            wheels.back();
            break;

        case 'left':
        case 'a':
            wheels.pivotLeft();
            break;

        case 'right':
        case 'd':
            wheels.pivotRight();
            break;

        case 'space':
        case 'escape':
            wheels.stop();
            break;
        }
    });
});<|endoftext|>


<commit_before>/**
 * This is the main file of Pokémon Showdown Bot
 *
 * Some parts of this code are taken from the Pokémon Showdown server code, so
 * credits also go to Guangcong Luo and other Pokémon Showdown contributors.
 * https://github.com/Zarel/Pokemon-Showdown
 *
 * @license MIT license
 */

global.info = function(text) {
	if (config.debuglevel > 3) return;
	if (!colors) global.colors = require('colors');
	console.log('info'.cyan + '  ' + text);
};

global.debug = function(text) {
	if (config.debuglevel > 2) return;
	if (!colors) global.colors = require('colors');
	console.log('debug'.blue + ' ' + text);
};

global.recv = function(text) {
	if (config.debuglevel > 0) return;
	if (!colors) global.colors = require('colors');
	console.log('recv'.grey + '  ' + text);
};

global.cmdr = function(text) { // receiving commands
	if (config.debuglevel !== 1) return;
	if (!colors) global.colors = require('colors');
	console.log('cmdr'.grey + '  ' + text);
};

global.dsend = function(text) {
	if (config.debuglevel > 1) return;
	if (!colors) global.colors = require('colors');
	console.log('send'.grey + '  ' + text);
};

global.error = function(text) {
	if (!colors) global.colors = require('colors');
	console.log('error'.red + ' ' + text);
};

global.ok = function(text) {
	if (config.debuglevel > 4) return;
	if (!colors) global.colors = require('colors');
	console.log('ok'.green + '    ' + text);
};

global.toId = function(text) {
	return text.toLowerCase().replace(/[^a-z0-9]/g, '');
};

global.stripCommands = function(text) {
	text = text.trim();
	if (text.charAt(0) === '/') return '/' + text;
	if (text.charAt(0) === '!' || /^>>>? /.test(text)) return ' ' + text;
	return text;
};

function runNpm(command) {
	console.log('Running `npm ' + command + '`...');

	var child_process = require('child_process');
	var npm = child_process.spawn('npm', [command]);

	npm.stdout.on('data', function(data) {
		process.stdout.write(data);
	});

	npm.stderr.on('data', function(data) {
		process.stderr.write(data);
	});

	npm.on('close', function(code) {
		if (!code) {
			child_process.fork('main.js').disconnect();
		}
	});
}

// Check if everything that is needed is available
try {
	require('sugar');
	require('colors');
} catch (e) {
	console.log('Dependencies are not installed!');
	return runNpm('install');
}

if (!Object.select) {
	console.log('Node needs to be updated!');
	return runNpm('update');
}

// First dependencies and welcome message
var sys = require('sys');
global.colors = require('colors');

console.log('-----------------------------------|'.red);
console.log('|'.red + ' Welcome to AxeBane\'s'.green + ' Writing Bot!|'.red);
console.log('-----------------------------------|'.red);
console.log('');

// Config and config.js watching...
global.fs = require('fs');
if (!('existsSync' in fs)) {
	fs.existsSync = require('path').existsSync;
}

if (!fs.existsSync('./config.js')) {
	error('config.js doesn\'t exist; are you sure you copied or renamed config-example.js to config.js?');
	process.exit(-1);
}

global.config = require('./config.js');
global.Pokedex = require('./pokedex.js').pokedex;
global.Movedex = require('./movedex.js').movedex;

var checkCommandCharacter = function() {
	if (!/[^a-z0-9 ]/i.test(config.commandcharacter)) {
		error('invalid command character; should at least contain one non-alphanumeric character');
		process.exit(-1);
	}
};

checkCommandCharacter();

var watchFile = function() {
	try {
		return fs.watchFile.apply(fs, arguments);
	} catch (e) {
		error('your version of node does not support `fs.watchFile`');
	}
};

if (config.watchconfig) {
	watchFile('./config.js', function(curr, prev) {
		if (curr.mtime <= prev.mtime) return;
		try {
			delete require.cache[require.resolve('./config.js')];
			config = require('./config.js');
			info('reloaded config.js');
			checkCommandCharacter();
		} catch (e) {}
	});
}

// And now comes the real stuff...
info('starting server');

var WebSocketClient = require('websocket').client;
global.Commands = require('./commands.js').commands;
global.Parse = require('./parser.js').parse;

var connection = null;
var queue = [];
var dequeueTimeout = null;
var lastSentAt = 0;

global.send = function(data) {
	if (!connection.connected) return false;

	var now = Date.now();
	var diff = now - lastSentAt;
	if (diff < 650) {
		if (!dequeueTimeout) dequeueTimeout = setTimeout(dequeue, 650 - diff);
		queue.push(data);
		return false;
	}

	if (!Array.isArray(data)) data = [data.toString()];
	data = JSON.stringify(data);
	dsend(data);
	connection.send(data);

	lastSentAt = now;
	if (dequeueTimeout) {
		if (queue.length) {
			dequeueTimeout = setTimeout(dequeue, 650);
		} else {
			dequeueTimeout = null;
		}
	}
};

function dequeue() {
	send(queue.shift());
}

var connect = function(retry) {
	if (retry) {
		info('retrying...');
	}

	var ws = new WebSocketClient();

	ws.on('connectFailed', function(err) {
		error('Could not connect to server ' + config.server + ': ' + sys.inspect(err));
		info('retrying in one minute');

		setTimeout(function() {
			connect(true);
		}, 60000);
	});

	ws.on('connect', function(con) {
		connection = con;
		ok('connected to server ' + config.server);

		con.on('error', function(err) {
			error('connection error: ' + sys.inspect(err));
		});

		con.on('close', function() {
			// Is this always error or can this be intended...?
			error('connection closed: ' + sys.inspect(arguments));
			info('retrying in one minute');

			setTimeout(function() {
				connect(true);
			}, 60000);
		});

		con.on('message', function(message) {
			if (message.type === 'utf8') {
				recv(sys.inspect(message.utf8Data));
				Parse.data(message.utf8Data);
			}
		});
	});

	// The connection itself
	var id = ~~(Math.random() * 900) + 100;
	var chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
	var str = '';
	for (var i = 0, l = chars.length; i < 8; i++) {
		str += chars.charAt(~~(Math.random() * l));
	}

	var conStr = 'ws://' + config.server + ':' + config.port + '/showdown/' + id + '/' + str + '/websocket';
	info('connecting to ' + conStr + ' - secondary protocols: ' + sys.inspect(config.secprotocols));
	ws.connect(conStr, config.secprotocols);
};

connect();
<commit_msg>New Global Function: ToTitleCase<commit_after>/**
 * This is the main file of Pokémon Showdown Bot
 *
 * Some parts of this code are taken from the Pokémon Showdown server code, so
 * credits also go to Guangcong Luo and other Pokémon Showdown contributors.
 * https://github.com/Zarel/Pokemon-Showdown
 *
 * @license MIT license
 */

global.info = function(text) {
	if (config.debuglevel > 3) return;
	if (!colors) global.colors = require('colors');
	console.log('info'.cyan + '  ' + text);
};

global.debug = function(text) {
	if (config.debuglevel > 2) return;
	if (!colors) global.colors = require('colors');
	console.log('debug'.blue + ' ' + text);
};

global.recv = function(text) {
	if (config.debuglevel > 0) return;
	if (!colors) global.colors = require('colors');
	console.log('recv'.grey + '  ' + text);
};

global.cmdr = function(text) { // receiving commands
	if (config.debuglevel !== 1) return;
	if (!colors) global.colors = require('colors');
	console.log('cmdr'.grey + '  ' + text);
};

global.dsend = function(text) {
	if (config.debuglevel > 1) return;
	if (!colors) global.colors = require('colors');
	console.log('send'.grey + '  ' + text);
};

global.error = function(text) {
	if (!colors) global.colors = require('colors');
	console.log('error'.red + ' ' + text);
};

global.ok = function(text) {
	if (config.debuglevel > 4) return;
	if (!colors) global.colors = require('colors');
	console.log('ok'.green + '    ' + text);
};

global.toId = function(text) {
	return text.toLowerCase().replace(/[^a-z0-9]/g, '');
};

global.toTitleCase = function(str) {
    var strArr = str.split(' ');
    var newArr = [];
    for (var i = 0; i < strArr.length; i++) {
        newArr.push(strArr[i].charAt(0).toUpperCase() + strArr[i].slice(1));
    }
    str = newArr.join(' ');
    return str;
};

global.stripCommands = function(text) {
	text = text.trim();
	if (text.charAt(0) === '/') return '/' + text;
	if (text.charAt(0) === '!' || /^>>>? /.test(text)) return ' ' + text;
	return text;
};

function runNpm(command) {
	console.log('Running `npm ' + command + '`...');

	var child_process = require('child_process');
	var npm = child_process.spawn('npm', [command]);

	npm.stdout.on('data', function(data) {
		process.stdout.write(data);
	});

	npm.stderr.on('data', function(data) {
		process.stderr.write(data);
	});

	npm.on('close', function(code) {
		if (!code) {
			child_process.fork('main.js').disconnect();
		}
	});
}

// Check if everything that is needed is available
try {
	require('sugar');
	require('colors');
} catch (e) {
	console.log('Dependencies are not installed!');
	return runNpm('install');
}

if (!Object.select) {
	console.log('Node needs to be updated!');
	return runNpm('update');
}

// First dependencies and welcome message
var sys = require('sys');
global.colors = require('colors');

console.log('-----------------------------------|'.red);
console.log('|'.red + ' Welcome to AxeBane\'s'.green + ' Writing Bot!|'.red);
console.log('-----------------------------------|'.red);
console.log('');

// Config and config.js watching...
global.fs = require('fs');
if (!('existsSync' in fs)) {
	fs.existsSync = require('path').existsSync;
}

if (!fs.existsSync('./config.js')) {
	error('config.js doesn\'t exist; are you sure you copied or renamed config-example.js to config.js?');
	process.exit(-1);
}

global.config = require('./config.js');
global.Pokedex = require('./pokedex.js').pokedex;
global.Movedex = require('./movedex.js').movedex;

var checkCommandCharacter = function() {
	if (!/[^a-z0-9 ]/i.test(config.commandcharacter)) {
		error('invalid command character; should at least contain one non-alphanumeric character');
		process.exit(-1);
	}
};

checkCommandCharacter();

var watchFile = function() {
	try {
		return fs.watchFile.apply(fs, arguments);
	} catch (e) {
		error('your version of node does not support `fs.watchFile`');
	}
};

if (config.watchconfig) {
	watchFile('./config.js', function(curr, prev) {
		if (curr.mtime <= prev.mtime) return;
		try {
			delete require.cache[require.resolve('./config.js')];
			config = require('./config.js');
			info('reloaded config.js');
			checkCommandCharacter();
		} catch (e) {}
	});
}

// And now comes the real stuff...
info('starting server');

var WebSocketClient = require('websocket').client;
global.Commands = require('./commands.js').commands;
global.Parse = require('./parser.js').parse;

var connection = null;
var queue = [];
var dequeueTimeout = null;
var lastSentAt = 0;

global.send = function(data) {
	if (!connection.connected) return false;

	var now = Date.now();
	var diff = now - lastSentAt;
	if (diff < 650) {
		if (!dequeueTimeout) dequeueTimeout = setTimeout(dequeue, 650 - diff);
		queue.push(data);
		return false;
	}

	if (!Array.isArray(data)) data = [data.toString()];
	data = JSON.stringify(data);
	dsend(data);
	connection.send(data);

	lastSentAt = now;
	if (dequeueTimeout) {
		if (queue.length) {
			dequeueTimeout = setTimeout(dequeue, 650);
		} else {
			dequeueTimeout = null;
		}
	}
};

function dequeue() {
	send(queue.shift());
}

var connect = function(retry) {
	if (retry) {
		info('retrying...');
	}

	var ws = new WebSocketClient();

	ws.on('connectFailed', function(err) {
		error('Could not connect to server ' + config.server + ': ' + sys.inspect(err));
		info('retrying in one minute');

		setTimeout(function() {
			connect(true);
		}, 60000);
	});

	ws.on('connect', function(con) {
		connection = con;
		ok('connected to server ' + config.server);

		con.on('error', function(err) {
			error('connection error: ' + sys.inspect(err));
		});

		con.on('close', function() {
			// Is this always error or can this be intended...?
			error('connection closed: ' + sys.inspect(arguments));
			info('retrying in one minute');

			setTimeout(function() {
				connect(true);
			}, 60000);
		});

		con.on('message', function(message) {
			if (message.type === 'utf8') {
				recv(sys.inspect(message.utf8Data));
				Parse.data(message.utf8Data);
			}
		});
	});

	// The connection itself
	var id = ~~(Math.random() * 900) + 100;
	var chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
	var str = '';
	for (var i = 0, l = chars.length; i < 8; i++) {
		str += chars.charAt(~~(Math.random() * l));
	}

	var conStr = 'ws://' + config.server + ':' + config.port + '/showdown/' + id + '/' + str + '/websocket';
	info('connecting to ' + conStr + ' - secondary protocols: ' + sys.inspect(config.secprotocols));
	ws.connect(conStr, config.secprotocols);
};

connect();
<|endoftext|>