spincycle
Version:
A reactive message router and object manager that lets clients subscribe to object property changes on the server
413 lines (362 loc) • 13.9 kB
text/coffeescript
#uuid = require('node-uuid')
#$q = require('node-promise')
#lru = require('lru')
$q = Q
#debug = process.env['DEBUG']
opts =
max: 1000
maxAgeInMilliseconds: 1000 * 60 * 60 * 24 * 4 # 4 days timeout of objects no matter what
debug = true
uuid = UUID4
class Chillman
: new LRUCache()
: new LRUCache()
: (key, type, resolveFunc) =>
#console.log 'Chillman.lookup for '+key+' '+type
q = $q.defer()
underway = Chillman.underwayCache.get(key+'_'+type)
if underway
callbacks = Chillman.callbackCache.get(key+'_'+type) or []
callbacks.push q
Chillman.callbackCache.set(key+'_'+type, callbacks)
else
Chillman.underwayCache.set(key+'_'+type, true)
Chillman._doLookup(key, type, resolveFunc, q)
return q.promise
: (key, type, resolveFunc, q) =>
#console.log 'Chillman._doLookup for '+key+' '+type
resolveFunc(key, type).then (result) =>
# console.log 'Chillman._doLookup got reply from resoolvefunc for '+key+' '+type
Chillman.underwayCache.remove(key+'_'+type)
callbacks = Chillman.callbackCache.get(key+'_'+type) or []
cbcount = callbacks.length
callbacks.forEach (_q) =>
#console.log 'calling callback '+cbcount+' for '+key+' and '+type
_q.resolve(result)
if --cbcount == 0
#console.log 'removing callback cache entry for '+key+'_'+type
Chillman.callbackCache.remove(key+'_'+type)
q.resolve(result)
class spinpolymer
constructor: ( ) ->
= false
= {}
= []
= {}
= {}
= []
= {}
= []
= []
= []
= null
= new LRUCache(opts)
= false
= ''
= new LRUCache({max:1000, maxAgeInMilliseconds: 5000})
if debug then console.log 'polymer-spincycle dbUrl = ' +
['OBJECT_UPDATE'] = [(obj) =>
#console.log 'spinpolymer +++++++++ obj update message router got obj '+obj.id+' of type '+obj.type
#console.dir(obj);
#console.dir( )
objsubs = [obj.id] or []
for k,v of objsubs
#console.log 'updating subscriber to @objects updates on id '+k
if not .get(obj.id)
.set(obj.id, obj)
else
o = .get(obj.id)
for prop, val of obj
o[prop] = val
v obj
]
['POPULATION_UPDATE'] = [(update) =>
#console.log 'spinpolymer +++++++++ population update message router got update'
#console.dir update
obj = update.added or update.removed
if obj
objsubs = [obj.type] or {}
for k,v of objsubs
if v.cb then v.cb update
]
on: (id,type,cb, onlyupdates)->
if typeof id == 'object' then xyzzy()
if not onlyupdates then .then (o)->cb(o)
get: (type, id)->
if typeof id == 'object' then xyzzy()
d = $q.defer()
o = .get(id)
if not o
#console.log '******************X get calling server for obj '+id+' of type '+type
Chillman.lookup(id, type, ).then (oo)-> d.resolve(oo)
else
#console.log 'get found obj for '+id+' in cache of type '+type
d.resolve(o)
return d.promise
_doGet: (k,t)=>
#console.log '******************* _doGet calling server for obj '+k+' of type '+t
dd = $q.defer()
.then (_oo)=>
#console.log '******************* server replied for obj '+k+' of type '+t
.set(_oo.id, _oo)
dd.resolve(_oo)
return dd.promise
save: (o) ->
.set(o.id, o)
.then (sres)->console.log('saved obj result: '+sres)
failed: (msg)->
console.log 'spinclient message failed!! ' + JSON.toString(msg)
if then msg.info
setSessionId: (id) ->
if(id)
console.log '++++++++++++++++++++++++++++++++++++++ spinclient setting session id to ' + id
= id
dumpOutstanding: ()->
console.log '-------------------------------- ' + .length + ' outstanding messages ---------------------------------'
.forEach (os)->
console.log os.messageId + ' -> ' + os.target + ' - ' + os.d
console.log '-----------------------------------------------------------------------------------------'
emit: (message) =>
_emit:(message)=>
#console.log 'emitting message '+message.target
#console.dir message
.set(message.messageId, message)
.emit('message', JSON.stringify(message))
setup: () =>
console.log '.....connecting to "' + + "'"
= io( )
.on 'connect', ()=>
.on 'message', (reply) =>
#console.log '***** got message ******'
#console.dir reply
status = reply.status
message = reply.payload
info = reply.info
isNew = not reply.messageId
isPopulationUpdate = (reply.info == 'POPULATION_UPDATE')
#console.log 'info = '+info
if info == 'list of available targets'
console.log 'Spincycle server channel is up and awake'
= true
else
if message and message.error and message.error == 'ERRCHILLMAN'
oldmsg = [reply.messageId]
if oldmsg
console.log 'got ERRCHILLMAN from spinycle service, preparing to retry sending message...'
setTimeout(
()=>
console.log 'resending message '+oldmsg.messageId+' due to target endpoint not open yet'
,250
)
else if isNew
.remove(reply.messageId)
if reply.messageId and reply.messageId isnt 'undefined' then .push(reply.messageId)
if .length > 10 then .shift()
index = -1
if reply.messageId
i = 0
while i < .length
index = i
detail = [i]
if detail and not detail.delivered and detail.messageId == reply.messageId
if reply.status == 'FAILURE' or reply.status == 'NOT_ALLOWED'
console.log 'spinclient message FAILURE'
console.dir reply
= true
= reply.info
console.log '--- initial message was'
console.dir detail
detail.d.reject reply
break
else
#console.log 'delivering message '+message+' reply to '+detail.target+' to '+reply.messageId
detail.d.resolve(message)
break
detail.delivered = true
i++
if index > -1
.splice index, 1
else
subs = [info]
if subs
subs.forEach (listener) ->
listener message
else
if debug then console.log 'no subscribers for message ' + message
if debug then console.dir reply
else
if debug then console.log '-- skipped resent message ' + reply.messageId
hasSeenThisMessage: (messageId) =>
.some (mid) -> messageId == mid
registerListener: (detail) =>
#console.log 'spinclient::registerListener called for ' + detail.message
subs = [detail.message] or []
subs.push detail.callback
[detail.message] = subs
deRegisterPopulationChangesSubscriber: (detail) =>
sid = detail.listenerid
type = detail.type
localsubs = [type]
if localsubs[sid]
console.log 'deregistering local updates for model type ' + type
delete localsubs[sid]
count = 0
for k,v in localsubs
count++
if count == 1 # only remotesid property left
_deRegisterPopulationChangesSubscriber: (sid, type) =>
subs = [type] or []
if subs and subs[sid]
delete subs[sid]
[type] = subs
.then (reply)->
console.log 'deregistering server updates for population changes for '+type
registerPopulationChangeSubscriber: (detail) =>
#console.log 'registerPopulationChangeSubscriber called for '+detail.type
d = $q.defer()
sid = uuid.generate()
localsubs = [detail.type]
if not localsubs
localsubs = {}
.then( (remotesid) =>
localsubs['remotesid'] = remotesid
localsubs[sid] = detail
[detail.type] = localsubs
d.resolve(sid)
,(rejection)=>
console.log 'spinpolymer registerPopulationSubscriber rejection: '+rejection
console.dir rejection
)
else
localsubs[sid] = detail
return d.promise
_registerPopulationSubscriber: (detail) =>
d = $q.defer()
subs = [detail.type] or {}
#console.log '_registerPopulationChangeSubscriber called for '+detail.type
.then(
(reply)=>
subs[reply] = detail.cb
[detail.type] = subs
d.resolve(reply)
, (reply)=>
)
return d.promise
registerObjectSubscriber: (detail) =>
d = $q.defer()
sid = uuid.generate()
localsubs = [detail.id]
if not localsubs
localsubs = []
.then( (remotesid) =>
localsubs['remotesid'] = remotesid
localsubs[sid] = detail
[detail.id] = localsubs
#console.log 'spinclient registered observer for object type '+detail.type+' id '+detail.id
d.resolve(sid)
,(rejection)=>
console.log 'spinpolymer registerObjectSubscriber rejection: '+rejection
console.dir rejection
)
else
localsubs[sid] = detail
return d.promise
_registerObjectSubscriber: (detail) =>
d = $q.defer()
subs = [detail.id] or []
.then(
(reply)=>
subs[reply] = detail.cb
[detail.id] = subs
d.resolve(reply)
, (reply)=>
)
return d.promise
deRegisterObjectsSubscriber: (sid, o) =>
localsubs = [o.id] or []
if localsubs[sid]
#console.log 'deregistering local updates for @objects ' + o.id
delete localsubs[sid]
count = 0
for k,v in localsubs
count++
if count == 1 # only remotesid property left
_deRegisterObjectsSubscriber: (sid, o) =>
subs = [o.id] or []
if subs and subs[sid]
delete subs[sid]
[o.id] = subs
.then (reply)->
#console.log 'deregistering server updates for @objects ' + o.id
emitMessage: (detail) =>
#if debug then console.log 'emitMessage called'
#if debug then console.dir detail
d = $q.defer()
detail.messageId = uuid.generate()
detail.sessionId = detail.sessionId or
detail.d = d
#console.log '------------------> EmitMessage sessionId = '+detail.sessionId
.push detail
#if debug then console.log 'saving outstanding reply to messageId ' + detail.messageId + ' and @sessionId ' + detail.sessionId
detail
return d.promise
# ------------------------------------------------------------------------------------------------------------------
getModelFor: (type) =>
d = $q.defer()
if [type]
#console.log 'getModelFor found model in cache..'
d.resolve( [type])
else
.then((model)=>
#console.log 'getModelFor got model from server'
#console.dir model
[type] = model
#console.log 'getModelFor resolving.....'
d.resolve(model)
,(rejection)=>
console.log '+++++++++++++++++ spinpolymer getModelFor rejection: '+rejection
console.dir rejection
)
return d.promise
listTargets: () =>
d = $q.defer()
.then((targets)->
d.resolve(targets)
,(rejection)->
console.log 'spinpolymer listTargets rejection: '+rejection
console.dir rejection
)
return d.promise
flattenModel: (model) =>
rv = {}
for k,v of model
if angular.isArray(v)
rv[k] = v.map (e) -> e.id
else
rv[k] = v
return rv
window.SpinClient = spinpolymer