"use strict";
/**
* @file Pomelo -- proto
* Copyright(c) 2012 xiechengchao <xiecc@163.com>
* MIT Licensed
*/
const utils = require('./util/utils');
const logger = require('@sex-pomelo/sex-pomelo-logger').getLogger('pomelo', __filename);
const EventEmitter = require('events').EventEmitter;
const events = require('./util/events');
const appUtil = require('./util/appUtil');
const Constants = require('./util/constants');
const appManager = require('./common/manager/appManager');
const fs = require('fs');
const path = require('path');
const ChannelService = require('./common/service/channelService');
const SessionService = require('./common/service/sessionService');
const BackendSessionService = require('./common/service/backendSessionService');
/**
* The ServerInfo
* @typedef {Object} ServerInfo
* @property {string} main - The main javascript full path.
* @property {string} env - The run env.
* @property {string} id - The Server ID.
* @property {string} host - The Server run host.
* @property {number} port - The Server run port.
* @property {number} clientPort - The Server run client port.
* @property {string} frontend - The Server is frontend server 'true'|'false' .
* @property {string} serverType - The Server type.
*/
/**
* @typedef {import('./components/connector').Connector} Connector
* @typedef {import('./components/server').ServerComp} ServerComp
* @typedef {import('./components/monitor').MonitorComp} MonitorComp
* @typedef {import('./components/i18n').SexPomeloI18n} SexPomeloI18n
*/
/**
* The Default Components
* @typedef {Object} DefaultComponents
* @property {Connector} __connector__
* @property {BackendSessionService} __backendSession__
* @property {ChannelService} __channel__
* @property {ServerComp} __server__
* @property {MonitorComp} __monitor__
* @property {SexPomeloI18n} __i18n__
*/
/**
* Application states
* @enum {number}
*/
/** app has inited
* @constant
* @memberof Application
*/
let STATE_INITED = 1;
/** app start
* @constant
* @memberof Application
*/
let STATE_START = 2;
/** app has started
* @constant
* @memberof Application
*/
let STATE_STARTED = 3;
/** app app has stoped
* @constant
* @memberof Application
*/
let STATE_STOPED = 4;
/** rpc Invoke
*
* @example app.rpcInvoke( serId,
{ namespace:'user',
serverType:serType,
service:'gameRemote',
method:'leaveGame',
args: args
},function( err, result){
console.log('-----RPCNotify: err: ',err," result: ", result);
});
* @function rpcInvoke
* @param {String} serId Server'sID
* @param {Object} msg message object
* @param {string} msg.namespace 'user'|'sys'
* @param {string} msg.serverType
* @param {string} msg.service
* @param {string} msg.method
* @param {Array} msg.args
* @param {Function} cb callback
* @memberOf Application
*/
/** Application class.
* @typedef {Application} Application
* @ignore
*/
/**
* Application prototype.
* @class
*/
class Application{
constructor(){
/** The ChannelService
* @type {ChannelService}
*/
this.channelService = null;
/**
* The BackendSessionService
*
* @type {BackendSessionService}
*/
this.backendSessionService = null;
/**
* The SessionService
*
* @type {SessionService}
*/
this.sessionService = null;
/**
* App run mode
*
* @type {string}
*/
this.mode = '';
// 后面会重赋值的
this.rpcInvoke = (serId, msg, cb)=>{};
}
/**
* Initialize the server.
*
* - setup default configuration
*/
init(opts) {
opts = opts || {};
this.loaded = []; // loaded component list
/** @type {DefaultComponents} */
this.components = {}; // name -> component map
this.settings = {}; // collection keep set/get
let base = opts.base || path.dirname(require.main.filename);
this.set(Constants.RESERVED.BASE, base, true);
this.event = new EventEmitter(); // event object to sub/pub events
// current server info
/** current server id
* @type {string}
*/
this.serverId = null;
/** current server type
* @type {string}
*/
this.serverType = null;
/** current server info
* @type {ServerInfo}
*/
this.curServer = null;
/** current server start time, unix timestamp
* @type {number}
*/
this.startTime = null;
// global server infos
this.master = null; // master server info
this.servers = {}; // current global server info maps, id -> info
this.serverTypeMaps = {}; // current global type maps, type -> [info]
this.serverTypes = []; // current global server type list
this.lifecycleCbs = {}; // current server custom lifecycle callbacks
this.clusterSeq = {}; // cluster id sequence
appUtil.defaultConfiguration(this);
this.state = STATE_INITED;
logger.info('application inited: %j', this.getServerId());
}
/**
* Get application base path
*
* @example
* cwd: /home/game/
* pomelo start
* app.getBase() -> /home/game
*
* @return {String} application base path
*
* @memberOf Application
*/
getBase() {
return this.get(Constants.RESERVED.BASE);
}
/**
* Get application config path
*
* @example
* cwd: /home/game/
* pomelo start
* app.getCfgPath() -> /home/game/config/[env]
* @param {String?} cfgFile config file name
*
* @return {String} application base path
*
* @memberOf Application
*/
getCfgPath( cfgFile ) {
if( typeof(cfgFile) !== 'string' ){
cfgFile = '';
}
let env = this.get(Constants.RESERVED.ENV);
if( env !== Constants.RESERVED.ENV_PRO ){
let cfgPath = this.getBase() + `/config/${env}/${cfgFile}`;
if(fs.existsSync(cfgPath))
return cfgPath;
}
return this.getBase() + '/config/' + cfgFile;
}
/**
* Get application config file content
*
* @example
* cwd: /home/game/
* pomelo start
* app.getCfgSync() -> /home/game/config/[env]
* @param {String?} cfgFile config file name
* @param {boolean?} parseJson weather parse as JSON, default false
*
* @return {String} cfg file content
*
* @memberOf Application
*/
getCfgSync( cfgFile, parseJson=false ){
let cfgPath = this.getCfgPath( cfgFile );
if( fs.existsSync(cfgPath) ) {
const stat = fs.lstatSync(cfgPath);
if( stat.isFile() ){
let data = fs.readFileSync( cfgPath );
return (parseJson === true)? JSON.parse(data) : data.toString();
}
}
return (parseJson === true)? {} : "";
}
async getCfg(cfgFile, parseJson=false){
let cfgPath = this.getCfgPath( cfgFile );
try{
let data = await fs.promises.readFile( cfgPath );
return (parseJson === true)? JSON.parse(data) : data.toString();
} catch (err){
logger.warn(err);
return (parseJson === true)? {} : "";
}
}
/**
* Override require method in application
*
* @param {String} relative path of file
*
* @memberOf Application
*/
require(ph) {
return require(path.join(Application.getBase(), ph));
}
/**
* Configure logger with {$base}/config/log4js.json
*
* @param {Object} logger sex-pomelo-logger instance without configuration
*
* @memberOf Application
*/
configureLogger(logger) {
if (process.env.POMELO_LOGGER !== 'off') {
let base = this.getBase();
let env = this.get(Constants.RESERVED.ENV);
let originPath = path.join(base, Constants.FILEPATH.LOG);
let presentPath = path.join(base, Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.LOG));
if(fs.existsSync(presentPath)) {
logger.configure(presentPath, {serverId: this.serverId, base: base});
} else if(fs.existsSync(originPath)) {
logger.configure(originPath, {serverId: this.serverId, base: base});
} else {
logger.error('logger file path configuration is error.');
}
}
}
/**
* add a filter to before and after filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
filter (filter) {
this.before(filter);
this.after(filter);
}
/**
* Add before filter.
*
* @param {Object|Function} bf before filter, bf(msg, session, next)
* @memberOf Application
*/
before (bf) {
addFilter(this, Constants.KEYWORDS.BEFORE_FILTER, bf);
}
/**
* Add after filter.
*
* @param {Object|Function} af after filter, `af(err, msg, session, resp, next)`
* @memberOf Application
*/
after (af) {
addFilter(this, Constants.KEYWORDS.AFTER_FILTER, af);
}
/**
* add a global filter to before and after global filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
globalFilter (filter) {
this.globalBefore(filter);
this.globalAfter(filter);
}
/**
* Add global before filter.
*
* @param {Object|Function} bf before filter, bf(msg, session, next)
* @memberOf Application
*/
globalBefore (bf) {
addFilter(this, Constants.KEYWORDS.GLOBAL_BEFORE_FILTER, bf);
}
/**
* Add global after filter.
*
* @param {Object|Function} af after filter, `af(err, msg, session, resp, next)`
* @memberOf Application
*/
globalAfter (af) {
addFilter(this, Constants.KEYWORDS.GLOBAL_AFTER_FILTER, af);
}
/**
* Add rpc before filter.
*
* @param {Object|Function} bf before fileter, bf(serverId, msg, opts, next)
* @memberOf Application
*/
rpcBefore(bf) {
addFilter(this, Constants.KEYWORDS.RPC_BEFORE_FILTER, bf);
};
/**
* Add rpc after filter.
*
* @param {Object|Function} af after filter, `af(serverId, msg, opts, next)`
* @memberOf Application
*/
rpcAfter(af) {
addFilter(this, Constants.KEYWORDS.RPC_AFTER_FILTER, af);
};
/**
* add a rpc filter to before and after rpc filter
*
* @param {Object} filter provide before and after filter method.
* A filter should have two methods: before and after.
* @memberOf Application
*/
rpcFilter(filter) {
this.rpcBefore(filter);
this.rpcAfter(filter);
};
/**
* Load component
*
* @param {String} [name] (optional) name of the component
* @param {Object} component component instance or factory function of the component
* @param {Object} [opts] (optional) construct parameters for the factory function
* @return {Object} app instance for chain invoke
* @memberOf Application
*/
load(name, component, opts) {
if(typeof name !== 'string') {
opts = component;
component = name;
name = null;
}
if(typeof component === 'function') {
component = component(this, opts);
}
if(!name && typeof component.name === 'string') {
name = component.name;
}
if(name && this.components[name]) {
// ignore duplicate component
logger.warn('ignore duplicate component: %j', name);
return;
}
this.loaded.push(component);
if(name) {
// components with a name would get by name through app.components later.
this.components[name] = component;
}
return this;
};
/**
* Load Configure json file to settings.(support different environment directory & compatible for old path)
*
* @param {String} key environment key
* @param {String} val environment value
* @param {Boolean} reload whether reload after change default false
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
loadConfigBaseApp(key, val, reload) {
let self = this;
let env = this.get(Constants.RESERVED.ENV);
let originPath = path.join(this.getBase(), val);
let presentPath = path.join(this.getBase(), Constants.FILEPATH.CONFIG_DIR, env, path.basename(val));
let realPath;
if(fs.existsSync(presentPath)) {
realPath = presentPath;
let pfile = require(presentPath);
this.set(key, pfile);
} else if(fs.existsSync(originPath)) {
realPath = originPath;
let file = require(originPath);
if (file[env]) {
file = file[env];
}
this.set(key, file);
} else {
logger.error('invalid configuration with file path: %s', key);
}
if(!!realPath && !!reload) {
fs.watch(realPath, function (event, filename) {
if(event === 'change') {
delete require.cache[require.resolve(realPath)];
self.loadConfigBaseApp(key, val);
}
});
}
};
/**
* Load Configure json file to settings.
*
* @param {String} key environment key
* @param {String|object} val environment value
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
loadConfig(key, val) {
let typeOfVal = typeof(val);
if( typeOfVal === 'string' ) {
let env = this.get(Constants.RESERVED.ENV);
val = require(val);
if (val[env]) {
val = val[env];
}
this.set(key, val);
} else if( typeOfVal === 'object' ) {
this.set(key, val);
}
};
/**
* Set the route function for the specified server type.
*
* Examples:
*
* app.route('area', routeFunc);
*
* let routeFunc = function(session, msg, app, cb) {
* // all request to area would be route to the first area server
* let areas = app.getServersByType('area');
* cb(null, areas[0].id);
* };
*
* @param {String} serverType server type string
* @param {Function} routeFunc route function. routeFunc(session, msg, app, cb)
* @return {Object} current application instance for chain invoking
* @memberOf Application
*/
route(serverType, routeFunc) {
let routes = this.get(Constants.KEYWORDS.ROUTE);
if(!routes) {
routes = {};
this.set(Constants.KEYWORDS.ROUTE, routes);
}
routes[serverType] = routeFunc;
return this;
}
/**
* Set before stop function. It would perform before servers stop.
*
* @param {Function} fun before close function
* @return {Void}
* @memberOf Application
*/
beforeStopHook(fun) {
logger.warn('this method was deprecated in pomelo 0.8');
if(!!fun && typeof fun === 'function') {
this.set(Constants.KEYWORDS.BEFORE_STOP_HOOK, fun);
}
}
/**
* Start application. It would load the default components and start all the loaded components.
*
* @param {Function} cb callback function
* @memberOf Application
*/
start(cb) {
this.startTime = Date.now();
if(this.state > STATE_INITED) {
utils.invokeCallback(cb, new Error('application has already start.'));
return;
}
let self = this;
appUtil.startByType(self, function() {
appUtil.loadDefaultComponents(self);
let startUp = function() {
appUtil.optComponents(self.loaded, Constants.RESERVED.START, function(err) {
self.state = STATE_START;
if(err) {
utils.invokeCallback(cb, err);
} else {
logger.info('%j enter after start...', self.getServerId());
self.afterStart(cb);
}
});
};
let beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP];
if(!!beforeFun) {
beforeFun.call(null, self, startUp);
} else {
startUp();
}
});
}
/**
* Lifecycle callback for after start.
*
* @param {Function} cb callback function
* @return {Void}
*/
afterStart(cb) {
if(this.state !== STATE_START) {
utils.invokeCallback(cb, new Error('application is not running now.'));
return;
}
let afterFun = this.lifecycleCbs[Constants.LIFECYCLE.AFTER_STARTUP];
let self = this;
appUtil.optComponents(this.loaded, Constants.RESERVED.AFTER_START, function(err) {
self.state = STATE_STARTED;
let id = self.getServerId();
if(!err) {
logger.info('%j finish start', id);
}
if(!!afterFun) {
afterFun.call(null, self, function() {
utils.invokeCallback(cb, err);
});
} else {
utils.invokeCallback(cb, err);
}
let usedTime = Date.now() - self.startTime;
logger.info('%j startup in %s ms', id, usedTime);
self.event.emit(events.START_SERVER, id);
});
}
/**
* Stop components.
*
* @param {Boolean} force whether stop the app immediately
*/
stop(force) {
if(this.state > STATE_STARTED) {
logger.warn('[pomelo application] application is not running now.');
return;
}
this.state = STATE_STOPED;
let self = this;
this.stopTimer = setTimeout(function() {
process.exit(0);
}, Constants.TIME.TIME_WAIT_STOP);
let cancelShutDownTimer =function(){
if(!!self.stopTimer) {
clearTimeout(self.stopTimer);
}
};
let shutDown = function() {
appUtil.stopComps(self.loaded, 0, force, function() {
cancelShutDownTimer();
if(force) {
process.exit(0);
}
});
};
let fun = this.get(Constants.KEYWORDS.BEFORE_STOP_HOOK);
let stopFun = this.lifecycleCbs[Constants.LIFECYCLE.BEFORE_SHUTDOWN];
if(!!stopFun) {
stopFun.call(null, this, shutDown, cancelShutDownTimer);
} else if(!!fun) {
utils.invokeCallback(fun, self, shutDown, cancelShutDownTimer);
} else {
shutDown();
}
}
/**
* Assign `setting` to `val`, or return `setting`'s value.
*
* Example:
*
* app.set('key1', 'value1');
* app.get('key1'); // 'value1'
* app.key1; // undefined
*
* app.set('key2', 'value2', true);
* app.get('key2'); // 'value2'
* app.key2; // 'value2'
*
* @param {String} setting the setting of application
* @param {String} val the setting's value
* @param {Boolean} attach whether attach the settings to application
* @return {Server|Mixed} for chaining, or the setting value
* @memberOf Application
*/
set (setting, val, attach) {
if (arguments.length === 1) {
return this.settings[setting];
}
this.settings[setting] = val;
if(attach) {
this[setting] = val;
}
return this;
}
/**
* Get property from setting
*
* @param {string} setting application setting
* - 'channelService' return @see ChannelService
* - 'sessionService' return @see SessionService
* - 'backendSessionService' return @see BackendSessionService
* - '__routes__' return Object
* @return {string|ChannelService|SessionService|BackendSessionService} val
* @memberOf Application
*/
get (setting) {
return this.settings[setting];
}
/**
* Get ChannelService
*
* @return {ChannelService}
* @memberOf Application
*/
getChannelService(){
return this.settings['channelService'];
}
/**
* Get SessionService
*
* @return {SessionService}
* @memberOf Application
*/
getSessionService(){
return this.settings['sessionService'];
}
/**
* Get BackendSessionService
*
* @return {BackendSessionService}
* @memberOf Application
*/
getBackendSessionService(){
return this.settings['backendSessionService'];
}
/**
* Get Routes
*
* @return {object}
* @memberOf Application
*/
getRoutes(){
return this.settings['__routes__'];
}
/**
* Check if `setting` is enabled.
*
* @param {String} setting application setting
* @return {Boolean}
* @memberOf Application
*/
enabled (setting) {
return !!this.get(setting);
}
/**
* Check if `setting` is disabled.
*
* @param {String} setting application setting
* @return {Boolean}
* @memberOf Application
*/
disabled (setting) {
return !this.get(setting);
}
/**
* Enable `setting`.
*
* @param {String} setting application setting
* @return {app} for chaining
* @memberOf Application
*/
enable (setting) {
return this.set(setting, true);
}
/**
* Disable `setting`.
*
* @param {String} setting application setting
* @return {app} for chaining
* @memberOf Application
*/
disable (setting) {
return this.set(setting, false);
}
/**
* Configure callback for the specified env and server type.
* When no env is specified that callback will
* be invoked for all environments and when no type is specified
* that callback will be invoked for all server types.
*
* Examples:
*
* app.configure(function(){
* // executed for all envs and server types
* });
*
* app.configure('development', function(){
* // executed development env
* });
*
* app.configure('development', 'connector', function(){
* // executed for development env and connector server type
* });
*
* app.configure('development', '!connector|gate', function(){
* // executed for development env and not connector,not gate server type
* });
*
* @param {String} env application environment
* @param {Function} fn callback function
* @param {String} type server type, If the string starts with ! At first, this configuration is used by all servers except type
* @return {Application} for chaining
* @memberOf Application
*/
configure (env, type, fn) {
let args = [].slice.call(arguments);
fn = args.pop();
env = type = Constants.RESERVED.ALL;
if(args.length > 0) {
env = args[0];
}
if(args.length > 1) {
type = args[1];
}
if (env === Constants.RESERVED.ALL || contains(this.settings.env, env)) {
if (type === Constants.RESERVED.ALL || contains(this.settings.serverType, type)) {
fn.call(this);
}
}
return this;
}
/**
* Register admin modules. Admin modules is the extends point of the monitor system.
*
* @param {String} module (optional) module id or provoided by module.moduleId
* @param {Object} module module object or factory function for module
* @param {Object} opts construct parameter for module
* @memberOf Application
*/
registerAdmin(moduleId, module, opts) {
let modules = this.get(Constants.KEYWORDS.MODULE);
if(!modules) {
modules = {};
this.set(Constants.KEYWORDS.MODULE, modules);
}
if(typeof moduleId !== 'string') {
opts = module;
module = moduleId;
if(module) {
moduleId = module.moduleId;
}
}
if(!moduleId){
return;
}
modules[moduleId] = {
moduleId: moduleId,
module: module,
opts: opts
};
}
/**
* Use plugin.
*
* @param {Object} plugin plugin instance
* @param {Object} [opts] (optional) construct parameters for the factory function
* @memberOf Application
*/
use(plugin, opts) {
if(!plugin.components) {
logger.error('invalid components, no components exist');
return;
}
let self = this;
opts = opts || {};
let dir = path.dirname(plugin.components);
if(!fs.existsSync(plugin.components)) {
logger.error('fail to find components, find path: %s', plugin.components);
return;
}
fs.readdirSync(plugin.components).forEach(function (filename) {
if (!/\.js$/.test(filename)) {
return;
}
let name = path.basename(filename, '.js');
let param = opts[name] || {};
let absolutePath = path.join(dir, Constants.DIR.COMPONENT, filename);
if(!fs.existsSync(absolutePath)) {
logger.error('component %s not exist at %s', name, absolutePath);
} else {
self.load(require(absolutePath), param);
}
});
// load events
if(!plugin.events) {
return;
} else {
if(!fs.existsSync(plugin.events)) {
logger.error('fail to find events, find path: %s', plugin.events);
return;
}
fs.readdirSync(plugin.events).forEach(function (filename) {
if (!/\.js$/.test(filename)) {
return;
}
let absolutePath = path.join(dir, Constants.DIR.EVENT, filename);
if(!fs.existsSync(absolutePath)) {
logger.error('events %s not exist at %s', filename, absolutePath);
} else {
bindEvents(require(absolutePath), self);
}
});
}
}
/**
* Application transaction. Transaction includes conditions and handlers, if conditions are satisfied, handlers would be executed.
* And you can set retry times to execute handlers. The transaction log is in file logs/transaction.log.
*
* @param {String} name transaction name
* @param {Object} conditions functions which are called before transaction
* @param {Object} handlers functions which are called during transaction
* @param {Number} retry retry times to execute handlers if conditions are successfully executed
* @memberOf Application
*/
transaction(name, conditions, handlers, retry) {
appManager.transaction(name, conditions, handlers, retry);
};
/**
* Get master server info.
*
* @return {Object} master server info, {id, host, port}
* @memberOf Application
*/
getMaster() {
return this.master;
}
/**
* Get current server info.
*
* @return {ServerInfo} current server info, {id, serverType, host, port}
* @memberOf Application
*/
getCurServer() {
return this.curServer;
}
/**
* Get current server id.
*
* @return {String|Number} current server id from servers.json
* @memberOf Application
*/
getServerId() {
return this.serverId;
}
/**
* Get current server type.
*
* @return {String|Number} current server type from servers.json
* @memberOf Application
*/
getServerType() {
return this.serverType;
}
/**
* Get all the current server infos.
*
* @return {Object} server info map, key: server id, value: server info
* @memberOf Application
*/
getServers () {
return this.servers;
}
/**
* Get all server infos from servers.json.
*
* @return {Object} server info map, key: server id, value: server info
* @memberOf Application
*/
getServersFromConfig () {
return this.get(Constants.KEYWORDS.SERVER_MAP);
}
/**
* Get all the server type.
*
* @return {Array} server type list
* @memberOf Application
*/
getServerTypes() {
return this.serverTypes;
}
/**
* Get server info by server id from current server cluster.
*
* @param {String} serverId server id
* @return {Object} server info or undefined
* @memberOf Application
*/
getServerById (serverId) {
return this.servers[serverId];
}
/**
* Get server info by server id from servers.json.
*
* @param {String} serverId server id
* @return {Object} server info or undefined
* @memberOf Application
*/
getServerFromConfig (serverId) {
return this.get(Constants.KEYWORDS.SERVER_MAP)[serverId];
}
/**
* Get server infos by server type.
*
* @param {String} serverType server type
* @return {Array} server info list
* @memberOf Application
*/
getServersByType (serverType) {
return this.serverTypeMaps[serverType];
}
/**
* Check the server whether is a frontend server
*
* @param {server} server server info. it would check current server
* if server not specified
* @return {Boolean}
*
* @memberOf Application
*/
isFrontend (server) {
server = server || this.getCurServer();
return !!server && server.frontend === 'true';
}
/**
* Check the server whether is a backend server
*
* @param {server} server server info. it would check current server
* if server not specified
* @return {Boolean}
* @memberOf Application
*/
isBackend (server) {
server = server || this.getCurServer();
return !!server && !server.frontend;
}
/**
* Check whether current server is a master server
*
* @return {Boolean}
* @memberOf Application
*/
isMaster() {
return this.serverType === Constants.RESERVED.MASTER;
}
/**
* Add new server info to current application in runtime.
*
* @param {Array} servers new server info list
* @memberOf Application
*/
addServers (servers) {
if(!servers || !servers.length) {
return;
}
let item, slist;
for(let i=0, l=servers.length; i<l; i++) {
item = servers[i];
// update global server map
this.servers[item.id] = item;
// update global server type map
slist = this.serverTypeMaps[item.serverType];
if(!slist) {
this.serverTypeMaps[item.serverType] = slist = [];
}
replaceServer(slist, item);
// update global server type list
if(this.serverTypes.indexOf(item.serverType) < 0) {
this.serverTypes.push(item.serverType);
}
}
this.event.emit(events.ADD_SERVERS, servers);
}
/**
* Remove server info from current application at runtime.
*
* @param {Array} ids server id list
* @memberOf Application
*/
removeServers (ids) {
if(!ids || !ids.length) {
return;
}
const rmIds = [];
let id, item, slist;
for(let i=0, l=ids.length; i<l; i++) {
id = ids[i];
item = this.servers[id];
if(!item) {
continue;
}
rmIds.push(item);
// clean global server map
delete this.servers[id];
// clean global server type map
slist = this.serverTypeMaps[item.serverType];
removeServer(slist, id);
// TODO: should remove the server type if the slist is empty?
}
this.event.emit(events.REMOVE_SERVERS, rmIds);
}
/**
* Replace server info from current application at runtime.
*
* @param {Object} server id map
* @memberOf Application
*/
replaceServers (servers) {
if(!servers){
return;
}
this.servers = servers;
this.serverTypeMaps = {};
this.serverTypes = [];
let serverArray = [];
for(let id in servers){
let server = servers[id];
let serverType = server[Constants.RESERVED.SERVER_TYPE];
let slist = this.serverTypeMaps[serverType];
if(!slist) {
this.serverTypeMaps[serverType] = slist = [];
}
this.serverTypeMaps[serverType].push(server);
// update global server type list
if(this.serverTypes.indexOf(serverType) < 0) {
this.serverTypes.push(serverType);
}
serverArray.push(server);
}
this.event.emit(events.REPLACE_SERVERS, serverArray);
}
/**
* Add crons from current application at runtime.
*
* @param {Array} crons new crons would be added in application
* @memberOf Application
*/
addCrons (crons) {
if(!crons || !crons.length) {
logger.warn('crons is not defined.');
return;
}
this.event.emit(events.ADD_CRONS, crons);
}
/**
* Remove crons from current application at runtime.
*
* @param {Array} crons old crons would be removed in application
* @memberOf Application
*/
removeCrons (crons) {
if(!crons || !crons.length) {
logger.warn('ids is not defined.');
return;
}
this.event.emit(events.REMOVE_CRONS, crons);
}
/** tr by default lang
*
* @param {string} msg
* @param {...any} paras
* @return {string}
*
* @memberOf Application
*/
tr(msg, ...paras ){
if( this.components.__i18n__ ) {
return this.components.__i18n__.tr(msg,...paras)
}
return msg;
}
/** tr by lang
*
* @param {string} locale - the locale
* @param {string} msg
* @param {...any} paras
* @return {string}
*
* @memberOf Application
*/
tr1(locale,msg, ...paras ){
if( this.components.__i18n__ ) {
return this.components.__i18n__.tr1(locale,msg, ...paras);
}
return msg;
}
////////////
};
module.exports = Application;
let replaceServer = function(slist, serverInfo) {
for(let i=0, l=slist.length; i<l; i++) {
if(slist[i].id === serverInfo.id) {
slist[i] = serverInfo;
return;
}
}
slist.push(serverInfo);
};
let removeServer = function(slist, id) {
if(!slist || !slist.length) {
return;
}
for(let i=0, l=slist.length; i<l; i++) {
if(slist[i].id === id) {
slist.splice(i, 1);
return;
}
}
};
let contains = function(str, settings) {
if(!settings) {
return false;
}
let exclude = ( settings.charAt(0) === '!' );
let ts = settings.split("|");
if( exclude ){
ts[0] = ts[0].substr(1);
}
return exclude?(ts.indexOf(str) === -1) : (ts.indexOf(str) !== -1);
};
let bindEvents = function(Event, app) {
let emethods = new Event(app);
for(let m in emethods) {
if(typeof emethods[m] === 'function') {
app.event.on(m, emethods[m].bind(emethods));
}
}
};
let addFilter = function(app, type, filter) {
let filters = app.get(type);
if(!filters) {
filters = [];
app.set(type, filters);
}
filters.push(filter);
};