1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
/*
* 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;
}
|