commit 153265b0c0e3b10016201089564b7390f811941f Author: 斟酌 鵬兄 Date: Tue May 31 17:55:08 2016 +0800 Initial commit diff --git a/infrastructure.js b/infrastructure.js new file mode 100644 index 0000000..7653725 --- /dev/null +++ b/infrastructure.js @@ -0,0 +1,84 @@ +"use stict"; + +const cl = global.botanLoader; + +const events = require( "events" ); +const util = require( "util" ); + +var Infrastructure = function() +{ + events.EventEmitter.call( this ); + var _self = this; + + var __readyList = []; + this.readyList = new Proxy( __readyList, { + + get: ( target, prop ) => Reflect.get( target, prop ) + + , set: ( target, prop, value ) => { + + // Have to emit the event at next tick + // due to error thrown from the handlers + // hangs the process + process.nextTick( () => { + if( __readyList.every( ( v ) => v === true ) ) + { + _self.isReady = true; + _self.emit( "apis_ready", _self ) + } + } ); + + return Reflect.set( target, prop, value ); + } + + } ); + + this.APIs = {}; + this.isReady = true; +}; + +util.inherits( Infrastructure, events.EventEmitter ); + +Infrastructure.prototype.callAPI = function( name ) +{ + var _self = this; + + var propName = name.split( "." ); + propName = name[ name.length - 1 ]; + + if( this.APIs[ propName ] ) return this.APIs[ propName ]; + + var path = name.replace( ".", "/" ); + + var API = cl.load( name ); + + if( !API.create ) + { + throw new Error( "API does not have support calling" ); + } + + var aclass = API.create( Array.prototype.slice.call( arguments, 1 ) ); + + this.APIs[ propName ] = aclass; + + if( aclass.ready !== undefined ) + { + this.isReady = false; + + var rList = this.readyList; + rList.push( aclass.ready || aclass ); + + aclass.once( "ready", ( e ) => rList[ rList.indexOf( e ) ] = true ); + } + + return aclass; +}; + +Infrastructure.prototype.Ready = function( callback ) +{ + var _self = this; + if( this.isReady ) callback(); + else this.once( "apis_ready", () => callback() ); +}; + +module.exports = Infrastructure; diff --git a/redisclient.js b/redisclient.js new file mode 100644 index 0000000..f21acb2 --- /dev/null +++ b/redisclient.js @@ -0,0 +1,22 @@ +const cl = global.botanLoader; +const Dragonfly = global.Dragonfly; + +const Redis = require( "redis" ); + +const SessConf = cl.load( "config.sx.modular.session" ); + +var Client = Redis.createClient( + SessConf.config.port + , SessConf.config.host + , SessConf.config.options +); + +Client.addListener( "error", ( e ) => { throw e; } ); + +Client.select( SessConf.config.database, function( err, message ) +{ + if( err ) throw err; + Dragonfly.Info( "[Session] Database connected. Ready." ); +}); + +module.exports = Client; diff --git a/session.js b/session.js new file mode 100644 index 0000000..91d76e8 --- /dev/null +++ b/session.js @@ -0,0 +1,152 @@ +"use strict"; + +const cl = global.botanLoader; +const Dragonfly = global.Dragonfly; + +const util = require( "util" ); +const EventEmitter = require( "events" ).EventEmitter; + +const Client = cl.load( "botansx.modular.redisclient" ); +const rand = cl.load( "botansx.utils.random" ); +const SessConf = cl.load( "config.sx.modular.session" ); + +class SessionEventArgs +{ + constructor( message ) + { + this.message = message; + } +} + +class Session extends EventEmitter +{ + static create( args ) + { + if( !args ) args = []; + return new Session( args[0], args[1] ); + } + + constructor( hash, prefix, noIdReset ) + { + super(); + var _self = this; + + if( hash && prefix ) + hash = hash.replace( new RegExp( "^" + prefix + "\\." ), "" ); + + prefix = prefix ? prefix + "." : ""; + + this.id = prefix + hash; + this.ready = false; + this.exists = false; + + Client.HGETALL( this.id, function( err, obj ) + { + if( err ) throw err; + + if( _self.exists = !!obj ) + { + _self.__sess = obj; + } + // Auto reset the session id if no match + else if( !noIdReset ) + { + _self.id = prefix + rand.uuid(); + } + + _self.ready = true; + _self.emit( "ready", _self ); + } ); + + this.__sess = {}; + this.__emitOk = ( e, m ) => { + _self.__emit( e, "set", new SessionEventArgs( m ) ); + }; + } + + __emit( err, EventName, SessionArgs ) + { + if( err ) throw err; + this.emit( EventName || "set", this, SessionArgs ); + } + + + // Spawn session with specified id + spawn( expire, handler ) + { + var ttl = ( expire === undefined ? SessConf.ttl : expire ) + 0; + this.__sess[ "lifespan" ] = ttl; + + var _self = this; + Client.multi() + .HSET( this.id, "spawn", new Date() ) + .HSET( this.id, "lifespan", ttl ) + .EXPIRE( this.id, ttl ) + .exec( handler || this.__emitOk ); + } + + destroy( handler ) + { + this.__sess = {}; + Client.DEL( this.id, handler || this.__emitOk ); + } + + set( name, val ) + { + this.__sess[ name ] = val; + Client.HSET( this.id, name, val, this.__emitOk ); + } + + get( name ) + { + return this.__sess[ name ]; + } + + // Get value asynchronously + aget( name, handler ) + { + var _self = this; + Client.HGET( + this.id, name + , function( err, rep ) + { + _self.__emitOk( err ); + _self.__sess[ name ] = rep; + handler && handler( rep ); + } + ); + } + + remove( name ) + { + delete this.__sess[ name ]; + Client.HDEL( this.id, name, this.__emitOk ); + } + + expire( seconds ) + { + // expire > lifespan > SessConf.ttl + var ttl = ( seconds === undefined + ? ( ( this.__sess[ "lifespan" ] === undefined ) + ? SessConf.ttl + : this.__sess[ "lifespan" ] + ) : seconds ) + 0 + ; + + this.__sess[ "lifespan" ] = ttl; + + Client.multi() + .HSET( this.id, "lifespan", ttl ) + .EXPIRE( this.id, ttl, this.__emitOk ) + .exec( this.__emitOk ); + } + + get busy() + { + return 0 < ( Client.command_queue.length + Client.offline_queue.length ); + } +} + +Session.SessionEventArgs = SessionEventArgs; + +module.exports = Session;