diff options
author | Steve Bennett <steveb@workware.net.au> | 2020-11-14 14:43:41 +1000 |
---|---|---|
committer | Steve Bennett <steveb@workware.net.au> | 2020-11-15 20:05:52 +1000 |
commit | be4c0e8732445172d52dfb38b56c8aa997762309 (patch) | |
tree | 3af6160b81a898427a0a96b45829176361ec3502 /jim-redis.c | |
parent | c436661c19ac193007af0d2d878f554bad08f905 (diff) | |
download | jimtcl-be4c0e8732445172d52dfb38b56c8aa997762309.zip jimtcl-be4c0e8732445172d52dfb38b56c8aa997762309.tar.gz jimtcl-be4c0e8732445172d52dfb38b56c8aa997762309.tar.bz2 |
redis: Add simple redis client extension
Using the hiredis client library.
Synchronous API only.
Signed-off-by: Steve Bennett <steveb@workware.net.au>
Diffstat (limited to 'jim-redis.c')
-rw-r--r-- | jim-redis.c | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/jim-redis.c b/jim-redis.c new file mode 100644 index 0000000..d1e9457 --- /dev/null +++ b/jim-redis.c @@ -0,0 +1,168 @@ +/* + * 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 { + args = Jim_Alloc(sizeof(*args) * argc - 1); + arglens = Jim_Alloc(sizeof(*arglens) * argc - 1); + for (i = 1; i < argc; i++) { + args[i - 1] = Jim_String(argv[i]); + arglens[i - 1] = Jim_Length(argv[i]); + } + reply = redisCommandArgv(c, argc - 1, 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) { + /*Jim_SetResultFormatted(interp, "%#s %#s: %#s", argv[0], argv[1], Jim_GetResult(interp));*/ + ret = JIM_ERR; + } + freeReplyObject(reply); + } + 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) +{ + if (Jim_PackageProvide(interp, "redis", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + Jim_CreateCommand(interp, "redis", jim_redis_cmd, NULL, NULL); + return JIM_OK; +} |