Skip to content
Snippets Groups Projects
js_queue.c 12.5 KiB
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"

/* 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)
	JS_ReadStructuredClone(cx, v->value, v->size, JS_STRUCTURED_CLONE_VERSION, rval, NULL, NULL);

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;
deuce's avatar
deuce committed
	jsrefcount	rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
	if(argc && JSVAL_IS_NUMBER(argv[0])) { 	/* timeout specified */
		if(!JS_ValueToInt32(cx,argv[0],&timeout))
			return JS_FALSE;
	}
deuce's avatar
deuce committed
	v=msgQueuePeek(q,timeout);
deuce's avatar
deuce committed
	if(v==NULL)
		JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
	else if(v->name[0])
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx,v->name)));
		JS_SET_RVAL(cx, arglist, JSVAL_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;
deuce's avatar
deuce committed
	jsrefcount	rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
	if(argc && JSVAL_IS_STRING(argv[0])) {	/* value named specified */
deuce's avatar
deuce committed
		JSVALUE_TO_STRBUF(cx, argv[0], find_v.name, sizeof(find_v.name), NULL);
		v=msgQueueFind(q,&find_v,sizeof(find_v.name));
		if(argc && JSVAL_IS_NUMBER(argv[0])) {
			if(!JS_ValueToInt32(cx,argv[0],&timeout))
				return JS_FALSE;
		}
		js_decode_value(cx, obj, v, &rval);
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;
deuce's avatar
deuce committed
	jsrefcount	rc;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((q=(msg_queue_t*)js_GetClassPrivate(cx, obj, &js_queue_class))==NULL) {
	if(argc && JSVAL_IS_NUMBER(argv[0])) { 	/* timeout specified */
		if(!JS_ValueToInt32(cx,argv[0],&timeout))
			return JS_FALSE;
	}
deuce's avatar
deuce committed
	v=msgQueuePeek(q, timeout);
deuce's avatar
deuce committed
	if(v!=NULL) {
		js_decode_value(cx, obj, v, &rval);
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)
	memset(v,0,sizeof(queued_value_t));
	if(!JS_WriteStructuredClone(cx, val, &serialized, &v->size, NULL, NULL)) {
		free(v);
	if((v->value=(uint64 *)malloc(v->size))==NULL) {
		JS_free(cx, serialized);
		free(v);
	memcpy(v->value, serialized, v->size);
	JS_free(cx, serialized);
bool js_enqueue_value(JSContext *cx, msg_queue_t* q, jsval val, char* name)
deuce's avatar
deuce committed
	jsrefcount		rc;
	if((v=js_encode_value(cx,val,name))==NULL)
	result=msgQueueWrite(q,v,sizeof(queued_value_t));
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) {
deuce's avatar
deuce committed
	if(argn < argc) {
		JSVALUE_TO_MSTRING(cx, argv[argn], name, NULL);
		argn++;
		HANDLE_PENDING(cx, name);
deuce's avatar
deuce committed
	}
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(js_enqueue_value(cx, q, val, name)));
deuce's avatar
deuce committed
	if(name)
		free(name);

	return(JS_TRUE);
}

/* Queue Object Properites */
enum {
	 QUEUE_PROP_NAME
	,QUEUE_PROP_DATA_WAITING
	,QUEUE_PROP_READ_LEVEL
	,QUEUE_PROP_WRITE_LEVEL
static const char* queue_prop_desc[] = {
Rob Swindell's avatar
Rob Swindell committed
	 "Name of the queue (if it has one)"
	,"<tt>true</tt> if data is waiting to be read from queue"
Rob Swindell's avatar
Rob Swindell committed
	,"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"
static JSBool js_queue_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
    jsint			tiny;
	msg_queue_t*	q;
deuce's avatar
deuce committed
	jsrefcount		rc;
	if((q=(msg_queue_t*)JS_GetPrivate(cx,obj))==NULL)
		return JS_FALSE;
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
			if(q->name[0])
				*vp = STRING_TO_JSVAL(JS_NewStringCopyZ(cx,q->name));
			break;
		case QUEUE_PROP_DATA_WAITING:
			*vp = BOOLEAN_TO_JSVAL(INT_TO_BOOL(msgQueueReadLevel(q)));
		case QUEUE_PROP_READ_LEVEL:
			*vp = INT_TO_JSVAL(msgQueueReadLevel(q));
			break;
		case QUEUE_PROP_WRITE_LEVEL:
			*vp = INT_TO_JSVAL(msgQueueWriteLevel(q));
		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]"
Rob Swindell's avatar
Rob Swindell committed
	,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")
	{"read",		js_read,		1,	JSTYPE_UNDEF,	"[<i>string</i> name] or [<i>number</i>timeout=0]"
Rob Swindell's avatar
Rob Swindell committed
	,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]"
Rob Swindell's avatar
Rob Swindell committed
	,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>)")
rswindell's avatar
rswindell committed
	{"write",		js_write,		1,	JSTYPE_BOOLEAN,	"value [,name=<i>none</i>]"
Rob Swindell's avatar
Rob Swindell committed
	,JSDOCSTR("Write a value (optionally named) to the queue")
static JSBool js_queue_resolve(JSContext *cx, JSObject *obj, jsid id)
{
	char*			name=NULL;
deuce's avatar
deuce committed
	JSBool			ret;
deuce's avatar
deuce committed
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
		
		JS_IdToValue(cx, id, &idval);
deuce's avatar
deuce committed
		if(JSVAL_IS_STRING(idval)) {
			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
			HANDLE_PENDING(cx, name);
deuce's avatar
deuce committed
		}
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	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)
{
deuce's avatar
deuce committed
	return(js_queue_resolve(cx, obj, JSID_VOID));
     "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)
	JSObject *obj;
	jsval *argv=JS_ARGV(cx, arglist);
	uintN			argn=0;
	char*			name=NULL;
rswindell's avatar
rswindell committed
	int32			flags=MSG_QUEUE_BIDIR;
	msg_queue_t*	q=NULL;
	list_node_t*	n;
deuce's avatar
deuce committed
	jsrefcount		rc;
	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;
	}
		listLock(&named_queues);
		for(n=named_queues.first;n!=NULL;n=n->next)
			if((q=n->data)!=NULL && !stricmp(q->name,name))
				break;
		listUnlock(&named_queues);
		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);

	if(!JS_SetPrivate(cx, obj, q)) {
		JS_ReportError(cx,"JS_SetPrivate failed");
		return(JS_FALSE);
	}

	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);
}