Newer
Older
/* Synchronet JavaScript "Queue" Object */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include "sbbs.h"
#include "msg_queue.h"
#include "js_request.h"
typedef struct
{
char name[128];
size_t size;
uint64 *value;
link_list_t named_queues;
/* Queue Destructor */
static void js_finalize_queue(JSContext *cx, JSObject *obj)
{
msg_queue_t* q;
list_node_t* n;
if((q=(msg_queue_t*)JS_GetPrivate(cx,obj))==NULL)
return;
if(msgQueueDetach(q)==0 && (n=listFindNode(&named_queues,q,/* length=0 for ptr compare */0))!=NULL)
listRemoveNode(&named_queues,n,false);
JS_SetPrivate(cx, obj, NULL);
}
static void js_decode_value(JSContext *cx, JSObject *parent
,queued_value_t* v, jsval* rval)
{
*rval = JSVAL_VOID;
if(v==NULL)
return;
JS_ReadStructuredClone(cx, v->value, v->size, JS_STRUCTURED_CLONE_VERSION, rval, NULL, NULL);
return;
}
/* Queue Object Methods */
extern JSClass js_queue_class;
static JSBool
js_poll(JSContext *cx, uintN argc, jsval *arglist)
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
jsval *argv=JS_ARGV(cx, arglist);
msg_queue_t* q;
queued_value_t* v;
int32 timeout=0;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
return(JS_FALSE);
}
if(argc && JSVAL_IS_NUMBER(argv[0])) { /* timeout specified */
if(!JS_ValueToInt32(cx,argv[0],&timeout))
return JS_FALSE;
}
rc=JS_SUSPENDREQUEST(cx);
JS_RESUMEREQUEST(cx, rc);
JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,v->name)));
JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
return(JS_TRUE);
}
js_read(JSContext *cx, uintN argc, jsval *arglist)
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
jsval *argv=JS_ARGV(cx, arglist);
msg_queue_t* q;
queued_value_t find_v;
queued_value_t* v;
int32 timeout=0;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
return(JS_FALSE);
}
if(argc && JSVAL_IS_STRING(argv[0])) { /* value named specified */
JSVALUE_TO_STRBUF(cx, argv[0], find_v.name, sizeof(find_v.name), NULL);
rc=JS_SUSPENDREQUEST(cx);
v=msgQueueFind(q,&find_v,sizeof(find_v.name));
JS_RESUMEREQUEST(cx, rc);
} else {
if(argc && JSVAL_IS_NUMBER(argv[0])) {
if(!JS_ValueToInt32(cx,argv[0],&timeout))
return JS_FALSE;
}
rc=JS_SUSPENDREQUEST(cx);
v=msgQueueRead(q, timeout);
JS_RESUMEREQUEST(cx, rc);
if(v!=NULL) {
jsval rval;
js_decode_value(cx, obj, v, &rval);
free(v->value);
free(v);
JS_SET_RVAL(cx, arglist, rval);
return(JS_TRUE);
}
static JSBool
js_peek(JSContext *cx, uintN argc, jsval *arglist)
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
jsval *argv=JS_ARGV(cx, arglist);
msg_queue_t* q;
queued_value_t* v;
int32 timeout=0;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
return(JS_FALSE);
}
if(argc && JSVAL_IS_NUMBER(argv[0])) { /* timeout specified */
if(!JS_ValueToInt32(cx,argv[0],&timeout))
return JS_FALSE;
}
rc=JS_SUSPENDREQUEST(cx);
JS_RESUMEREQUEST(cx, rc);
jsval rval;
js_decode_value(cx, obj, v, &rval);
JS_SET_RVAL(cx, arglist, rval);
return(JS_TRUE);
}
static queued_value_t* js_encode_value(JSContext *cx, jsval val, char* name)
queued_value_t *v;
uint64 *serialized;
if((v=malloc(sizeof(queued_value_t)))==NULL)
return(NULL);
memset(v,0,sizeof(queued_value_t));
if(name!=NULL)
SAFECOPY(v->name,name);
if(!JS_WriteStructuredClone(cx, val, &serialized, &v->size, NULL, NULL)) {
free(v);
return NULL;
if((v->value=(uint64 *)malloc(v->size))==NULL) {
JS_free(cx, serialized);
return NULL;
}
memcpy(v->value, serialized, v->size);
JS_free(cx, serialized);
return(v);
}
bool js_enqueue_value(JSContext *cx, msg_queue_t* q, jsval val, char* name)
{
queued_value_t* v;
if((v=js_encode_value(cx,val,name))==NULL)
rc=JS_SUSPENDREQUEST(cx);
result=msgQueueWrite(q,v,sizeof(queued_value_t));
free(v);
JS_RESUMEREQUEST(cx, rc);
return(result);
}
static JSBool
js_write(JSContext *cx, uintN argc, jsval *arglist)
JSObject *obj=JS_THIS_OBJECT(cx, arglist);
jsval *argv=JS_ARGV(cx, arglist);
uintN argn=0;
msg_queue_t* q;
jsval val;
char* name=NULL;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
return(JS_FALSE);
}
val = argv[argn++];
JSVALUE_TO_MSTRING(cx, argv[argn], name, NULL);
argn++;
HANDLE_PENDING(cx, name);
JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(js_enqueue_value(cx, q, val, name)));
return(JS_TRUE);
}
/* Queue Object Properites */
enum {
QUEUE_PROP_NAME
,QUEUE_PROP_DATA_WAITING
,QUEUE_PROP_READ_LEVEL
,QUEUE_PROP_WRITE_LEVEL
,QUEUE_PROP_OWNER
,QUEUE_PROP_ORPHAN
#ifdef BUILD_JSDOCS
static const char* queue_prop_desc[] = {
,"<tt>true</tt> if data is waiting to be read from queue"
,"Number of values in the read queue"
,"Number of values in the write queue"
,"<tt>true</tt> if current thread is the owner/creator of the queue"
,"<tt>true</tt> if the owner of the queue has detached from the queue"
,NULL
};
#endif
static JSBool js_queue_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
jsval idval;
jsint tiny;
msg_queue_t* q;
if((q=(msg_queue_t*)JS_GetPrivate(cx,obj))==NULL)
return JS_FALSE;
JS_IdToValue(cx, id, &idval);
tiny = JSVAL_TO_INT(idval);
case QUEUE_PROP_NAME:
*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,q->name));
break;
case QUEUE_PROP_DATA_WAITING:
rc=JS_SUSPENDREQUEST(cx);
*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(msgQueueReadLevel(q)));
JS_RESUMEREQUEST(cx, rc);
case QUEUE_PROP_READ_LEVEL:
rc=JS_SUSPENDREQUEST(cx);
*vp = INT_TO_JSVAL(msgQueueReadLevel(q));
JS_RESUMEREQUEST(cx, rc);
break;
case QUEUE_PROP_WRITE_LEVEL:
rc=JS_SUSPENDREQUEST(cx);
*vp = INT_TO_JSVAL(msgQueueWriteLevel(q));
JS_RESUMEREQUEST(cx, rc);
case QUEUE_PROP_OWNER:
rc=JS_SUSPENDREQUEST(cx);
*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(msgQueueOwner(q)));
JS_RESUMEREQUEST(cx, rc);
break;
case QUEUE_PROP_ORPHAN:
rc=JS_SUSPENDREQUEST(cx);
*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(q->flags & MSG_QUEUE_ORPHAN));
JS_RESUMEREQUEST(cx, rc);
break;
}
return(JS_TRUE);
}
#define QUEUE_PROP_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY
static jsSyncPropertySpec js_queue_properties[] = {
/* name ,tinyid ,flags, ver */
{ "name" ,QUEUE_PROP_NAME ,QUEUE_PROP_FLAGS, 312 },
{ "data_waiting" ,QUEUE_PROP_DATA_WAITING,QUEUE_PROP_FLAGS, 312 },
{ "read_level" ,QUEUE_PROP_READ_LEVEL ,QUEUE_PROP_FLAGS, 312 },
{ "write_level" ,QUEUE_PROP_WRITE_LEVEL ,QUEUE_PROP_FLAGS, 312 },
{ "owner" ,QUEUE_PROP_OWNER ,QUEUE_PROP_FLAGS, 316 },
{ "orphan" ,QUEUE_PROP_ORPHAN ,QUEUE_PROP_FLAGS, 31702 },
{0}
};
static jsSyncMethodSpec js_queue_functions[] = {
{"poll", js_poll, 1, JSTYPE_UNDEF, "[<i>number</i> timeout=0]"
,JSDOCSTR("Wait for any value to be written to the queue for up to <i>timeout</i> milliseconds "
"(default: <i>0</i>), returns <tt>true</tt> or the <i>name</i> (string) of "
"the value waiting (if it has one), or <tt>false</tt> if no values are waiting")
,312
},
{"read", js_read, 1, JSTYPE_UNDEF, "[<i>string</i> name] or [<i>number</i>timeout=0]"
,JSDOCSTR("Read a value from the queue, if <i>name</i> not specified, reads next value "
"from the bottom of the queue (waiting up to <i>timeout</i> milliseconds)")
{"peek", js_peek, 1, JSTYPE_UNDEF, "[<i>number</i> timeout=0]"
,JSDOCSTR("Peek at the value at the bottom of the queue, "
"wait up to <i>timeout</i> milliseconds for any value to be written "
"(default: <i>0</i>)")
{"write", js_write, 1, JSTYPE_BOOLEAN, "value [,name=<i>none</i>]"
,JSDOCSTR("Write a value (optionally named) to the queue")
,312
},
{0}
};
static JSBool js_queue_resolve(JSContext *cx, JSObject *obj, jsid id)
if(id != JSID_VOID && id != JSID_EMPTY) {
jsval idval;
JS_IdToValue(cx, id, &idval);
if(JSVAL_IS_STRING(idval)) {
JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
HANDLE_PENDING(cx, name);
ret=js_SyncResolve(cx, obj, name, js_queue_properties, js_queue_functions, NULL, 0);
if(name)
free(name);
return ret;
}
static JSBool js_queue_enumerate(JSContext *cx, JSObject *obj)
{
JSClass js_queue_class = {
"Queue" /* name */
,JSCLASS_HAS_PRIVATE /* flags */
,JS_PropertyStub /* addProperty */
,JS_PropertyStub /* delProperty */
,js_queue_get /* getProperty */
,JS_StrictPropertyStub /* setProperty */
,js_queue_enumerate /* enumerate */
,js_queue_resolve /* resolve */
,JS_ConvertStub /* convert */
,js_finalize_queue /* finalize */
};
/* Queue Constructor (creates queue) */
static JSBool
js_queue_constructor(JSContext *cx, uintN argc, jsval *arglist)
jsval *argv=JS_ARGV(cx, arglist);
uintN argn=0;
char* name=NULL;
msg_queue_t* q=NULL;
list_node_t* n;
obj=JS_NewObject(cx, &js_queue_class, NULL, NULL);
JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(obj));
#if 0 /* This doesn't appear to be doing anything but leaking memory */
if((q=(msg_queue_t*)malloc(sizeof(msg_queue_t)))==NULL) {
JS_ReportError(cx,"malloc failed");
return(JS_FALSE);
}
memset(q,0,sizeof(msg_queue_t));
if(argn<argc && JSVAL_IS_STRING(argv[argn])) {
JSVALUE_TO_ASTRING(cx, argv[argn], name, sizeof(q->name), NULL);
argn++;
}
if(argn<argc && JSVAL_IS_NUMBER(argv[argn])) {
if(!JS_ValueToInt32(cx,argv[argn++],&flags))
return JS_FALSE;
}
rc=JS_SUSPENDREQUEST(cx);
listLock(&named_queues);
for(n=named_queues.first;n!=NULL;n=n->next)
if((q=n->data)!=NULL && !stricmp(q->name,name))
break;
if(n==NULL)
q=NULL;
}
if(q==NULL) {
q=msgQueueInit(NULL,flags);
if(name!=NULL)
SAFECOPY(q->name,name);
listPushNode(&named_queues,q);
} else
msgQueueAttach(q);
JS_RESUMEREQUEST(cx, rc);
if(!JS_SetPrivate(cx, obj, q)) {
JS_ReportError(cx,"JS_SetPrivate failed");
return(JS_FALSE);
}
#ifdef BUILD_JSDOCS
js_DescribeSyncObject(cx,obj,"Class for bi-directional message queues. "
"Used for inter-thread/module communications.", 312);
js_DescribeSyncConstructor(cx,obj,"To create a new (named) Queue object: "
"<tt>var q = new Queue(<i>name</i>)</tt>");
js_CreateArrayOfStrings(cx, obj, "_property_desc_list", queue_prop_desc, JSPROP_READONLY);
#endif
return(JS_TRUE);
}
JSObject* js_CreateQueueClass(JSContext* cx, JSObject* parent)
{
JSObject* obj;
obj = JS_InitClass(cx, parent, NULL
,&js_queue_class
,js_queue_constructor
,0 /* number of constructor args */
,NULL /* props, specified in constructor */
,NULL /* funcs, specified in constructor */
,NULL
,NULL);
return(obj);
}
JSObject* js_CreateQueueObject(JSContext* cx, JSObject* parent, char *name, msg_queue_t* q)
{
JSObject* obj;
if(name==NULL)
obj = JS_NewObject(cx, &js_queue_class, NULL, parent);
else
obj = JS_DefineObject(cx, parent, name, &js_queue_class, NULL
,JSPROP_ENUMERATE|JSPROP_READONLY);
if(obj==NULL)
return(NULL);
if(!JS_SetPrivate(cx, obj, q))
return(NULL);
return(obj);
}