171 lines
4.9 KiB
C
171 lines
4.9 KiB
C
/*
|
|
* Simple redis interface
|
|
*
|
|
* (c) 2020 Steve Bennett <steveb@workware.net.au>
|
|
*
|
|
* See LICENSE for license details.
|
|
*/
|
|
#include <jim.h>
|
|
#include <jim-eventloop.h>
|
|
#include <unistd.h>
|
|
#include <hiredis.h>
|
|
|
|
/**
|
|
* Recursively decode a redis reply as Tcl data structure.
|
|
*/
|
|
static Jim_Obj *jim_redis_get_result(Jim_Interp *interp, redisReply *reply)
|
|
{
|
|
int i;
|
|
switch (reply->type) {
|
|
case REDIS_REPLY_INTEGER:
|
|
return Jim_NewIntObj(interp, reply->integer);
|
|
case REDIS_REPLY_STATUS:
|
|
case REDIS_REPLY_ERROR:
|
|
case REDIS_REPLY_STRING:
|
|
return Jim_NewStringObj(interp, reply->str, reply->len);
|
|
break;
|
|
case REDIS_REPLY_ARRAY:
|
|
{
|
|
Jim_Obj *obj = Jim_NewListObj(interp, NULL, 0);
|
|
for (i = 0; i < reply->elements; i++) {
|
|
Jim_ListAppendElement(interp, obj, jim_redis_get_result(interp, reply->element[i]));
|
|
}
|
|
return obj;
|
|
}
|
|
case REDIS_REPLY_NIL:
|
|
return Jim_NewStringObj(interp, NULL, 0);
|
|
default:
|
|
return Jim_NewStringObj(interp, "badtype", -1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* $r readable ?script?
|
|
* - set or clear a readable script
|
|
* $r close
|
|
* - close (delete) the handle
|
|
* $r read
|
|
* - synchronously read a SUBSCRIBE response (typically from within readable)
|
|
* $r <redis-command> ...
|
|
* - invoke the redis command and return the decoded result
|
|
*/
|
|
static int jim_redis_subcmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
int i;
|
|
redisContext *c = Jim_CmdPrivData(interp);
|
|
const char **args;
|
|
size_t *arglens;
|
|
int ret = JIM_OK;
|
|
|
|
redisReply *reply;
|
|
|
|
if (argc < 2) {
|
|
Jim_WrongNumArgs(interp, 1, argv, "cmd ?args ...?");
|
|
return JIM_ERR;
|
|
}
|
|
|
|
if (Jim_CompareStringImmediate(interp, argv[1], "readable")) {
|
|
/* Remove any existing handler */
|
|
Jim_DeleteFileHandler(interp, c->fd, JIM_EVENT_READABLE);
|
|
if (argc > 2) {
|
|
Jim_CreateScriptFileHandler(interp, c->fd, JIM_EVENT_READABLE, argv[2]);
|
|
}
|
|
return JIM_OK;
|
|
}
|
|
if (Jim_CompareStringImmediate(interp, argv[1], "close")) {
|
|
return Jim_DeleteCommand(interp, argv[0]);
|
|
}
|
|
if (Jim_CompareStringImmediate(interp, argv[1], "read")) {
|
|
if (redisGetReply(c, (void **)&reply) != REDIS_OK) {
|
|
reply = NULL;
|
|
}
|
|
}
|
|
else {
|
|
int nargs = argc - 1;
|
|
args = Jim_Alloc(sizeof(*args) * nargs);
|
|
arglens = Jim_Alloc(sizeof(*arglens) * nargs);
|
|
for (i = 0; i < nargs; i++) {
|
|
args[i] = Jim_String(argv[i + 1]);
|
|
arglens[i] = Jim_Length(argv[i + 1]);
|
|
}
|
|
reply = redisCommandArgv(c, nargs, args, arglens);
|
|
Jim_Free(args);
|
|
Jim_Free(arglens);
|
|
}
|
|
/* sometimes commands return NULL */
|
|
if (reply) {
|
|
Jim_SetResult(interp, jim_redis_get_result(interp, reply));
|
|
if (reply->type == REDIS_REPLY_ERROR) {
|
|
ret = JIM_ERR;
|
|
}
|
|
freeReplyObject(reply);
|
|
}
|
|
else if (c->err) {
|
|
Jim_SetResultFormatted(interp, "%#s: %s", argv[1], c->errstr);
|
|
ret = JIM_ERR;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void jim_redis_del_proc(Jim_Interp *interp, void *privData)
|
|
{
|
|
redisContext *c = privData;
|
|
JIM_NOTUSED(interp);
|
|
Jim_DeleteFileHandler(interp, c->fd, JIM_EVENT_READABLE);
|
|
redisFree(c);
|
|
}
|
|
|
|
/**
|
|
* redis <socket-stream>
|
|
*
|
|
* Returns a handle that can be used to communicate with the redis
|
|
* instance over the socket.
|
|
* The original socket handle is closed.
|
|
*/
|
|
static int jim_redis_cmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
|
|
{
|
|
redisContext *c;
|
|
char buf[60];
|
|
Jim_Obj *objv[2];
|
|
long fd;
|
|
int ret;
|
|
|
|
if (argc != 2) {
|
|
Jim_WrongNumArgs(interp, 1, argv, "socket-stream");
|
|
return JIM_ERR;
|
|
}
|
|
|
|
/* Invoke getfd to get the file descriptor */
|
|
objv[0] = argv[1];
|
|
objv[1] = Jim_NewStringObj(interp, "getfd", -1);
|
|
ret = Jim_EvalObjVector(interp, 2, objv);
|
|
if (ret == JIM_OK) {
|
|
ret = Jim_GetLong(interp, Jim_GetResult(interp), &fd) == JIM_ERR;
|
|
}
|
|
if (ret != JIM_OK) {
|
|
Jim_SetResultFormatted(interp, "%#s: not a valid stream handle: %#s", argv[0], argv[1]);
|
|
return ret;
|
|
}
|
|
|
|
/* Note that we dup the file descriptor here so that we can close the original */
|
|
fd = dup(fd);
|
|
/* Can't fail */
|
|
c = redisConnectFd(fd);
|
|
/* Now delete the original stream */
|
|
Jim_DeleteCommand(interp, argv[1]);
|
|
snprintf(buf, sizeof(buf), "redis.handle%ld", Jim_GetId(interp));
|
|
Jim_CreateCommand(interp, buf, jim_redis_subcmd, c, jim_redis_del_proc);
|
|
|
|
Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
|
|
|
|
return JIM_OK;
|
|
}
|
|
|
|
int
|
|
Jim_redisInit(Jim_Interp *interp)
|
|
{
|
|
Jim_PackageProvideCheck(interp, "redis");
|
|
Jim_CreateCommand(interp, "redis", jim_redis_cmd, NULL, NULL);
|
|
return JIM_OK;
|
|
}
|