From d405d31edf0d1cf4b5ac89d4b542fdf04f045807 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 20 Jan 2014 14:42:50 -0800 Subject: [PATCH 01/17] add data-server to hifi repo, abstract DataServerClient --- CMakeLists.txt | 1 + data-server/CMakeLists.txt | 23 + data-server/external/hiredis/async.c | 648 ++++++++++++ data-server/external/hiredis/async.h | 125 +++ data-server/external/hiredis/dict.c | 338 ++++++ data-server/external/hiredis/dict.h | 126 +++ data-server/external/hiredis/fmacros.h | 20 + data-server/external/hiredis/hiredis.c | 1322 ++++++++++++++++++++++++ data-server/external/hiredis/hiredis.h | 213 ++++ data-server/external/hiredis/net.c | 339 ++++++ data-server/external/hiredis/net.h | 48 + data-server/external/hiredis/sds.c | 606 +++++++++++ data-server/external/hiredis/sds.h | 88 ++ data-server/src/DataServer.cpp | 194 ++++ data-server/src/DataServer.h | 29 + data-server/src/main.cpp | 24 + interface/src/Application.cpp | 16 +- interface/src/Application.h | 2 +- interface/src/DataServerClient.cpp | 290 +++--- interface/src/DataServerClient.h | 31 +- interface/src/Menu.cpp | 2 +- interface/src/avatar/Profile.cpp | 29 +- interface/src/avatar/Profile.h | 9 +- 23 files changed, 4355 insertions(+), 168 deletions(-) create mode 100755 data-server/CMakeLists.txt create mode 100755 data-server/external/hiredis/async.c create mode 100755 data-server/external/hiredis/async.h create mode 100755 data-server/external/hiredis/dict.c create mode 100755 data-server/external/hiredis/dict.h create mode 100755 data-server/external/hiredis/fmacros.h create mode 100755 data-server/external/hiredis/hiredis.c create mode 100755 data-server/external/hiredis/hiredis.h create mode 100755 data-server/external/hiredis/net.c create mode 100755 data-server/external/hiredis/net.h create mode 100755 data-server/external/hiredis/sds.c create mode 100755 data-server/external/hiredis/sds.h create mode 100644 data-server/src/DataServer.cpp create mode 100644 data-server/src/DataServer.h create mode 100755 data-server/src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e01986b355..e0a03036ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ endif (NOT WIN32) # targets on all platforms add_subdirectory(assignment-client) +add_subdirectory(data-server) add_subdirectory(domain-server) add_subdirectory(interface) add_subdirectory(pairing-server) diff --git a/data-server/CMakeLists.txt b/data-server/CMakeLists.txt new file mode 100755 index 0000000000..65e3e4a489 --- /dev/null +++ b/data-server/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 2.8) + +set(ROOT_DIR ..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +set(TARGET_NAME data-server) + +find_package(Qt5Network REQUIRED) + +include(${MACRO_DIR}/SetupHifiProject.cmake) +setup_hifi_project(${TARGET_NAME} TRUE) + +qt5_use_modules(${TARGET_NAME} Network) + +# link the shared hifi library +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) + +# add hiredis as a library +FILE(GLOB HIREDIS_SRCS external/hiredis/*.c) +add_library(hiredis ${HIREDIS_SRCS}) +include_directories(external/hiredis/) +target_link_libraries(${TARGET_NAME} hiredis) \ No newline at end of file diff --git a/data-server/external/hiredis/async.c b/data-server/external/hiredis/async.c new file mode 100755 index 0000000000..65327120fe --- /dev/null +++ b/data-server/external/hiredis/async.c @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include +#include +#include +#include +#include +#include +#include "async.h" +#include "net.h" +#include "dict.c" +#include "sds.h" + +#define _EL_ADD_READ(ctx) do { \ + if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_READ(ctx) do { \ + if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ + } while(0) +#define _EL_ADD_WRITE(ctx) do { \ + if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_WRITE(ctx) do { \ + if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ + } while(0) +#define _EL_CLEANUP(ctx) do { \ + if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ + } while(0); + +/* Forward declaration of function in hiredis.c */ +void __redisAppendCommand(redisContext *c, char *cmd, size_t len); + +/* Functions managing dictionary of callbacks for pub/sub. */ +static unsigned int callbackHash(const void *key) { + return dictGenHashFunction((const unsigned char *)key, + sdslen((const sds)key)); +} + +static void *callbackValDup(void *privdata, const void *src) { + ((void) privdata); + redisCallback *dup = malloc(sizeof(*dup)); + memcpy(dup,src,sizeof(*dup)); + return dup; +} + +static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { + int l1, l2; + ((void) privdata); + + l1 = sdslen((const sds)key1); + l2 = sdslen((const sds)key2); + if (l1 != l2) return 0; + return memcmp(key1,key2,l1) == 0; +} + +static void callbackKeyDestructor(void *privdata, void *key) { + ((void) privdata); + sdsfree((sds)key); +} + +static void callbackValDestructor(void *privdata, void *val) { + ((void) privdata); + free(val); +} + +static dictType callbackDict = { + callbackHash, + NULL, + callbackValDup, + callbackKeyCompare, + callbackKeyDestructor, + callbackValDestructor +}; + +static redisAsyncContext *redisAsyncInitialize(redisContext *c) { + redisAsyncContext *ac; + + ac = realloc(c,sizeof(redisAsyncContext)); + if (ac == NULL) + return NULL; + + c = &(ac->c); + + /* The regular connect functions will always set the flag REDIS_CONNECTED. + * For the async API, we want to wait until the first write event is + * received up before setting this flag, so reset it here. */ + c->flags &= ~REDIS_CONNECTED; + + ac->err = 0; + ac->errstr = NULL; + ac->data = NULL; + + ac->ev.data = NULL; + ac->ev.addRead = NULL; + ac->ev.delRead = NULL; + ac->ev.addWrite = NULL; + ac->ev.delWrite = NULL; + ac->ev.cleanup = NULL; + + ac->onConnect = NULL; + ac->onDisconnect = NULL; + + ac->replies.head = NULL; + ac->replies.tail = NULL; + ac->sub.invalid.head = NULL; + ac->sub.invalid.tail = NULL; + ac->sub.channels = dictCreate(&callbackDict,NULL); + ac->sub.patterns = dictCreate(&callbackDict,NULL); + return ac; +} + +/* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ +static void __redisAsyncCopyError(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + ac->err = c->err; + ac->errstr = c->errstr; +} + +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectNonBlock(ip,port); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectUnix(const char *path) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectUnixNonBlock(path); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { + if (ac->onConnect == NULL) { + ac->onConnect = fn; + + /* The common way to detect an established connection is to wait for + * the first write event to be fired. This assumes the related event + * library functions are already set. */ + _EL_ADD_WRITE(ac); + return REDIS_OK; + } + return REDIS_ERR; +} + +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { + if (ac->onDisconnect == NULL) { + ac->onDisconnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +/* Helper functions to push/shift callbacks */ +static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { + redisCallback *cb; + + /* Copy callback from stack to heap */ + cb = malloc(sizeof(*cb)); + if (cb == NULL) + return REDIS_ERR_OOM; + + if (source != NULL) { + memcpy(cb,source,sizeof(*cb)); + cb->next = NULL; + } + + /* Store callback in list */ + if (list->head == NULL) + list->head = cb; + if (list->tail != NULL) + list->tail->next = cb; + list->tail = cb; + return REDIS_OK; +} + +static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { + redisCallback *cb = list->head; + if (cb != NULL) { + list->head = cb->next; + if (cb == list->tail) + list->tail = NULL; + + /* Copy callback from heap to stack */ + if (target != NULL) + memcpy(target,cb,sizeof(*cb)); + free(cb); + return REDIS_OK; + } + return REDIS_ERR; +} + +static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { + redisContext *c = &(ac->c); + if (cb->fn != NULL) { + c->flags |= REDIS_IN_CALLBACK; + cb->fn(ac,reply,cb->privdata); + c->flags &= ~REDIS_IN_CALLBACK; + } +} + +/* Helper function to free the context. */ +static void __redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + dictIterator *it; + dictEntry *de; + + /* Execute pending callbacks with NULL reply. */ + while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Execute callbacks for invalid commands */ + while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Run subscription callbacks callbacks with NULL reply */ + it = dictGetIterator(ac->sub.channels); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.channels); + + it = dictGetIterator(ac->sub.patterns); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.patterns); + + /* Signal event lib to clean up */ + _EL_CLEANUP(ac); + + /* Execute disconnect callback. When redisAsyncFree() initiated destroying + * this context, the status will always be REDIS_OK. */ + if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { + if (c->flags & REDIS_FREEING) { + ac->onDisconnect(ac,REDIS_OK); + } else { + ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); + } + } + + /* Cleanup self */ + redisFree(c); +} + +/* Free the async context. When this function is called from a callback, + * control needs to be returned to redisProcessCallbacks() before actual + * free'ing. To do so, a flag is set on the context which is picked up by + * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ +void redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_FREEING; + if (!(c->flags & REDIS_IN_CALLBACK)) + __redisAsyncFree(ac); +} + +/* Helper function to make the disconnect happen and clean up. */ +static void __redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + /* Make sure error is accessible if there is any */ + __redisAsyncCopyError(ac); + + if (ac->err == 0) { + /* For clean disconnects, there should be no pending callbacks. */ + assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); + } else { + /* Disconnection is caused by an error, make sure that pending + * callbacks cannot call new commands. */ + c->flags |= REDIS_DISCONNECTING; + } + + /* For non-clean disconnects, __redisAsyncFree() will execute pending + * callbacks with a NULL-reply. */ + __redisAsyncFree(ac); +} + +/* Tries to do a clean disconnect from Redis, meaning it stops new commands + * from being issued, but tries to flush the output buffer and execute + * callbacks for all remaining replies. When this function is called from a + * callback, there might be more replies and we can safely defer disconnecting + * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately + * when there are no pending callbacks. */ +void redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_DISCONNECTING; + if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) + __redisAsyncDisconnect(ac); +} + +static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { + redisContext *c = &(ac->c); + dict *callbacks; + dictEntry *de; + int pvariant; + char *stype; + sds sname; + + /* Custom reply functions are not supported for pub/sub. This will fail + * very hard when they are used... */ + if (reply->type == REDIS_REPLY_ARRAY) { + assert(reply->elements >= 2); + assert(reply->element[0]->type == REDIS_REPLY_STRING); + stype = reply->element[0]->str; + pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; + + if (pvariant) + callbacks = ac->sub.patterns; + else + callbacks = ac->sub.channels; + + /* Locate the right callback */ + assert(reply->element[1]->type == REDIS_REPLY_STRING); + sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); + de = dictFind(callbacks,sname); + if (de != NULL) { + memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); + + /* If this is an unsubscribe message, remove it. */ + if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { + dictDelete(callbacks,sname); + + /* If this was the last unsubscribe message, revert to + * non-subscribe mode. */ + assert(reply->element[2]->type == REDIS_REPLY_INTEGER); + if (reply->element[2]->integer == 0) + c->flags &= ~REDIS_SUBSCRIBED; + } + } + sdsfree(sname); + } else { + /* Shift callback for invalid commands. */ + __redisShiftCallback(&ac->sub.invalid,dstcb); + } + return REDIS_OK; +} + +void redisProcessCallbacks(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + void *reply = NULL; + int status; + + while((status = redisGetReply(c,&reply)) == REDIS_OK) { + if (reply == NULL) { + /* When the connection is being disconnected and there are + * no more replies, this is the cue to really disconnect. */ + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { + __redisAsyncDisconnect(ac); + return; + } + + /* If monitor mode, repush callback */ + if(c->flags & REDIS_MONITORING) { + __redisPushCallback(&ac->replies,&cb); + } + + /* When the connection is not being disconnected, simply stop + * trying to get replies and wait for the next loop tick. */ + break; + } + + /* Even if the context is subscribed, pending regular callbacks will + * get a reply before pub/sub messages arrive. */ + if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { + /* + * A spontaneous reply in a not-subscribed context can be the error + * reply that is sent when a new connection exceeds the maximum + * number of allowed connections on the server side. + * + * This is seen as an error instead of a regular reply because the + * server closes the connection after sending it. + * + * To prevent the error from being overwritten by an EOF error the + * connection is closed here. See issue #43. + * + * Another possibility is that the server is loading its dataset. + * In this case we also want to close the connection, and have the + * user wait until the server is ready to take our request. + */ + if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { + c->err = REDIS_ERR_OTHER; + snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); + __redisAsyncDisconnect(ac); + return; + } + /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ + assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); + if(c->flags & REDIS_SUBSCRIBED) + __redisGetSubscribeCallback(ac,reply,&cb); + } + + if (cb.fn != NULL) { + __redisRunCallback(ac,&cb,reply); + c->reader->fn->freeObject(reply); + + /* Proceed with free'ing when redisAsyncFree() was called. */ + if (c->flags & REDIS_FREEING) { + __redisAsyncFree(ac); + return; + } + } else { + /* No callback for this reply. This can either be a NULL callback, + * or there were no callbacks to begin with. Either way, don't + * abort with an error, but simply ignore it because the client + * doesn't know what the server will spit out over the wire. */ + c->reader->fn->freeObject(reply); + } + } + + /* Disconnect when there was an error reading the reply */ + if (status != REDIS_OK) + __redisAsyncDisconnect(ac); +} + +/* Internal helper function to detect socket status the first time a read or + * write event fires. When connecting was not succesful, the connect callback + * is called with a REDIS_ERR status and the context is free'd. */ +static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (redisCheckSocketError(c,c->fd) == REDIS_ERR) { + /* Try again later when connect(2) is still in progress. */ + if (errno == EINPROGRESS) + return REDIS_OK; + + if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); + __redisAsyncDisconnect(ac); + return REDIS_ERR; + } + + /* Mark context as connected. */ + c->flags |= REDIS_CONNECTED; + if (ac->onConnect) ac->onConnect(ac,REDIS_OK); + return REDIS_OK; +} + +/* This function should be called when the socket is readable. + * It processes all replies that can be read and executes their callbacks. + */ +void redisAsyncHandleRead(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferRead(c) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Always re-schedule reads */ + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +void redisAsyncHandleWrite(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + int done = 0; + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferWrite(c,&done) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Continue writing when not done, stop writing otherwise */ + if (!done) + _EL_ADD_WRITE(ac); + else + _EL_DEL_WRITE(ac); + + /* Always schedule reads after writes */ + _EL_ADD_READ(ac); + } +} + +/* Sets a pointer to the first argument and its length starting at p. Returns + * the number of bytes to skip to get to the following argument. */ +static char *nextArgument(char *start, char **str, size_t *len) { + char *p = start; + if (p[0] != '$') { + p = strchr(p,'$'); + if (p == NULL) return NULL; + } + + *len = (int)strtol(p+1,NULL,10); + p = strchr(p,'\r'); + assert(p); + *str = p+2; + return p+2+(*len)+2; +} + +/* Helper function for the redisAsyncCommand* family of functions. Writes a + * formatted command to the output buffer and registers the provided callback + * function with the context. */ +static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) { + redisContext *c = &(ac->c); + redisCallback cb; + int pvariant, hasnext; + char *cstr, *astr; + size_t clen, alen; + char *p; + sds sname; + + /* Don't accept new commands when the connection is about to be closed. */ + if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; + + /* Setup callback */ + cb.fn = fn; + cb.privdata = privdata; + + /* Find out which command will be appended. */ + p = nextArgument(cmd,&cstr,&clen); + assert(p != NULL); + hasnext = (p[0] == '$'); + pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; + cstr += pvariant; + clen -= pvariant; + + if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { + c->flags |= REDIS_SUBSCRIBED; + + /* Add every channel/pattern to the list of subscription callbacks. */ + while ((p = nextArgument(p,&astr,&alen)) != NULL) { + sname = sdsnewlen(astr,alen); + if (pvariant) + dictReplace(ac->sub.patterns,sname,&cb); + else + dictReplace(ac->sub.channels,sname,&cb); + } + } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { + /* It is only useful to call (P)UNSUBSCRIBE when the context is + * subscribed to one or more channels or patterns. */ + if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; + + /* (P)UNSUBSCRIBE does not have its own response: every channel or + * pattern that is unsubscribed will receive a message. This means we + * should not append a callback function for this command. */ + } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { + /* Set monitor flag and push callback */ + c->flags |= REDIS_MONITORING; + __redisPushCallback(&ac->replies,&cb); + } else { + if (c->flags & REDIS_SUBSCRIBED) + /* This will likely result in an error reply, but it needs to be + * received and passed to the callback. */ + __redisPushCallback(&ac->sub.invalid,&cb); + else + __redisPushCallback(&ac->replies,&cb); + } + + __redisAppendCommand(c,cmd,len); + + /* Always schedule a write when the write buffer is non-empty */ + _EL_ADD_WRITE(ac); + + return REDIS_OK; +} + +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { + char *cmd; + int len; + int status; + len = redisvFormatCommand(&cmd,format,ap); + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + free(cmd); + return status; +} + +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { + va_list ap; + int status; + va_start(ap,format); + status = redisvAsyncCommand(ac,fn,privdata,format,ap); + va_end(ap); + return status; +} + +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { + char *cmd; + int len; + int status; + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + free(cmd); + return status; +} diff --git a/data-server/external/hiredis/async.h b/data-server/external/hiredis/async.h new file mode 100755 index 0000000000..268274e8e7 --- /dev/null +++ b/data-server/external/hiredis/async.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HIREDIS_ASYNC_H +#define __HIREDIS_ASYNC_H +#include "hiredis.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ +struct dict; /* dictionary header is included in async.c */ + +/* Reply callback prototype and container */ +typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); +typedef struct redisCallback { + struct redisCallback *next; /* simple singly linked list */ + redisCallbackFn *fn; + void *privdata; +} redisCallback; + +/* List of callbacks for either regular replies or pub/sub */ +typedef struct redisCallbackList { + redisCallback *head, *tail; +} redisCallbackList; + +/* Connection callback prototypes */ +typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); +typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); + +/* Context for an async connection to Redis */ +typedef struct redisAsyncContext { + /* Hold the regular context, so it can be realloc'ed. */ + redisContext c; + + /* Setup error flags so they can be used directly. */ + int err; + char *errstr; + + /* Not used by hiredis */ + void *data; + + /* Event library data and hooks */ + struct { + void *data; + + /* Hooks that are called when the library expects to start + * reading/writing. These functions should be idempotent. */ + void (*addRead)(void *privdata); + void (*delRead)(void *privdata); + void (*addWrite)(void *privdata); + void (*delWrite)(void *privdata); + void (*cleanup)(void *privdata); + } ev; + + /* Called when either the connection is terminated due to an error or per + * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ + redisDisconnectCallback *onDisconnect; + + /* Called when the first write event was received. */ + redisConnectCallback *onConnect; + + /* Regular command callbacks */ + redisCallbackList replies; + + /* Subscription callbacks */ + struct { + redisCallbackList invalid; + struct dict *channels; + struct dict *patterns; + } sub; +} redisAsyncContext; + +/* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnect(const char *ip, int port); +redisAsyncContext *redisAsyncConnectUnix(const char *path); +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +void redisAsyncDisconnect(redisAsyncContext *ac); +void redisAsyncFree(redisAsyncContext *ac); + +/* Handle read/write events */ +void redisAsyncHandleRead(redisAsyncContext *ac); +void redisAsyncHandleWrite(redisAsyncContext *ac); + +/* Command functions for an async context. Write the command to the + * output buffer and register the provided callback. */ +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/data-server/external/hiredis/dict.c b/data-server/external/hiredis/dict.c new file mode 100755 index 0000000000..79b1041cad --- /dev/null +++ b/data-server/external/hiredis/dict.c @@ -0,0 +1,338 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include +#include +#include +#include "dict.h" + +/* -------------------------- private prototypes ---------------------------- */ + +static int _dictExpandIfNeeded(dict *ht); +static unsigned long _dictNextPower(unsigned long size); +static int _dictKeyIndex(dict *ht, const void *key); +static int _dictInit(dict *ht, dictType *type, void *privDataPtr); + +/* -------------------------- hash functions -------------------------------- */ + +/* Generic hash function (a popular one from Bernstein). + * I tested a few and this was the best. */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { + unsigned int hash = 5381; + + while (len--) + hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ + return hash; +} + +/* ----------------------------- API implementation ------------------------- */ + +/* Reset an hashtable already initialized with ht_init(). + * NOTE: This function should only called by ht_destroy(). */ +static void _dictReset(dict *ht) { + ht->table = NULL; + ht->size = 0; + ht->sizemask = 0; + ht->used = 0; +} + +/* Create a new hash table */ +static dict *dictCreate(dictType *type, void *privDataPtr) { + dict *ht = malloc(sizeof(*ht)); + _dictInit(ht,type,privDataPtr); + return ht; +} + +/* Initialize the hash table */ +static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { + _dictReset(ht); + ht->type = type; + ht->privdata = privDataPtr; + return DICT_OK; +} + +/* Expand or create the hashtable */ +static int dictExpand(dict *ht, unsigned long size) { + dict n; /* the new hashtable */ + unsigned long realsize = _dictNextPower(size), i; + + /* the size is invalid if it is smaller than the number of + * elements already inside the hashtable */ + if (ht->used > size) + return DICT_ERR; + + _dictInit(&n, ht->type, ht->privdata); + n.size = realsize; + n.sizemask = realsize-1; + n.table = calloc(realsize,sizeof(dictEntry*)); + + /* Copy all the elements from the old to the new table: + * note that if the old hash table is empty ht->size is zero, + * so dictExpand just creates an hash table. */ + n.used = ht->used; + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if (ht->table[i] == NULL) continue; + + /* For each hash entry on this slot... */ + he = ht->table[i]; + while(he) { + unsigned int h; + + nextHe = he->next; + /* Get the new element index */ + h = dictHashKey(ht, he->key) & n.sizemask; + he->next = n.table[h]; + n.table[h] = he; + ht->used--; + /* Pass to the next element */ + he = nextHe; + } + } + assert(ht->used == 0); + free(ht->table); + + /* Remap the new hashtable in the old */ + *ht = n; + return DICT_OK; +} + +/* Add an element to the target hash table */ +static int dictAdd(dict *ht, void *key, void *val) { + int index; + dictEntry *entry; + + /* Get the index of the new element, or -1 if + * the element already exists. */ + if ((index = _dictKeyIndex(ht, key)) == -1) + return DICT_ERR; + + /* Allocates the memory and stores key */ + entry = malloc(sizeof(*entry)); + entry->next = ht->table[index]; + ht->table[index] = entry; + + /* Set the hash entry fields. */ + dictSetHashKey(ht, entry, key); + dictSetHashVal(ht, entry, val); + ht->used++; + return DICT_OK; +} + +/* Add an element, discarding the old if the key already exists. + * Return 1 if the key was added from scratch, 0 if there was already an + * element with such key and dictReplace() just performed a value update + * operation. */ +static int dictReplace(dict *ht, void *key, void *val) { + dictEntry *entry, auxentry; + + /* Try to add the element. If the key + * does not exists dictAdd will suceed. */ + if (dictAdd(ht, key, val) == DICT_OK) + return 1; + /* It already exists, get the entry */ + entry = dictFind(ht, key); + /* Free the old value and set the new one */ + /* Set the new value and free the old one. Note that it is important + * to do that in this order, as the value may just be exactly the same + * as the previous one. In this context, think to reference counting, + * you want to increment (set), and then decrement (free), and not the + * reverse. */ + auxentry = *entry; + dictSetHashVal(ht, entry, val); + dictFreeEntryVal(ht, &auxentry); + return 0; +} + +/* Search and remove an element */ +static int dictDelete(dict *ht, const void *key) { + unsigned int h; + dictEntry *de, *prevde; + + if (ht->size == 0) + return DICT_ERR; + h = dictHashKey(ht, key) & ht->sizemask; + de = ht->table[h]; + + prevde = NULL; + while(de) { + if (dictCompareHashKeys(ht,key,de->key)) { + /* Unlink the element from the list */ + if (prevde) + prevde->next = de->next; + else + ht->table[h] = de->next; + + dictFreeEntryKey(ht,de); + dictFreeEntryVal(ht,de); + free(de); + ht->used--; + return DICT_OK; + } + prevde = de; + de = de->next; + } + return DICT_ERR; /* not found */ +} + +/* Destroy an entire hash table */ +static int _dictClear(dict *ht) { + unsigned long i; + + /* Free all the elements */ + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if ((he = ht->table[i]) == NULL) continue; + while(he) { + nextHe = he->next; + dictFreeEntryKey(ht, he); + dictFreeEntryVal(ht, he); + free(he); + ht->used--; + he = nextHe; + } + } + /* Free the table and the allocated cache structure */ + free(ht->table); + /* Re-initialize the table */ + _dictReset(ht); + return DICT_OK; /* never fails */ +} + +/* Clear & Release the hash table */ +static void dictRelease(dict *ht) { + _dictClear(ht); + free(ht); +} + +static dictEntry *dictFind(dict *ht, const void *key) { + dictEntry *he; + unsigned int h; + + if (ht->size == 0) return NULL; + h = dictHashKey(ht, key) & ht->sizemask; + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return he; + he = he->next; + } + return NULL; +} + +static dictIterator *dictGetIterator(dict *ht) { + dictIterator *iter = malloc(sizeof(*iter)); + + iter->ht = ht; + iter->index = -1; + iter->entry = NULL; + iter->nextEntry = NULL; + return iter; +} + +static dictEntry *dictNext(dictIterator *iter) { + while (1) { + if (iter->entry == NULL) { + iter->index++; + if (iter->index >= + (signed)iter->ht->size) break; + iter->entry = iter->ht->table[iter->index]; + } else { + iter->entry = iter->nextEntry; + } + if (iter->entry) { + /* We need to save the 'next' here, the iterator user + * may delete the entry we are returning. */ + iter->nextEntry = iter->entry->next; + return iter->entry; + } + } + return NULL; +} + +static void dictReleaseIterator(dictIterator *iter) { + free(iter); +} + +/* ------------------------- private functions ------------------------------ */ + +/* Expand the hash table if needed */ +static int _dictExpandIfNeeded(dict *ht) { + /* If the hash table is empty expand it to the intial size, + * if the table is "full" dobule its size. */ + if (ht->size == 0) + return dictExpand(ht, DICT_HT_INITIAL_SIZE); + if (ht->used == ht->size) + return dictExpand(ht, ht->size*2); + return DICT_OK; +} + +/* Our hash table capability is a power of two */ +static unsigned long _dictNextPower(unsigned long size) { + unsigned long i = DICT_HT_INITIAL_SIZE; + + if (size >= LONG_MAX) return LONG_MAX; + while(1) { + if (i >= size) + return i; + i *= 2; + } +} + +/* Returns the index of a free slot that can be populated with + * an hash entry for the given 'key'. + * If the key already exists, -1 is returned. */ +static int _dictKeyIndex(dict *ht, const void *key) { + unsigned int h; + dictEntry *he; + + /* Expand the hashtable if needed */ + if (_dictExpandIfNeeded(ht) == DICT_ERR) + return -1; + /* Compute the key hash value */ + h = dictHashKey(ht, key) & ht->sizemask; + /* Search if this slot does not already contain the given key */ + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return -1; + he = he->next; + } + return h; +} + diff --git a/data-server/external/hiredis/dict.h b/data-server/external/hiredis/dict.h new file mode 100755 index 0000000000..95fcd280e2 --- /dev/null +++ b/data-server/external/hiredis/dict.h @@ -0,0 +1,126 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __DICT_H +#define __DICT_H + +#define DICT_OK 0 +#define DICT_ERR 1 + +/* Unused arguments generate annoying warnings... */ +#define DICT_NOTUSED(V) ((void) V) + +typedef struct dictEntry { + void *key; + void *val; + struct dictEntry *next; +} dictEntry; + +typedef struct dictType { + unsigned int (*hashFunction)(const void *key); + void *(*keyDup)(void *privdata, const void *key); + void *(*valDup)(void *privdata, const void *obj); + int (*keyCompare)(void *privdata, const void *key1, const void *key2); + void (*keyDestructor)(void *privdata, void *key); + void (*valDestructor)(void *privdata, void *obj); +} dictType; + +typedef struct dict { + dictEntry **table; + dictType *type; + unsigned long size; + unsigned long sizemask; + unsigned long used; + void *privdata; +} dict; + +typedef struct dictIterator { + dict *ht; + int index; + dictEntry *entry, *nextEntry; +} dictIterator; + +/* This is the initial size of every hash table */ +#define DICT_HT_INITIAL_SIZE 4 + +/* ------------------------------- Macros ------------------------------------*/ +#define dictFreeEntryVal(ht, entry) \ + if ((ht)->type->valDestructor) \ + (ht)->type->valDestructor((ht)->privdata, (entry)->val) + +#define dictSetHashVal(ht, entry, _val_) do { \ + if ((ht)->type->valDup) \ + entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ + else \ + entry->val = (_val_); \ +} while(0) + +#define dictFreeEntryKey(ht, entry) \ + if ((ht)->type->keyDestructor) \ + (ht)->type->keyDestructor((ht)->privdata, (entry)->key) + +#define dictSetHashKey(ht, entry, _key_) do { \ + if ((ht)->type->keyDup) \ + entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ + else \ + entry->key = (_key_); \ +} while(0) + +#define dictCompareHashKeys(ht, key1, key2) \ + (((ht)->type->keyCompare) ? \ + (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ + (key1) == (key2)) + +#define dictHashKey(ht, key) (ht)->type->hashFunction(key) + +#define dictGetEntryKey(he) ((he)->key) +#define dictGetEntryVal(he) ((he)->val) +#define dictSlots(ht) ((ht)->size) +#define dictSize(ht) ((ht)->used) + +/* API */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len); +static dict *dictCreate(dictType *type, void *privDataPtr); +static int dictExpand(dict *ht, unsigned long size); +static int dictAdd(dict *ht, void *key, void *val); +static int dictReplace(dict *ht, void *key, void *val); +static int dictDelete(dict *ht, const void *key); +static void dictRelease(dict *ht); +static dictEntry * dictFind(dict *ht, const void *key); +static dictIterator *dictGetIterator(dict *ht); +static dictEntry *dictNext(dictIterator *iter); +static void dictReleaseIterator(dictIterator *iter); + +#endif /* __DICT_H */ diff --git a/data-server/external/hiredis/fmacros.h b/data-server/external/hiredis/fmacros.h new file mode 100755 index 0000000000..799c12c1c0 --- /dev/null +++ b/data-server/external/hiredis/fmacros.h @@ -0,0 +1,20 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#if !defined(_BSD_SOURCE) +#define _BSD_SOURCE +#endif + +#if defined(__sun__) +#define _POSIX_C_SOURCE 200112L +#elif defined(__linux__) +#define _XOPEN_SOURCE 600 +#else +#define _XOPEN_SOURCE +#endif + +#if __APPLE__ && __MACH__ +#define _OSX +#endif + +#endif diff --git a/data-server/external/hiredis/hiredis.c b/data-server/external/hiredis/hiredis.c new file mode 100755 index 0000000000..9b74b5b775 --- /dev/null +++ b/data-server/external/hiredis/hiredis.c @@ -0,0 +1,1322 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" +#include "sds.h" + +static redisReply *createReplyObject(int type); +static void *createStringObject(const redisReadTask *task, char *str, size_t len); +static void *createArrayObject(const redisReadTask *task, int elements); +static void *createIntegerObject(const redisReadTask *task, long long value); +static void *createNilObject(const redisReadTask *task); + +/* Default set of functions to build the reply. Keep in mind that such a + * function returning NULL is interpreted as OOM. */ +static redisReplyObjectFunctions defaultFunctions = { + createStringObject, + createArrayObject, + createIntegerObject, + createNilObject, + freeReplyObject +}; + +/* Create a reply object */ +static redisReply *createReplyObject(int type) { + redisReply *r = calloc(1,sizeof(*r)); + + if (r == NULL) + return NULL; + + r->type = type; + return r; +} + +/* Free a reply object */ +void freeReplyObject(void *reply) { + redisReply *r = reply; + size_t j; + + switch(r->type) { + case REDIS_REPLY_INTEGER: + break; /* Nothing to free */ + case REDIS_REPLY_ARRAY: + if (r->element != NULL) { + for (j = 0; j < r->elements; j++) + if (r->element[j] != NULL) + freeReplyObject(r->element[j]); + free(r->element); + } + break; + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_STRING: + if (r->str != NULL) + free(r->str); + break; + } + free(r); +} + +static void *createStringObject(const redisReadTask *task, char *str, size_t len) { + redisReply *r, *parent; + char *buf; + + r = createReplyObject(task->type); + if (r == NULL) + return NULL; + + buf = malloc(len+1); + if (buf == NULL) { + freeReplyObject(r); + return NULL; + } + + assert(task->type == REDIS_REPLY_ERROR || + task->type == REDIS_REPLY_STATUS || + task->type == REDIS_REPLY_STRING); + + /* Copy string value */ + memcpy(buf,str,len); + buf[len] = '\0'; + r->str = buf; + r->len = len; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createArrayObject(const redisReadTask *task, int elements) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_ARRAY); + if (r == NULL) + return NULL; + + if (elements > 0) { + r->element = calloc(elements,sizeof(redisReply*)); + if (r->element == NULL) { + freeReplyObject(r); + return NULL; + } + } + + r->elements = elements; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createIntegerObject(const redisReadTask *task, long long value) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_INTEGER); + if (r == NULL) + return NULL; + + r->integer = value; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createNilObject(const redisReadTask *task) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_NIL); + if (r == NULL) + return NULL; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void __redisReaderSetError(redisReader *r, int type, const char *str) { + size_t len; + + if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + r->reply = NULL; + } + + /* Clear input buffer on errors. */ + if (r->buf != NULL) { + sdsfree(r->buf); + r->buf = NULL; + r->pos = r->len = 0; + } + + /* Reset task stack. */ + r->ridx = -1; + + /* Set error. */ + r->err = type; + len = strlen(str); + len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); + memcpy(r->errstr,str,len); + r->errstr[len] = '\0'; +} + +static size_t chrtos(char *buf, size_t size, char byte) { + size_t len = 0; + + switch(byte) { + case '\\': + case '"': + len = snprintf(buf,size,"\"\\%c\"",byte); + break; + case '\n': len = snprintf(buf,size,"\"\\n\""); break; + case '\r': len = snprintf(buf,size,"\"\\r\""); break; + case '\t': len = snprintf(buf,size,"\"\\t\""); break; + case '\a': len = snprintf(buf,size,"\"\\a\""); break; + case '\b': len = snprintf(buf,size,"\"\\b\""); break; + default: + if (isprint(byte)) + len = snprintf(buf,size,"\"%c\"",byte); + else + len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); + break; + } + + return len; +} + +static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { + char cbuf[8], sbuf[128]; + + chrtos(cbuf,sizeof(cbuf),byte); + snprintf(sbuf,sizeof(sbuf), + "Protocol error, got %s as reply type byte", cbuf); + __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); +} + +static void __redisReaderSetErrorOOM(redisReader *r) { + __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); +} + +static char *readBytes(redisReader *r, unsigned int bytes) { + char *p; + if (r->len-r->pos >= bytes) { + p = r->buf+r->pos; + r->pos += bytes; + return p; + } + return NULL; +} + +/* Find pointer to \r\n. */ +static char *seekNewline(char *s, size_t len) { + int pos = 0; + int _len = len-1; + + /* Position should be < len-1 because the character at "pos" should be + * followed by a \n. Note that strchr cannot be used because it doesn't + * allow to search a limited length and the buffer that is being searched + * might not have a trailing NULL character. */ + while (pos < _len) { + while(pos < _len && s[pos] != '\r') pos++; + if (s[pos] != '\r') { + /* Not found. */ + return NULL; + } else { + if (s[pos+1] == '\n') { + /* Found. */ + return s+pos; + } else { + /* Continue searching. */ + pos++; + } + } + } + return NULL; +} + +/* Read a long long value starting at *s, under the assumption that it will be + * terminated by \r\n. Ambiguously returns -1 for unexpected input. */ +static long long readLongLong(char *s) { + long long v = 0; + int dec, mult = 1; + char c; + + if (*s == '-') { + mult = -1; + s++; + } else if (*s == '+') { + mult = 1; + s++; + } + + while ((c = *(s++)) != '\r') { + dec = c - '0'; + if (dec >= 0 && dec < 10) { + v *= 10; + v += dec; + } else { + /* Should not happen... */ + return -1; + } + } + + return mult*v; +} + +static char *readLine(redisReader *r, int *_len) { + char *p, *s; + int len; + + p = r->buf+r->pos; + s = seekNewline(p,(r->len-r->pos)); + if (s != NULL) { + len = s-(r->buf+r->pos); + r->pos += len+2; /* skip \r\n */ + if (_len) *_len = len; + return p; + } + return NULL; +} + +static void moveToNextTask(redisReader *r) { + redisReadTask *cur, *prv; + while (r->ridx >= 0) { + /* Return a.s.a.p. when the stack is now empty. */ + if (r->ridx == 0) { + r->ridx--; + return; + } + + cur = &(r->rstack[r->ridx]); + prv = &(r->rstack[r->ridx-1]); + assert(prv->type == REDIS_REPLY_ARRAY); + if (cur->idx == prv->elements-1) { + r->ridx--; + } else { + /* Reset the type because the next item can be anything */ + assert(cur->idx < prv->elements); + cur->type = -1; + cur->elements = -1; + cur->idx++; + return; + } + } +} + +static int processLineItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + int len; + + if ((p = readLine(r,&len)) != NULL) { + if (cur->type == REDIS_REPLY_INTEGER) { + if (r->fn && r->fn->createInteger) + obj = r->fn->createInteger(cur,readLongLong(p)); + else + obj = (void*)REDIS_REPLY_INTEGER; + } else { + /* Type will be error or status. */ + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,p,len); + else + obj = (void*)(size_t)(cur->type); + } + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj = NULL; + char *p, *s; + long len; + unsigned long bytelen; + int success = 0; + + p = r->buf+r->pos; + s = seekNewline(p,r->len-r->pos); + if (s != NULL) { + p = r->buf+r->pos; + bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ + len = readLongLong(p); + + if (len < 0) { + /* The nil object can always be created. */ + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + success = 1; + } else { + /* Only continue when the buffer contains the entire bulk item. */ + bytelen += len+2; /* include \r\n */ + if (r->pos+bytelen <= r->len) { + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,s+2,len); + else + obj = (void*)REDIS_REPLY_STRING; + success = 1; + } + } + + /* Proceed when obj was created. */ + if (success) { + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->pos += bytelen; + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + } + + return REDIS_ERR; +} + +static int processMultiBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + long elements; + int root = 0; + + /* Set error for nested multi bulks with depth > 7 */ + if (r->ridx == 8) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "No support for nested multi bulk replies with depth > 7"); + return REDIS_ERR; + } + + if ((p = readLine(r,NULL)) != NULL) { + elements = readLongLong(p); + root = (r->ridx == 0); + + if (elements == -1) { + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + moveToNextTask(r); + } else { + if (r->fn && r->fn->createArray) + obj = r->fn->createArray(cur,elements); + else + obj = (void*)REDIS_REPLY_ARRAY; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Modify task stack when there are more than 0 elements. */ + if (elements > 0) { + cur->elements = elements; + cur->obj = obj; + r->ridx++; + r->rstack[r->ridx].type = -1; + r->rstack[r->ridx].elements = -1; + r->rstack[r->ridx].idx = 0; + r->rstack[r->ridx].obj = NULL; + r->rstack[r->ridx].parent = cur; + r->rstack[r->ridx].privdata = r->privdata; + } else { + moveToNextTask(r); + } + } + + /* Set reply if this is the root object. */ + if (root) r->reply = obj; + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + char *p; + + /* check if we need to read type */ + if (cur->type < 0) { + if ((p = readBytes(r,1)) != NULL) { + switch (p[0]) { + case '-': + cur->type = REDIS_REPLY_ERROR; + break; + case '+': + cur->type = REDIS_REPLY_STATUS; + break; + case ':': + cur->type = REDIS_REPLY_INTEGER; + break; + case '$': + cur->type = REDIS_REPLY_STRING; + break; + case '*': + cur->type = REDIS_REPLY_ARRAY; + break; + default: + __redisReaderSetErrorProtocolByte(r,*p); + return REDIS_ERR; + } + } else { + /* could not consume 1 byte */ + return REDIS_ERR; + } + } + + /* process typed item */ + switch(cur->type) { + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_INTEGER: + return processLineItem(r); + case REDIS_REPLY_STRING: + return processBulkItem(r); + case REDIS_REPLY_ARRAY: + return processMultiBulkItem(r); + default: + assert(NULL); + return REDIS_ERR; /* Avoid warning. */ + } +} + +redisReader *redisReaderCreate(void) { + redisReader *r; + + r = calloc(sizeof(redisReader),1); + if (r == NULL) + return NULL; + + r->err = 0; + r->errstr[0] = '\0'; + r->fn = &defaultFunctions; + r->buf = sdsempty(); + r->maxbuf = REDIS_READER_MAX_BUF; + if (r->buf == NULL) { + free(r); + return NULL; + } + + r->ridx = -1; + return r; +} + +void redisReaderFree(redisReader *r) { + if (r->reply != NULL && r->fn && r->fn->freeObject) + r->fn->freeObject(r->reply); + if (r->buf != NULL) + sdsfree(r->buf); + free(r); +} + +int redisReaderFeed(redisReader *r, const char *buf, size_t len) { + sds newbuf; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* Copy the provided buffer. */ + if (buf != NULL && len >= 1) { + /* Destroy internal buffer when it is empty and is quite large. */ + if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { + sdsfree(r->buf); + r->buf = sdsempty(); + r->pos = 0; + + /* r->buf should not be NULL since we just free'd a larger one. */ + assert(r->buf != NULL); + } + + newbuf = sdscatlen(r->buf,buf,len); + if (newbuf == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->buf = newbuf; + r->len = sdslen(r->buf); + } + + return REDIS_OK; +} + +int redisReaderGetReply(redisReader *r, void **reply) { + /* Default target pointer to NULL. */ + if (reply != NULL) + *reply = NULL; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* When the buffer is empty, there will never be a reply. */ + if (r->len == 0) + return REDIS_OK; + + /* Set first item to process when the stack is empty. */ + if (r->ridx == -1) { + r->rstack[0].type = -1; + r->rstack[0].elements = -1; + r->rstack[0].idx = -1; + r->rstack[0].obj = NULL; + r->rstack[0].parent = NULL; + r->rstack[0].privdata = r->privdata; + r->ridx = 0; + } + + /* Process items in reply. */ + while (r->ridx >= 0) + if (processItem(r) != REDIS_OK) + break; + + /* Return ASAP when an error occurred. */ + if (r->err) + return REDIS_ERR; + + /* Discard part of the buffer when we've consumed at least 1k, to avoid + * doing unnecessary calls to memmove() in sds.c. */ + if (r->pos >= 1024) { + r->buf = sdsrange(r->buf,r->pos,-1); + r->pos = 0; + r->len = sdslen(r->buf); + } + + /* Emit a reply when there is one. */ + if (r->ridx == -1) { + if (reply != NULL) + *reply = r->reply; + r->reply = NULL; + } + return REDIS_OK; +} + +/* Calculate the number of bytes needed to represent an integer as string. */ +static int intlen(int i) { + int len = 0; + if (i < 0) { + len++; + i = -i; + } + do { + len++; + i /= 10; + } while(i); + return len; +} + +/* Helper that calculates the bulk length given a certain string length. */ +static size_t bulklen(size_t len) { + return 1+intlen(len)+2+len+2; +} + +int redisvFormatCommand(char **target, const char *format, va_list ap) { + const char *c = format; + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + sds curarg, newarg; /* current argument */ + int touched = 0; /* was the current argument touched? */ + char **curargv = NULL, **newargv = NULL; + int argc = 0; + int totlen = 0; + int j; + + /* Abort if there is not target to set */ + if (target == NULL) + return -1; + + /* Build the command string accordingly to protocol */ + curarg = sdsempty(); + if (curarg == NULL) + return -1; + + while(*c != '\0') { + if (*c != '%' || c[1] == '\0') { + if (*c == ' ') { + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + + /* curarg is put in argv so it can be overwritten. */ + curarg = sdsempty(); + if (curarg == NULL) goto err; + touched = 0; + } + } else { + newarg = sdscatlen(curarg,c,1); + if (newarg == NULL) goto err; + curarg = newarg; + touched = 1; + } + } else { + char *arg; + size_t size; + + /* Set newarg so it can be checked even if it is not touched. */ + newarg = curarg; + + switch(c[1]) { + case 's': + arg = va_arg(ap,char*); + size = strlen(arg); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case 'b': + arg = va_arg(ap,char*); + size = va_arg(ap,size_t); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case '%': + newarg = sdscat(curarg,"%"); + break; + default: + /* Try to detect printf format */ + { + static const char intfmts[] = "diouxX"; + char _format[16]; + const char *_p = c+1; + size_t _l = 0; + va_list _cpy; + + /* Flags */ + if (*_p != '\0' && *_p == '#') _p++; + if (*_p != '\0' && *_p == '0') _p++; + if (*_p != '\0' && *_p == '-') _p++; + if (*_p != '\0' && *_p == ' ') _p++; + if (*_p != '\0' && *_p == '+') _p++; + + /* Field width */ + while (*_p != '\0' && isdigit(*_p)) _p++; + + /* Precision */ + if (*_p == '.') { + _p++; + while (*_p != '\0' && isdigit(*_p)) _p++; + } + + /* Copy va_list before consuming with va_arg */ + va_copy(_cpy,ap); + + /* Integer conversion (without modifiers) */ + if (strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); + goto fmt_valid; + } + + /* Double conversion (without modifiers) */ + if (strchr("eEfFgGaA",*_p) != NULL) { + va_arg(ap,double); + goto fmt_valid; + } + + /* Size: char */ + if (_p[0] == 'h' && _p[1] == 'h') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* char gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: short */ + if (_p[0] == 'h') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* short gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long long */ + if (_p[0] == 'l' && _p[1] == 'l') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long long); + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long */ + if (_p[0] == 'l') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long); + goto fmt_valid; + } + goto fmt_invalid; + } + + fmt_invalid: + va_end(_cpy); + goto err; + + fmt_valid: + _l = (_p+1)-c; + if (_l < sizeof(_format)-2) { + memcpy(_format,c,_l); + _format[_l] = '\0'; + newarg = sdscatvprintf(curarg,_format,_cpy); + + /* Update current position (note: outer blocks + * increment c twice so compensate here) */ + c = _p-1; + } + + va_end(_cpy); + break; + } + } + + if (newarg == NULL) goto err; + curarg = newarg; + + touched = 1; + c++; + } + c++; + } + + /* Add the last argument if needed */ + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + } else { + sdsfree(curarg); + } + + /* Clear curarg because it was put in curargv or was free'd. */ + curarg = NULL; + + /* Add bytes needed to hold multi bulk count */ + totlen += 1+intlen(argc)+2; + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) goto err; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); + memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); + pos += sdslen(curargv[j]); + sdsfree(curargv[j]); + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + free(curargv); + *target = cmd; + return totlen; + +err: + while(argc--) + sdsfree(curargv[argc]); + free(curargv); + + if (curarg != NULL) + sdsfree(curarg); + + /* No need to check cmd since it is the last statement that can fail, + * but do it anyway to be as defensive as possible. */ + if (cmd != NULL) + free(cmd); + + return -1; +} + +/* Format a command according to the Redis protocol. This function + * takes a format similar to printf: + * + * %s represents a C null terminated string you want to interpolate + * %b represents a binary safe string + * + * When using %b you need to provide both the pointer to the string + * and the length in bytes as a size_t. Examples: + * + * len = redisFormatCommand(target, "GET %s", mykey); + * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); + */ +int redisFormatCommand(char **target, const char *format, ...) { + va_list ap; + int len; + va_start(ap,format); + len = redisvFormatCommand(target,format,ap); + va_end(ap); + return len; +} + +/* Format a command according to the Redis protocol. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + size_t len; + int totlen, j; + + /* Calculate number of bytes needed for the command */ + totlen = 1+intlen(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) + return -1; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",len); + memcpy(cmd+pos,argv[j],len); + pos += len; + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + *target = cmd; + return totlen; +} + +void __redisSetError(redisContext *c, int type, const char *str) { + size_t len; + + c->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); + memcpy(c->errstr,str,len); + c->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + strerror_r(errno,c->errstr,sizeof(c->errstr)); + } +} + +static redisContext *redisContextInit(void) { + redisContext *c; + + c = calloc(1,sizeof(redisContext)); + if (c == NULL) + return NULL; + + c->err = 0; + c->errstr[0] = '\0'; + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + return c; +} + +void redisFree(redisContext *c) { + if (c->fd > 0) + close(c->fd); + if (c->obuf != NULL) + sdsfree(c->obuf); + if (c->reader != NULL) + redisReaderFree(c->reader); + free(c); +} + +/* Connect to a Redis instance. On error the field error in the returned + * context will be set to the return value of the error function. + * When no set of reply functions is given, the default set will be used. */ +redisContext *redisConnect(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,&tv); + return c; +} + +redisContext *redisConnectNonBlock(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectUnix(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,&tv); + return c; +} + +redisContext *redisConnectUnixNonBlock(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +/* Set read/write timeout on a blocking socket. */ +int redisSetTimeout(redisContext *c, const struct timeval tv) { + if (c->flags & REDIS_BLOCK) + return redisContextSetTimeout(c,tv); + return REDIS_ERR; +} + +/* Enable connection KeepAlive. */ +int redisEnableKeepAlive(redisContext *c) { + if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) + return REDIS_ERR; + return REDIS_OK; +} + +/* Use this function to handle a read event on the descriptor. It will try + * and read some bytes from the socket and feed them to the reply parser. + * + * After this function is called, you may use redisContextReadReply to + * see if there is a reply available. */ +int redisBufferRead(redisContext *c) { + char buf[1024*16]; + int nread; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + nread = read(c->fd,buf,sizeof(buf)); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nread == 0) { + __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + return REDIS_ERR; + } else { + if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + } + return REDIS_OK; +} + +/* Write the output buffer to the socket. + * + * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was + * succesfully written to the socket. When the buffer is empty after the + * write operation, "done" is set to 1 (if given). + * + * Returns REDIS_ERR if an error occured trying to write and sets + * c->errstr to hold the appropriate error string. + */ +int redisBufferWrite(redisContext *c, int *done) { + int nwritten; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + if (sdslen(c->obuf) > 0) { + nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); + if (nwritten == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nwritten > 0) { + if (nwritten == (signed)sdslen(c->obuf)) { + sdsfree(c->obuf); + c->obuf = sdsempty(); + } else { + c->obuf = sdsrange(c->obuf,nwritten,-1); + } + } + } + if (done != NULL) *done = (sdslen(c->obuf) == 0); + return REDIS_OK; +} + +/* Internal helper function to try and get a reply from the reader, + * or set an error in the context otherwise. */ +int redisGetReplyFromReader(redisContext *c, void **reply) { + if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisGetReply(redisContext *c, void **reply) { + int wdone = 0; + void *aux = NULL; + + /* Try to read pending replies */ + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + + /* For the blocking context, flush output buffer and read reply */ + if (aux == NULL && c->flags & REDIS_BLOCK) { + /* Write until done */ + do { + if (redisBufferWrite(c,&wdone) == REDIS_ERR) + return REDIS_ERR; + } while (!wdone); + + /* Read until there is a reply */ + do { + if (redisBufferRead(c) == REDIS_ERR) + return REDIS_ERR; + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + } while (aux == NULL); + } + + /* Set reply object */ + if (reply != NULL) *reply = aux; + return REDIS_OK; +} + + +/* Helper function for the redisAppendCommand* family of functions. + * + * Write a formatted command to the output buffer. When this family + * is used, you need to call redisGetReply yourself to retrieve + * the reply (or replies in pub/sub). + */ +int __redisAppendCommand(redisContext *c, char *cmd, size_t len) { + sds newbuf; + + newbuf = sdscatlen(c->obuf,cmd,len); + if (newbuf == NULL) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + c->obuf = newbuf; + return REDIS_OK; +} + +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { + char *cmd; + int len; + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + + free(cmd); + return REDIS_OK; +} + +int redisAppendCommand(redisContext *c, const char *format, ...) { + va_list ap; + int ret; + + va_start(ap,format); + ret = redisvAppendCommand(c,format,ap); + va_end(ap); + return ret; +} + +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + char *cmd; + int len; + + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + + free(cmd); + return REDIS_OK; +} + +/* Helper function for the redisCommand* family of functions. + * + * Write a formatted command to the output buffer. If the given context is + * blocking, immediately read the reply into the "reply" pointer. When the + * context is non-blocking, the "reply" pointer will not be used and the + * command is simply appended to the write buffer. + * + * Returns the reply when a reply was succesfully retrieved. Returns NULL + * otherwise. When NULL is returned in a blocking context, the error field + * in the context will be set. + */ +static void *__redisBlockForReply(redisContext *c) { + void *reply; + + if (c->flags & REDIS_BLOCK) { + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; + } + return NULL; +} + +void *redisvCommand(redisContext *c, const char *format, va_list ap) { + if (redisvAppendCommand(c,format,ap) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} + +void *redisCommand(redisContext *c, const char *format, ...) { + va_list ap; + void *reply = NULL; + va_start(ap,format); + reply = redisvCommand(c,format,ap); + va_end(ap); + return reply; +} + +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} diff --git a/data-server/external/hiredis/hiredis.h b/data-server/external/hiredis/hiredis.h new file mode 100755 index 0000000000..c65098b740 --- /dev/null +++ b/data-server/external/hiredis/hiredis.h @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HIREDIS_H +#define __HIREDIS_H +#include /* for size_t */ +#include /* for va_list */ +#include /* for struct timeval */ + +#define HIREDIS_MAJOR 0 +#define HIREDIS_MINOR 11 +#define HIREDIS_PATCH 0 + +#define REDIS_ERR -1 +#define REDIS_OK 0 + +/* When an error occurs, the err flag in a context is set to hold the type of + * error that occured. REDIS_ERR_IO means there was an I/O error and you + * should use the "errno" variable to find out what is wrong. + * For other values, the "errstr" field will hold a description. */ +#define REDIS_ERR_IO 1 /* Error in read or write */ +#define REDIS_ERR_EOF 3 /* End of file */ +#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ +#define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_OTHER 2 /* Everything else... */ + +/* Connection type can be blocking or non-blocking and is set in the + * least significant bit of the flags field in redisContext. */ +#define REDIS_BLOCK 0x1 + +/* Connection may be disconnected before being free'd. The second bit + * in the flags field is set when the context is connected. */ +#define REDIS_CONNECTED 0x2 + +/* The async API might try to disconnect cleanly and flush the output + * buffer and read all subsequent replies before disconnecting. + * This flag means no new commands can come in and the connection + * should be terminated once all replies have been read. */ +#define REDIS_DISCONNECTING 0x4 + +/* Flag specific to the async API which means that the context should be clean + * up as soon as possible. */ +#define REDIS_FREEING 0x8 + +/* Flag that is set when an async callback is executed. */ +#define REDIS_IN_CALLBACK 0x10 + +/* Flag that is set when the async context has one or more subscriptions. */ +#define REDIS_SUBSCRIBED 0x20 + +/* Flag that is set when monitor mode is active */ +#define REDIS_MONITORING 0x40 + +#define REDIS_REPLY_STRING 1 +#define REDIS_REPLY_ARRAY 2 +#define REDIS_REPLY_INTEGER 3 +#define REDIS_REPLY_NIL 4 +#define REDIS_REPLY_STATUS 5 +#define REDIS_REPLY_ERROR 6 + +#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ + +#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reply object returned by redisCommand() */ +typedef struct redisReply { + int type; /* REDIS_REPLY_* */ + long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ + int len; /* Length of string */ + char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ + size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ + struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ +} redisReply; + +typedef struct redisReadTask { + int type; + int elements; /* number of elements in multibulk container */ + int idx; /* index in parent (array) object */ + void *obj; /* holds user-generated value for a read task */ + struct redisReadTask *parent; /* parent task */ + void *privdata; /* user-settable arbitrary field */ +} redisReadTask; + +typedef struct redisReplyObjectFunctions { + void *(*createString)(const redisReadTask*, char*, size_t); + void *(*createArray)(const redisReadTask*, int); + void *(*createInteger)(const redisReadTask*, long long); + void *(*createNil)(const redisReadTask*); + void (*freeObject)(void*); +} redisReplyObjectFunctions; + +/* State for the protocol parser */ +typedef struct redisReader { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + + char *buf; /* Read buffer */ + size_t pos; /* Buffer cursor */ + size_t len; /* Buffer length */ + size_t maxbuf; /* Max length of unused buffer */ + + redisReadTask rstack[9]; + int ridx; /* Index of current read task */ + void *reply; /* Temporary reply pointer */ + + redisReplyObjectFunctions *fn; + void *privdata; +} redisReader; + +/* Public API for the protocol parser. */ +redisReader *redisReaderCreate(void); +void redisReaderFree(redisReader *r); +int redisReaderFeed(redisReader *r, const char *buf, size_t len); +int redisReaderGetReply(redisReader *r, void **reply); + +/* Backwards compatibility, can be removed on big version bump. */ +#define redisReplyReaderCreate redisReaderCreate +#define redisReplyReaderFree redisReaderFree +#define redisReplyReaderFeed redisReaderFeed +#define redisReplyReaderGetReply redisReaderGetReply +#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) + +/* Function to free the reply objects hiredis returns by default. */ +void freeReplyObject(void *reply); + +/* Functions to format a command according to the protocol. */ +int redisvFormatCommand(char **target, const char *format, va_list ap); +int redisFormatCommand(char **target, const char *format, ...); +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); + +/* Context for a connection to Redis */ +typedef struct redisContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + int fd; + int flags; + char *obuf; /* Write buffer */ + redisReader *reader; /* Protocol reader */ +} redisContext; + +redisContext *redisConnect(const char *ip, int port); +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); +redisContext *redisConnectNonBlock(const char *ip, int port); +redisContext *redisConnectUnix(const char *path); +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); +redisContext *redisConnectUnixNonBlock(const char *path); +int redisSetTimeout(redisContext *c, const struct timeval tv); +int redisEnableKeepAlive(redisContext *c); +void redisFree(redisContext *c); +int redisBufferRead(redisContext *c); +int redisBufferWrite(redisContext *c, int *done); + +/* In a blocking context, this function first checks if there are unconsumed + * replies to return and returns one if so. Otherwise, it flushes the output + * buffer to the socket and reads until it has a reply. In a non-blocking + * context, it will return unconsumed replies until there are no more. */ +int redisGetReply(redisContext *c, void **reply); +int redisGetReplyFromReader(redisContext *c, void **reply); + +/* Write a command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisvAppendCommand(redisContext *c, const char *format, va_list ap); +int redisAppendCommand(redisContext *c, const char *format, ...); +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +/* Issue a command to Redis. In a blocking context, it is identical to calling + * redisAppendCommand, followed by redisGetReply. The function will return + * NULL if there was an error in performing the request, otherwise it will + * return the reply. In a non-blocking context, it is identical to calling + * only redisAppendCommand and will always return NULL. */ +void *redisvCommand(redisContext *c, const char *format, va_list ap); +void *redisCommand(redisContext *c, const char *format, ...); +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/data-server/external/hiredis/net.c b/data-server/external/hiredis/net.c new file mode 100755 index 0000000000..603699c936 --- /dev/null +++ b/data-server/external/hiredis/net.c @@ -0,0 +1,339 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2006-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" +#include "sds.h" + +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { + char buf[128] = { 0 }; + size_t len = 0; + + if (prefix != NULL) + len = snprintf(buf,sizeof(buf),"%s: ",prefix); + strerror_r(errno,buf+len,sizeof(buf)-len); + __redisSetError(c,type,buf); +} + +static int redisSetReuseAddr(redisContext *c, int fd) { + int on = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + close(fd); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int redisCreateSocket(redisContext *c, int type) { + int s; + if ((s = socket(type, SOCK_STREAM, 0)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + if (type == AF_INET) { + if (redisSetReuseAddr(c,s) == REDIS_ERR) { + return REDIS_ERR; + } + } + return s; +} + +static int redisSetBlocking(redisContext *c, int fd, int blocking) { + int flags; + + /* Set the socket nonblocking. + * Note that fcntl(2) for F_GETFL and F_SETFL can't be + * interrupted by a signal. */ + if ((flags = fcntl(fd, F_GETFL)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); + close(fd); + return REDIS_ERR; + } + + if (blocking) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); + close(fd); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisKeepAlive(redisContext *c, int interval) { + int val = 1; + int fd = c->fd; + + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + +#ifdef _OSX + val = interval; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#else + val = interval; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval/3; + if (val == 0) val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#endif + + return REDIS_OK; +} + +static int redisSetTcpNoDelay(redisContext *c, int fd) { + int yes = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); + close(fd); + return REDIS_ERR; + } + return REDIS_OK; +} + +#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) + +static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) { + struct pollfd wfd[1]; + long msec; + + msec = -1; + wfd[0].fd = fd; + wfd[0].events = POLLOUT; + + /* Only use timeout when not NULL. */ + if (timeout != NULL) { + if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); + close(fd); + return REDIS_ERR; + } + + msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); + + if (msec < 0 || msec > INT_MAX) { + msec = INT_MAX; + } + } + + if (errno == EINPROGRESS) { + int res; + + if ((res = poll(wfd, 1, msec)) == -1) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); + close(fd); + return REDIS_ERR; + } else if (res == 0) { + errno = ETIMEDOUT; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + close(fd); + return REDIS_ERR; + } + + if (redisCheckSocketError(c, fd) != REDIS_OK) + return REDIS_ERR; + + return REDIS_OK; + } + + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + close(fd); + return REDIS_ERR; +} + +int redisCheckSocketError(redisContext *c, int fd) { + int err = 0; + socklen_t errlen = sizeof(err); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); + close(fd); + return REDIS_ERR; + } + + if (err) { + errno = err; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + close(fd); + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisContextSetTimeout(redisContext *c, const struct timeval tv) { + if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); + return REDIS_ERR; + } + if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout) { + int s, rv; + char _port[6]; /* strlen("65535"); */ + struct addrinfo hints, *servinfo, *p; + int blocking = (c->flags & REDIS_BLOCK); + + snprintf(_port, 6, "%d", port); + memset(&hints,0,sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + /* Try with IPv6 if no IPv4 address was found. We do it in this order since + * in a Redis client you can't afford to test if you have IPv6 connectivity + * as this would add latency to every connect. Otherwise a more sensible + * route could be: Use IPv6 if both addresses are available and there is IPv6 + * connectivity. */ + if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + hints.ai_family = AF_INET6; + if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); + return REDIS_ERR; + } + } + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + continue; + + if (redisSetBlocking(c,s,0) != REDIS_OK) + goto error; + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { + if (errno == EHOSTUNREACH) { + close(s); + continue; + } else if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else { + if (redisContextWaitReady(c,s,timeout) != REDIS_OK) + goto error; + } + } + if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) + goto error; + if (redisSetTcpNoDelay(c,s) != REDIS_OK) + goto error; + + c->fd = s; + c->flags |= REDIS_CONNECTED; + rv = REDIS_OK; + goto end; + } + if (p == NULL) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + +error: + rv = REDIS_ERR; +end: + freeaddrinfo(servinfo); + return rv; // Need to return REDIS_OK if alright +} + +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { + int s; + int blocking = (c->flags & REDIS_BLOCK); + struct sockaddr_un sa; + + if ((s = redisCreateSocket(c,AF_LOCAL)) < 0) + return REDIS_ERR; + if (redisSetBlocking(c,s,0) != REDIS_OK) + return REDIS_ERR; + + sa.sun_family = AF_LOCAL; + strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); + if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else { + if (redisContextWaitReady(c,s,timeout) != REDIS_OK) + return REDIS_ERR; + } + } + + /* Reset socket to be blocking after connect(2). */ + if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) + return REDIS_ERR; + + c->fd = s; + c->flags |= REDIS_CONNECTED; + return REDIS_OK; +} diff --git a/data-server/external/hiredis/net.h b/data-server/external/hiredis/net.h new file mode 100755 index 0000000000..94b76f5ca7 --- /dev/null +++ b/data-server/external/hiredis/net.h @@ -0,0 +1,48 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2006-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __NET_H +#define __NET_H + +#include "hiredis.h" + +#if defined(__sun) +#define AF_LOCAL AF_UNIX +#endif + +int redisCheckSocketError(redisContext *c, int fd); +int redisContextSetTimeout(redisContext *c, const struct timeval tv); +int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); +int redisKeepAlive(redisContext *c, int interval); + +#endif diff --git a/data-server/external/hiredis/sds.c b/data-server/external/hiredis/sds.c new file mode 100755 index 0000000000..9226799fcf --- /dev/null +++ b/data-server/external/hiredis/sds.c @@ -0,0 +1,606 @@ +/* SDSLib, A C dynamic strings library + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include "sds.h" + +#ifdef SDS_ABORT_ON_OOM +static void sdsOomAbort(void) { + fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n"); + abort(); +} +#endif + +sds sdsnewlen(const void *init, size_t initlen) { + struct sdshdr *sh; + + sh = malloc(sizeof(struct sdshdr)+initlen+1); +#ifdef SDS_ABORT_ON_OOM + if (sh == NULL) sdsOomAbort(); +#else + if (sh == NULL) return NULL; +#endif + sh->len = initlen; + sh->free = 0; + if (initlen) { + if (init) memcpy(sh->buf, init, initlen); + else memset(sh->buf,0,initlen); + } + sh->buf[initlen] = '\0'; + return (char*)sh->buf; +} + +sds sdsempty(void) { + return sdsnewlen("",0); +} + +sds sdsnew(const char *init) { + size_t initlen = (init == NULL) ? 0 : strlen(init); + return sdsnewlen(init, initlen); +} + +sds sdsdup(const sds s) { + return sdsnewlen(s, sdslen(s)); +} + +void sdsfree(sds s) { + if (s == NULL) return; + free(s-sizeof(struct sdshdr)); +} + +void sdsupdatelen(sds s) { + struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); + int reallen = strlen(s); + sh->free += (sh->len-reallen); + sh->len = reallen; +} + +static sds sdsMakeRoomFor(sds s, size_t addlen) { + struct sdshdr *sh, *newsh; + size_t free = sdsavail(s); + size_t len, newlen; + + if (free >= addlen) return s; + len = sdslen(s); + sh = (void*) (s-(sizeof(struct sdshdr))); + newlen = (len+addlen)*2; + newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1); +#ifdef SDS_ABORT_ON_OOM + if (newsh == NULL) sdsOomAbort(); +#else + if (newsh == NULL) return NULL; +#endif + + newsh->free = newlen - len; + return newsh->buf; +} + +/* Grow the sds to have the specified length. Bytes that were not part of + * the original length of the sds will be set to zero. */ +sds sdsgrowzero(sds s, size_t len) { + struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); + size_t totlen, curlen = sh->len; + + if (len <= curlen) return s; + s = sdsMakeRoomFor(s,len-curlen); + if (s == NULL) return NULL; + + /* Make sure added region doesn't contain garbage */ + sh = (void*)(s-(sizeof(struct sdshdr))); + memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ + totlen = sh->len+sh->free; + sh->len = len; + sh->free = totlen-sh->len; + return s; +} + +sds sdscatlen(sds s, const void *t, size_t len) { + struct sdshdr *sh; + size_t curlen = sdslen(s); + + s = sdsMakeRoomFor(s,len); + if (s == NULL) return NULL; + sh = (void*) (s-(sizeof(struct sdshdr))); + memcpy(s+curlen, t, len); + sh->len = curlen+len; + sh->free = sh->free-len; + s[curlen+len] = '\0'; + return s; +} + +sds sdscat(sds s, const char *t) { + return sdscatlen(s, t, strlen(t)); +} + +sds sdscpylen(sds s, char *t, size_t len) { + struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); + size_t totlen = sh->free+sh->len; + + if (totlen < len) { + s = sdsMakeRoomFor(s,len-sh->len); + if (s == NULL) return NULL; + sh = (void*) (s-(sizeof(struct sdshdr))); + totlen = sh->free+sh->len; + } + memcpy(s, t, len); + s[len] = '\0'; + sh->len = len; + sh->free = totlen-len; + return s; +} + +sds sdscpy(sds s, char *t) { + return sdscpylen(s, t, strlen(t)); +} + +sds sdscatvprintf(sds s, const char *fmt, va_list ap) { + va_list cpy; + char *buf, *t; + size_t buflen = 16; + + while(1) { + buf = malloc(buflen); +#ifdef SDS_ABORT_ON_OOM + if (buf == NULL) sdsOomAbort(); +#else + if (buf == NULL) return NULL; +#endif + buf[buflen-2] = '\0'; + va_copy(cpy,ap); + vsnprintf(buf, buflen, fmt, cpy); + va_end(cpy); + if (buf[buflen-2] != '\0') { + free(buf); + buflen *= 2; + continue; + } + break; + } + t = sdscat(s, buf); + free(buf); + return t; +} + +sds sdscatprintf(sds s, const char *fmt, ...) { + va_list ap; + char *t; + va_start(ap, fmt); + t = sdscatvprintf(s,fmt,ap); + va_end(ap); + return t; +} + +sds sdstrim(sds s, const char *cset) { + struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); + char *start, *end, *sp, *ep; + size_t len; + + sp = start = s; + ep = end = s+sdslen(s)-1; + while(sp <= end && strchr(cset, *sp)) sp++; + while(ep > start && strchr(cset, *ep)) ep--; + len = (sp > ep) ? 0 : ((ep-sp)+1); + if (sh->buf != sp) memmove(sh->buf, sp, len); + sh->buf[len] = '\0'; + sh->free = sh->free+(sh->len-len); + sh->len = len; + return s; +} + +sds sdsrange(sds s, int start, int end) { + struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); + size_t newlen, len = sdslen(s); + + if (len == 0) return s; + if (start < 0) { + start = len+start; + if (start < 0) start = 0; + } + if (end < 0) { + end = len+end; + if (end < 0) end = 0; + } + newlen = (start > end) ? 0 : (end-start)+1; + if (newlen != 0) { + if (start >= (signed)len) { + newlen = 0; + } else if (end >= (signed)len) { + end = len-1; + newlen = (start > end) ? 0 : (end-start)+1; + } + } else { + start = 0; + } + if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); + sh->buf[newlen] = 0; + sh->free = sh->free+(sh->len-newlen); + sh->len = newlen; + return s; +} + +void sdstolower(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = tolower(s[j]); +} + +void sdstoupper(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = toupper(s[j]); +} + +int sdscmp(sds s1, sds s2) { + size_t l1, l2, minlen; + int cmp; + + l1 = sdslen(s1); + l2 = sdslen(s2); + minlen = (l1 < l2) ? l1 : l2; + cmp = memcmp(s1,s2,minlen); + if (cmp == 0) return l1-l2; + return cmp; +} + +/* Split 's' with separator in 'sep'. An array + * of sds strings is returned. *count will be set + * by reference to the number of tokens returned. + * + * On out of memory, zero length string, zero length + * separator, NULL is returned. + * + * Note that 'sep' is able to split a string using + * a multi-character separator. For example + * sdssplit("foo_-_bar","_-_"); will return two + * elements "foo" and "bar". + * + * This version of the function is binary-safe but + * requires length arguments. sdssplit() is just the + * same function but for zero-terminated strings. + */ +sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) { + int elements = 0, slots = 5, start = 0, j; + + sds *tokens = malloc(sizeof(sds)*slots); +#ifdef SDS_ABORT_ON_OOM + if (tokens == NULL) sdsOomAbort(); +#endif + if (seplen < 1 || len < 0 || tokens == NULL) return NULL; + if (len == 0) { + *count = 0; + return tokens; + } + for (j = 0; j < (len-(seplen-1)); j++) { + /* make sure there is room for the next element and the final one */ + if (slots < elements+2) { + sds *newtokens; + + slots *= 2; + newtokens = realloc(tokens,sizeof(sds)*slots); + if (newtokens == NULL) { +#ifdef SDS_ABORT_ON_OOM + sdsOomAbort(); +#else + goto cleanup; +#endif + } + tokens = newtokens; + } + /* search the separator */ + if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { + tokens[elements] = sdsnewlen(s+start,j-start); + if (tokens[elements] == NULL) { +#ifdef SDS_ABORT_ON_OOM + sdsOomAbort(); +#else + goto cleanup; +#endif + } + elements++; + start = j+seplen; + j = j+seplen-1; /* skip the separator */ + } + } + /* Add the final element. We are sure there is room in the tokens array. */ + tokens[elements] = sdsnewlen(s+start,len-start); + if (tokens[elements] == NULL) { +#ifdef SDS_ABORT_ON_OOM + sdsOomAbort(); +#else + goto cleanup; +#endif + } + elements++; + *count = elements; + return tokens; + +#ifndef SDS_ABORT_ON_OOM +cleanup: + { + int i; + for (i = 0; i < elements; i++) sdsfree(tokens[i]); + free(tokens); + return NULL; + } +#endif +} + +void sdsfreesplitres(sds *tokens, int count) { + if (!tokens) return; + while(count--) + sdsfree(tokens[count]); + free(tokens); +} + +sds sdsfromlonglong(long long value) { + char buf[32], *p; + unsigned long long v; + + v = (value < 0) ? -value : value; + p = buf+31; /* point to the last character */ + do { + *p-- = '0'+(v%10); + v /= 10; + } while(v); + if (value < 0) *p-- = '-'; + p++; + return sdsnewlen(p,32-(p-buf)); +} + +sds sdscatrepr(sds s, char *p, size_t len) { + s = sdscatlen(s,"\"",1); + if (s == NULL) return NULL; + + while(len--) { + switch(*p) { + case '\\': + case '"': + s = sdscatprintf(s,"\\%c",*p); + break; + case '\n': s = sdscatlen(s,"\\n",2); break; + case '\r': s = sdscatlen(s,"\\r",2); break; + case '\t': s = sdscatlen(s,"\\t",2); break; + case '\a': s = sdscatlen(s,"\\a",2); break; + case '\b': s = sdscatlen(s,"\\b",2); break; + default: + if (isprint(*p)) + s = sdscatprintf(s,"%c",*p); + else + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); + break; + } + p++; + if (s == NULL) return NULL; + } + return sdscatlen(s,"\"",1); +} + +/* Split a line into arguments, where every argument can be in the + * following programming-language REPL-alike form: + * + * foo bar "newline are supported\n" and "\xff\x00otherstuff" + * + * The number of arguments is stored into *argc, and an array + * of sds is returned. The caller should sdsfree() all the returned + * strings and finally free() the array itself. + * + * Note that sdscatrepr() is able to convert back a string into + * a quoted string in the same format sdssplitargs() is able to parse. + */ +sds *sdssplitargs(char *line, int *argc) { + char *p = line; + char *current = NULL; + char **vector = NULL, **_vector = NULL; + + *argc = 0; + while(1) { + /* skip blanks */ + while(*p && isspace(*p)) p++; + if (*p) { + /* get a token */ + int inq=0; /* set to 1 if we are in "quotes" */ + int done=0; + + if (current == NULL) { + current = sdsempty(); + if (current == NULL) goto err; + } + + while(!done) { + if (inq) { + if (*p == '\\' && *(p+1)) { + char c; + + p++; + switch(*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'b': c = '\b'; break; + case 'a': c = '\a'; break; + default: c = *p; break; + } + current = sdscatlen(current,&c,1); + } else if (*p == '"') { + /* closing quote must be followed by a space */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else { + switch(*p) { + case ' ': + case '\n': + case '\r': + case '\t': + case '\0': + done=1; + break; + case '"': + inq=1; + break; + default: + current = sdscatlen(current,p,1); + break; + } + } + if (*p) p++; + if (current == NULL) goto err; + } + /* add the token to the vector */ + _vector = realloc(vector,((*argc)+1)*sizeof(char*)); + if (_vector == NULL) goto err; + + vector = _vector; + vector[*argc] = current; + (*argc)++; + current = NULL; + } else { + return vector; + } + } + +err: + while((*argc)--) + sdsfree(vector[*argc]); + if (vector != NULL) free(vector); + if (current != NULL) sdsfree(current); + return NULL; +} + +#ifdef SDS_TEST_MAIN +#include + +int __failed_tests = 0; +int __test_num = 0; +#define test_cond(descr,_c) do { \ + __test_num++; printf("%d - %s: ", __test_num, descr); \ + if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \ +} while(0); +#define test_report() do { \ + printf("%d tests, %d passed, %d failed\n", __test_num, \ + __test_num-__failed_tests, __failed_tests); \ + if (__failed_tests) { \ + printf("=== WARNING === We have failed tests here...\n"); \ + } \ +} while(0); + +int main(void) { + { + sds x = sdsnew("foo"), y; + + test_cond("Create a string and obtain the length", + sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) + + sdsfree(x); + x = sdsnewlen("foo",2); + test_cond("Create a string with specified length", + sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) + + x = sdscat(x,"bar"); + test_cond("Strings concatenation", + sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); + + x = sdscpy(x,"a"); + test_cond("sdscpy() against an originally longer string", + sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) + + x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); + test_cond("sdscpy() against an originally shorter string", + sdslen(x) == 33 && + memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) + + sdsfree(x); + x = sdscatprintf(sdsempty(),"%d",123); + test_cond("sdscatprintf() seems working in the base case", + sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) + + sdsfree(x); + x = sdstrim(sdsnew("xxciaoyyy"),"xy"); + test_cond("sdstrim() correctly trims characters", + sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) + + y = sdsrange(sdsdup(x),1,1); + test_cond("sdsrange(...,1,1)", + sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) + + sdsfree(y); + y = sdsrange(sdsdup(x),1,-1); + test_cond("sdsrange(...,1,-1)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsrange(sdsdup(x),-2,-1); + test_cond("sdsrange(...,-2,-1)", + sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) + + sdsfree(y); + y = sdsrange(sdsdup(x),2,1); + test_cond("sdsrange(...,2,1)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + y = sdsrange(sdsdup(x),1,100); + test_cond("sdsrange(...,1,100)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsrange(sdsdup(x),100,100); + test_cond("sdsrange(...,100,100)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("foo"); + y = sdsnew("foa"); + test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("bar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("aar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) + } + test_report() +} +#endif diff --git a/data-server/external/hiredis/sds.h b/data-server/external/hiredis/sds.h new file mode 100755 index 0000000000..94f5871f54 --- /dev/null +++ b/data-server/external/hiredis/sds.h @@ -0,0 +1,88 @@ +/* SDSLib, A C dynamic strings library + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __SDS_H +#define __SDS_H + +#include +#include + +typedef char *sds; + +struct sdshdr { + int len; + int free; + char buf[]; +}; + +static inline size_t sdslen(const sds s) { + struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); + return sh->len; +} + +static inline size_t sdsavail(const sds s) { + struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); + return sh->free; +} + +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +size_t sdslen(const sds s); +sds sdsdup(const sds s); +void sdsfree(sds s); +size_t sdsavail(sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscpylen(sds s, char *t, size_t len); +sds sdscpy(sds s, char *t); + +sds sdscatvprintf(sds s, const char *fmt, va_list ap); +#ifdef __GNUC__ +sds sdscatprintf(sds s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +sds sdscatprintf(sds s, const char *fmt, ...); +#endif + +sds sdstrim(sds s, const char *cset); +sds sdsrange(sds s, int start, int end); +void sdsupdatelen(sds s); +int sdscmp(sds s1, sds s2); +sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, char *p, size_t len); +sds *sdssplitargs(char *line, int *argc); + +#endif diff --git a/data-server/src/DataServer.cpp b/data-server/src/DataServer.cpp new file mode 100644 index 0000000000..7dbfdd2c07 --- /dev/null +++ b/data-server/src/DataServer.cpp @@ -0,0 +1,194 @@ +// +// DataServer.cpp +// hifi +// +// Created by Stephen Birarda on 1/20/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include + +#include +#include + +#include "DataServer.h" + +const quint16 DATA_SERVER_LISTEN_PORT = 3282; + +const char REDIS_HOSTNAME[] = "127.0.0.1"; +const unsigned short REDIS_PORT = 6379; + +DataServer::DataServer(int argc, char* argv[]) : + QCoreApplication(argc, argv), + _socket(), + _redis(NULL) +{ + _socket.bind(QHostAddress::Any, DATA_SERVER_LISTEN_PORT); + + connect(&_socket, &QUdpSocket::readyRead, this, &DataServer::readPendingDatagrams); + + _redis = redisConnect(REDIS_HOSTNAME, REDIS_PORT); + + if (_redis && _redis->err) { + if (_redis) { + qDebug() << "Redis connection error:" << _redis->errstr; + } else { + qDebug("Redis connection error - can't allocate redis context."); + } + + // couldn't connect to redis, bail out + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + } +} + +DataServer::~DataServer() { + // disconnect from redis and free the context + if (_redis) { + redisFree(_redis); + } +} + +const int MAX_PACKET_SIZE = 1500; + +void DataServer::readPendingDatagrams() { + qint64 receivedBytes = 0; + static unsigned char packetData[MAX_PACKET_SIZE]; + + QHostAddress senderAddress; + quint16 senderPort; + + while (_socket.hasPendingDatagrams() && + (receivedBytes = _socket.readDatagram(reinterpret_cast(packetData), MAX_PACKET_SIZE, + &senderAddress, &senderPort))) { + if ((packetData[0] == PACKET_TYPE_DATA_SERVER_PUT || packetData[0] == PACKET_TYPE_DATA_SERVER_GET) && + packetVersionMatch(packetData)) { + + int readBytes = numBytesForPacketHeader(packetData); + + // pull the UUID that we will need as part of the key + QString uuidString(reinterpret_cast(packetData + readBytes)); + QUuid parsedUUID(uuidString); + + if (parsedUUID.isNull()) { + // we failed to parse a UUID, this means the user has sent us a username + + QString username(reinterpret_cast(packetData + readBytes)); + readBytes += sizeof(username) + sizeof('\0'); + + + // ask redis for the UUID for this user + redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(username)); + + if (reply->type == REDIS_REPLY_STRING) { + + parsedUUID = QUuid(QString(reply->str)); + } + + if (!parsedUUID.isNull()) { + qDebug() << "Found UUID" << parsedUUID << "for username" << username; + } else { + qDebug() << "Failed UUID lookup for username" << username; + } + + freeReplyObject(reply); + reply = NULL; + } else { + readBytes += uuidString.size() + sizeof('\0'); + } + + if (!parsedUUID.isNull()) { + // pull the number of keys the user has sent + unsigned char numKeys = packetData[readBytes++]; + + if (packetData[0] == PACKET_TYPE_DATA_SERVER_PUT) { + + // pull the key that specifies the data the user is putting/getting + QString dataKey(reinterpret_cast(packetData + readBytes)); + readBytes += dataKey.size() + sizeof('\0'); + + // grab the string value the user wants us to put, null terminate it + QString dataValue(reinterpret_cast(packetData + readBytes)); + readBytes += dataValue.size() + sizeof('\0'); + + qDebug("Sending command to redis: SET uuid:%s:%s %s\n", + qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)), + qPrintable(dataKey), qPrintable(dataValue)); + + redisReply* reply = (redisReply*) redisCommand(_redis, "SET uuid:%s:%s %s", + qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)), + qPrintable(dataKey), qPrintable(dataValue)); + + if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) { + // if redis stored the value successfully reply back with a confirm + + // which is the sent packet with the header replaced + packetData[0] = PACKET_TYPE_DATA_SERVER_CONFIRM; + _socket.writeDatagram(reinterpret_cast(packetData), receivedBytes, + senderAddress, senderPort); + } + + freeReplyObject(reply); + } else { + // setup a send packet with the returned data + // leverage the packetData sent by overwriting and appending + int numSendPacketBytes = 0; + + numSendPacketBytes += populateTypeAndVersion(packetData, PACKET_TYPE_DATA_SERVER_SEND); + + const char MULTI_KEY_VALUE_SEPARATOR = '|'; + + if (strcmp((char*) packetData + readBytes, "uuid") != 0) { + + // the user has sent one or more keys - make the associated requests + + for (int j = 0; j < numKeys; j++) { + + // pull the key that specifies the data the user is putting/getting, null terminate it + int numDataKeyBytes = 0; + + // look for the key separator or the null terminator + while (packetData[readBytes + numDataKeyBytes] != MULTI_KEY_VALUE_SEPARATOR + && packetData[readBytes + numDataKeyBytes] != '\0') { + numDataKeyBytes++; + } + + QString dataKey(QByteArray(reinterpret_cast(packetData + readBytes), numDataKeyBytes)); + readBytes += dataKey.size() + sizeof('\0'); + + qDebug("Sending command to redis: GET uuid:%s:%s\n", + qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)), + qPrintable(dataKey)); + redisReply* reply = (redisReply*) redisCommand(_redis, "GET uuid:%s:%s", + qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)), + qPrintable(dataKey)); + + if (reply->len) { + // copy the value that redis returned + memcpy(packetData + numSendPacketBytes, reply->str, reply->len); + numSendPacketBytes += reply->len; + + } else { + // didn't find a value - insert a space + packetData[numSendPacketBytes++] = ' '; + } + + // add the multi-value separator + packetData[numSendPacketBytes++] = MULTI_KEY_VALUE_SEPARATOR; + + freeReplyObject(reply); + } + } else { + // user is asking for a UUID matching username, copy the UUID we found + QString uuidString = uuidStringWithoutCurlyBraces(parsedUUID); + memcpy(packetData + numSendPacketBytes, uuidString.constData(), uuidString.size() + sizeof('\0')); + numSendPacketBytes = uuidString.size() + sizeof('\0'); + } + + // reply back with the send packet + _socket.writeDatagram(reinterpret_cast(packetData), numSendPacketBytes, + senderAddress, senderPort); + } + } + } + } +} \ No newline at end of file diff --git a/data-server/src/DataServer.h b/data-server/src/DataServer.h new file mode 100644 index 0000000000..097f6a6533 --- /dev/null +++ b/data-server/src/DataServer.h @@ -0,0 +1,29 @@ +// +// DataServer.h +// hifi +// +// Created by Stephen Birarda on 1/20/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__DataServer__ +#define __hifi__DataServer__ + +#include +#include + +#include + +class DataServer : public QCoreApplication { + Q_OBJECT +public: + DataServer(int argc, char* argv[]); + ~DataServer(); +private: + QUdpSocket _socket; + redisContext* _redis; +private slots: + void readPendingDatagrams(); +}; + +#endif /* defined(__hifi__DataServer__) */ diff --git a/data-server/src/main.cpp b/data-server/src/main.cpp new file mode 100755 index 0000000000..8e71789032 --- /dev/null +++ b/data-server/src/main.cpp @@ -0,0 +1,24 @@ +// +// main.cpp +// data-server +// +// Created by Stephen Birarda on 1/1/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include + +#include + +#include "DataServer.h" + +int main(int argc, char* argv[]) { + + setvbuf(stdout, NULL, _IOLBF, 0); + + qInstallMessageHandler(Logging::verboseMessageHandler); + + DataServer dataServer(argc, argv); + + return dataServer.exec(); +} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 229cedf55c..4b8343e72f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1446,13 +1446,9 @@ void Application::processAvatarURLsMessage(unsigned char* packetData, size_t dat // Tell the other avatars that your dataserver data has // changed. - //QDataStream in(QByteArray((char*)packetData, dataBytes)); - //QUrl voxelURL; - //in >> voxelURL; - // use this timing to as the data-server for an updated mesh for this avatar (if we have UUID) DataServerClient::getValuesForKeysAndUUID(QStringList() << DataServerKey::FaceMeshURL << DataServerKey::SkeletonURL, - avatar->getUUID()); + avatar->getUUID(), &_profile); } void Application::checkBandwidthMeterClick() { @@ -1858,8 +1854,8 @@ void Application::init() { if (!_profile.getUsername().isEmpty()) { // we have a username for this avatar, ask the data-server for the mesh URL for this avatar - DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL); - DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL); + DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL, &_profile); + DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL, &_profile); } // Set up VoxelSystem after loading preferences so we can get the desired max voxel count @@ -3960,12 +3956,14 @@ void Application::attachNewHeadToNode(Node* newNode) { void Application::updateWindowTitle(){ QString title = ""; QString buildVersion = " (build " + QString::number(BUILD_VERSION) + ")"; + QString username = _profile.getUsername(); if(!username.isEmpty()){ - title += _profile.getUsername(); + title += username; title += " @ "; } - title += _profile.getLastDomain(); + + title += NodeList::getInstance()->getDomainHostname(); title += buildVersion; qDebug("Application title set to: %s", title.toStdString().c_str()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 7e78cced4c..45edc25b8e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -256,7 +256,7 @@ private: void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true); static bool sendVoxelsOperation(OctreeElement* node, void* extraData); - static void processAvatarURLsMessage(unsigned char* packetData, size_t dataBytes); + void processAvatarURLsMessage(unsigned char* packetData, size_t dataBytes); static void sendPingPackets(); void initDisplay(); diff --git a/interface/src/DataServerClient.cpp b/interface/src/DataServerClient.cpp index 45bdd6f6a9..af76d8eea2 100644 --- a/interface/src/DataServerClient.cpp +++ b/interface/src/DataServerClient.cpp @@ -13,36 +13,40 @@ #include #include -#include "Application.h" #include "avatar/Profile.h" #include "DataServerClient.h" -std::map DataServerClient::_unmatchedPackets; +QMap DataServerClient::_unmatchedPackets; +QMap DataServerClient::_callbackObjects; +QString DataServerClient::_clientIdentifier; +quint8 DataServerClient::_sequenceNumber = 0; const char MULTI_KEY_VALUE_SEPARATOR = '|'; const char DATA_SERVER_HOSTNAME[] = "data.highfidelity.io"; const unsigned short DATA_SERVER_PORT = 3282; - const HifiSockAddr& DataServerClient::dataServerSockAddr() { static HifiSockAddr dsSockAddr = HifiSockAddr(DATA_SERVER_HOSTNAME, DATA_SERVER_PORT); return dsSockAddr; } void DataServerClient::putValueForKey(const QString& key, const char* value) { - QString clientString = Application::getInstance()->getProfile()->getUserString(); - if (!clientString.isEmpty()) { - - unsigned char* putPacket = new unsigned char[MAX_PACKET_SIZE]; - + if (!_clientIdentifier.isEmpty()) { + + static unsigned char putPacket[MAX_PACKET_SIZE]; + // setup the header for this packet int numPacketBytes = populateTypeAndVersion(putPacket, PACKET_TYPE_DATA_SERVER_PUT); - + + // pack the sequence number + memcpy(putPacket + numPacketBytes, &_sequenceNumber, sizeof(_sequenceNumber)); + numPacketBytes += sizeof(_sequenceNumber); + // pack the client UUID, null terminated - memcpy(putPacket + numPacketBytes, clientString.toLocal8Bit().constData(), clientString.toLocal8Bit().size()); - numPacketBytes += clientString.toLocal8Bit().size(); + memcpy(putPacket + numPacketBytes, qPrintable(_clientIdentifier), _clientIdentifier.toLocal8Bit().size()); + numPacketBytes += _clientIdentifier.toLocal8Bit().size(); putPacket[numPacketBytes++] = '\0'; // pack a 1 to designate that we are putting a single value @@ -59,31 +63,39 @@ void DataServerClient::putValueForKey(const QString& key, const char* value) { putPacket[numPacketBytes++] = '\0'; // add the putPacket to our vector of unconfirmed packets, will be deleted once put is confirmed - // _unmatchedPackets.insert(std::pair(putPacket, numPacketBytes)); - + _unmatchedPackets.insert(_sequenceNumber, QByteArray((char*) putPacket, numPacketBytes)); + // send this put request to the data server NodeList::getInstance()->getNodeSocket().writeDatagram((char*) putPacket, numPacketBytes, dataServerSockAddr().getAddress(), dataServerSockAddr().getPort()); + + // push the sequence number forwards + _sequenceNumber++; } } -void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uuid) { - getValuesForKeysAndUUID(QStringList(key), uuid); +void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uuid, DataServerCallbackObject* callbackObject) { + getValuesForKeysAndUUID(QStringList(key), uuid, callbackObject); } -void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid) { +void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject) { if (!uuid.isNull()) { - getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid)); + getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid), callbackObject); } } -void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString) { +void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, + DataServerCallbackObject* callbackObject) { if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) { - unsigned char* getPacket = new unsigned char[MAX_PACKET_SIZE]; - + static unsigned char getPacket[MAX_PACKET_SIZE]; + // setup the header for this packet int numPacketBytes = populateTypeAndVersion(getPacket, PACKET_TYPE_DATA_SERVER_GET); + + // pack the sequence number + memcpy(getPacket + numPacketBytes, &_sequenceNumber, sizeof(_sequenceNumber)); + numPacketBytes += sizeof(_sequenceNumber); // pack the user string (could be username or UUID string), null-terminate memcpy(getPacket + numPacketBytes, userString.toLocal8Bit().constData(), userString.toLocal8Bit().size()); @@ -99,121 +111,133 @@ void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, co strcpy((char*) getPacket + numPacketBytes, keyString.toLocal8Bit().constData()); numPacketBytes += keyString.size() + sizeof('\0'); - // add the getPacket to our vector of uncofirmed packets, will be deleted once we get a response from the nameserver - // _unmatchedPackets.insert(std::pair(getPacket, numPacketBytes)); - + // add the getPacket to our map of unconfirmed packets, will be deleted once we get a response from the nameserver + _unmatchedPackets.insert(_sequenceNumber, QByteArray((char*) getPacket, numPacketBytes)); + _callbackObjects.insert(_sequenceNumber, callbackObject); + // send the get to the data server NodeList::getInstance()->getNodeSocket().writeDatagram((char*) getPacket, numPacketBytes, dataServerSockAddr().getAddress(), dataServerSockAddr().getPort()); + + _sequenceNumber++; } } -void DataServerClient::getClientValueForKey(const QString& key) { - getValuesForKeysAndUserString(QStringList(key), Application::getInstance()->getProfile()->getUserString()); +void DataServerClient::getClientValueForKey(const QString& key, DataServerCallbackObject* callbackObject) { + if (!_clientIdentifier.isEmpty()) { + getValuesForKeysAndUserString(QStringList(key), _clientIdentifier, callbackObject); + } else { + qDebug() << "There is no client identifier set for the DataServerClient."; + } } -void DataServerClient::processConfirmFromDataServer(unsigned char* packetData, int numPacketBytes) { - removeMatchedPacketFromMap(packetData, numPacketBytes); +void DataServerClient::processConfirmFromDataServer(unsigned char* packetData) { + removeMatchedPacketFromMap(packetData); } void DataServerClient::processSendFromDataServer(unsigned char* packetData, int numPacketBytes) { // pull the user string from the packet so we know who to associate this with int numHeaderBytes = numBytesForPacketHeader(packetData); - - char* userStringPosition = (char*) packetData + numHeaderBytes; - - QString userString(QByteArray(userStringPosition, strlen(userStringPosition))); - - QUuid userUUID(userString); - - char* keysPosition = (char*) packetData + numHeaderBytes + strlen(userStringPosition) + + _sequenceNumber = *(packetData + numHeaderBytes); + + if (_callbackObjects.find(_sequenceNumber) != _callbackObjects.end()) { + DataServerCallbackObject* callbackObject = _callbackObjects.take(_sequenceNumber); + + char* userStringPosition = (char*) packetData + numHeaderBytes + sizeof(_sequenceNumber); + + QString userString(QByteArray(userStringPosition, strlen(userStringPosition))); + + QUuid userUUID(userString); + + char* keysPosition = (char*) packetData + numHeaderBytes + strlen(userStringPosition) + sizeof('\0') + sizeof(unsigned char); - char* valuesPosition = keysPosition + strlen(keysPosition) + sizeof('\0'); - - QStringList keyList = QString(keysPosition).split(MULTI_KEY_VALUE_SEPARATOR); - QStringList valueList = QString(valuesPosition).split(MULTI_KEY_VALUE_SEPARATOR); - - // user string was UUID, find matching avatar and associate data - for (int i = 0; i < keyList.size(); i++) { - if (valueList[i] != " ") { - if (keyList[i] == DataServerKey::FaceMeshURL) { - - if (userUUID.isNull() || userUUID == Application::getInstance()->getProfile()->getUUID()) { - qDebug("Changing user's face model URL to %s", valueList[i].toLocal8Bit().constData()); - Application::getInstance()->getProfile()->setFaceModelURL(QUrl(valueList[i])); - } else { - // mesh URL for a UUID, find avatar in our list - - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { - if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { - Avatar* avatar = (Avatar *) node->getLinkedData(); - - if (avatar->getUUID() == userUUID) { - QMetaObject::invokeMethod(&avatar->getHead().getFaceModel(), - "setURL", Q_ARG(QUrl, QUrl(valueList[i]))); - } - } - } - } - } else if (keyList[i] == DataServerKey::SkeletonURL) { - - if (userUUID.isNull() || userUUID == Application::getInstance()->getProfile()->getUUID()) { - qDebug("Changing user's skeleton URL to %s", valueList[i].toLocal8Bit().constData()); - Application::getInstance()->getProfile()->setSkeletonModelURL(QUrl(valueList[i])); - } else { - // skeleton URL for a UUID, find avatar in our list - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { - if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { - Avatar* avatar = (Avatar *) node->getLinkedData(); - - if (avatar->getUUID() == userUUID) { - QMetaObject::invokeMethod(&avatar->getSkeletonModel(), "setURL", - Q_ARG(QUrl, QUrl(valueList[i]))); - } - } - } - } - } else if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position && - keyList[i + 2] == DataServerKey::Orientation && valueList[i] != " " && - valueList[i + 1] != " " && valueList[i + 2] != " ") { - - QStringList coordinateItems = valueList[i + 1].split(','); - QStringList orientationItems = valueList[i + 2].split(','); - - if (coordinateItems.size() == 3 && orientationItems.size() == 3) { - - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - NodeList::getInstance()->sendKillNode(&NODE_TYPE_AVATAR_MIXER, 1); - - qDebug() << "Changing domain to" << valueList[i].toLocal8Bit().constData() << - ", position to" << valueList[i + 1].toLocal8Bit().constData() << - ", and orientation to" << valueList[i + 2].toLocal8Bit().constData() << - "to go to" << userString; - - NodeList::getInstance()->setDomainHostname(valueList[i]); - // orient the user to face the target - glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), - orientationItems[1].toFloat(), orientationItems[2].toFloat()))) * - glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f); - Application::getInstance()->getAvatar()->setOrientation(newOrientation); - - // move the user a couple units away - const float DISTANCE_TO_USER = 2.0f; - glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), - coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; - Application::getInstance()->getAvatar()->setPosition(newPosition); - } - - } else if (keyList[i] == DataServerKey::UUID) { - // this is the user's UUID - set it on the profile - Application::getInstance()->getProfile()->setUUID(valueList[i]); - } - } + char* valuesPosition = keysPosition + strlen(keysPosition) + sizeof('\0'); + + QStringList keyList = QString(keysPosition).split(MULTI_KEY_VALUE_SEPARATOR); + QStringList valueList = QString(valuesPosition).split(MULTI_KEY_VALUE_SEPARATOR); + + callbackObject->processDataServerResponse(userUUID, keyList, valueList); } - // remove the matched packet from our map so it isn't re-sent to the data-server - // removeMatchedPacketFromMap(packetData, numPacketBytes); + + // user string was UUID, find matching avatar and associate data +// for (int i = 0; i < keyList.size(); i++) { +// if (valueList[i] != " ") { +// if (keyList[i] == DataServerKey::FaceMeshURL) { + +// if (userUUID.isNull() || userUUID == Application::getInstance()->getProfile()->getUUID()) { +// qDebug("Changing user's face model URL to %s\n", valueList[i].toLocal8Bit().constData()); +// Application::getInstance()->getProfile()->setFaceModelURL(QUrl(valueList[i])); +// } else { +// // mesh URL for a UUID, find avatar in our list +// +// foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { +// if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { +// Avatar* avatar = (Avatar *) node->getLinkedData(); +// +// if (avatar->getUUID() == userUUID) { +// QMetaObject::invokeMethod(&avatar->getHead().getFaceModel(), +// "setURL", Q_ARG(QUrl, QUrl(valueList[i]))); +// } +// } +// } +// } +// } else if (keyList[i] == DataServerKey::SkeletonURL) { + +// if (userUUID.isNull() || userUUID == Application::getInstance()->getProfile()->getUUID()) { +// qDebug("Changing user's skeleton URL to %s\n", valueList[i].toLocal8Bit().constData()); +// Application::getInstance()->getProfile()->setSkeletonModelURL(QUrl(valueList[i])); +// } else { +// // skeleton URL for a UUID, find avatar in our list +// foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { +// if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { +// Avatar* avatar = (Avatar *) node->getLinkedData(); +// +// if (avatar->getUUID() == userUUID) { +// QMetaObject::invokeMethod(&avatar->getSkeletonModel(), "setURL", +// Q_ARG(QUrl, QUrl(valueList[i]))); +// } +// } +// } +// } +// } else if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position && +// keyList[i + 2] == DataServerKey::Orientation && valueList[i] != " " && +// valueList[i + 1] != " " && valueList[i + 2] != " ") { +// +// QStringList coordinateItems = valueList[i + 1].split(','); +// QStringList orientationItems = valueList[i + 2].split(','); + +// if (coordinateItems.size() == 3 && orientationItems.size() == 3) { +// +// // send a node kill request, indicating to other clients that they should play the "disappeared" effect +// NodeList::getInstance()->sendKillNode(&NODE_TYPE_AVATAR_MIXER, 1); +// +// qDebug() << "Changing domain to" << valueList[i].toLocal8Bit().constData() << +// ", position to" << valueList[i + 1].toLocal8Bit().constData() << +// ", and orientation to" << valueList[i + 2].toLocal8Bit().constData() << +// "to go to" << userString << "\n"; +// +// NodeList::getInstance()->setDomainHostname(valueList[i]); +// +// // orient the user to face the target +// glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), +// orientationItems[1].toFloat(), orientationItems[2].toFloat()))) * +// glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f); +// Application::getInstance()->getAvatar()->setOrientation(newOrientation); +// +// // move the user a couple units away +// const float DISTANCE_TO_USER = 2.0f; +// glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), +// coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; +// Application::getInstance()->getAvatar()->setPosition(newPosition); +// } + +// } else if (keyList[i] == DataServerKey::UUID) { + // this is the user's UUID - set it on the profile +// Application::getInstance()->getProfile()->setUUID(valueList[i]); + } void DataServerClient::processMessageFromDataServer(unsigned char* packetData, int numPacketBytes) { @@ -222,38 +246,24 @@ void DataServerClient::processMessageFromDataServer(unsigned char* packetData, i processSendFromDataServer(packetData, numPacketBytes); break; case PACKET_TYPE_DATA_SERVER_CONFIRM: - processConfirmFromDataServer(packetData, numPacketBytes); + processConfirmFromDataServer(packetData); break; default: break; } } -void DataServerClient::removeMatchedPacketFromMap(unsigned char* packetData, int numPacketBytes) { - for (std::map::iterator mapIterator = _unmatchedPackets.begin(); - mapIterator != _unmatchedPackets.end(); - ++mapIterator) { - if (memcmp(mapIterator->first + sizeof(PACKET_TYPE), - packetData + sizeof(PACKET_TYPE), - numPacketBytes - sizeof(PACKET_TYPE)) == 0) { - - // this is a match - remove the confirmed packet from the vector and delete associated member - // so it isn't sent back out - delete[] mapIterator->first; - _unmatchedPackets.erase(mapIterator); - - // we've matched the packet - bail out - break; - } - } +void DataServerClient::removeMatchedPacketFromMap(unsigned char* packetData) { + quint8 sequenceNumber = *(packetData + numBytesForPacketHeader(packetData)); + + // attempt to remove a packet with this sequence number from the QMap of unmatched packets + _unmatchedPackets.remove(sequenceNumber); } void DataServerClient::resendUnmatchedPackets() { - for (std::map::iterator mapIterator = _unmatchedPackets.begin(); - mapIterator != _unmatchedPackets.end(); - ++mapIterator) { + foreach (const QByteArray& packet, _unmatchedPackets) { // send the unmatched packet to the data server - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) mapIterator->first, mapIterator->second, + NodeList::getInstance()->getNodeSocket().writeDatagram(packet.data(), packet.size(), dataServerSockAddr().getAddress(), dataServerSockAddr().getPort()); } diff --git a/interface/src/DataServerClient.h b/interface/src/DataServerClient.h index 89ec944eb6..0b4819d2e9 100644 --- a/interface/src/DataServerClient.h +++ b/interface/src/DataServerClient.h @@ -11,29 +11,42 @@ #include +#include +#include #include -#include "Application.h" +#include + +class DataServerCallbackObject { +public: + virtual void processDataServerResponse(const QUuid& userUUID, const QStringList& keyList, const QStringList& valueList) = 0; +}; class DataServerClient { public: static const HifiSockAddr& dataServerSockAddr(); static void putValueForKey(const QString& key, const char* value); - static void getClientValueForKey(const QString& key); + static void getClientValueForKey(const QString& key, DataServerCallbackObject* callbackObject); - static void getValueForKeyAndUUID(const QString& key, const QUuid& uuid); - static void getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid); - static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString); + static void getValueForKeyAndUUID(const QString& key, const QUuid& uuid, DataServerCallbackObject* callbackObject); + static void getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject); + static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, DataServerCallbackObject* callbackObject); - static void processConfirmFromDataServer(unsigned char* packetData, int numPacketBytes); - static void processSendFromDataServer(unsigned char* packetData, int numPacketBytes); static void processMessageFromDataServer(unsigned char* packetData, int numPacketBytes); - static void removeMatchedPacketFromMap(unsigned char* packetData, int numPacketBytes); + static void resendUnmatchedPackets(); + + static void setClientIdentifier(const QString& clientIdentifier) { _clientIdentifier = clientIdentifier; } private: + static void processConfirmFromDataServer(unsigned char* packetData); + static void processSendFromDataServer(unsigned char* packetData, int numPacketBytes); + static void removeMatchedPacketFromMap(unsigned char* packetData); - static std::map _unmatchedPackets; + static QString _clientIdentifier; + static QMap _unmatchedPackets; + static QMap _callbackObjects; + static quint8 _sequenceNumber; }; namespace DataServerKey { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7299615ecc..8c33ead8a5 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -963,7 +963,7 @@ void Menu::goToUser() { // there's a username entered by the user, make a request to the data-server DataServerClient::getValuesForKeysAndUserString( QStringList() << DataServerKey::Domain << DataServerKey::Position << DataServerKey::Orientation, - userDialog.textValue()); + userDialog.textValue(), Application::getInstance()->getProfile()); } sendFakeEnterEvent(); diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index 316ca195c9..68c38dd022 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -8,24 +8,28 @@ #include +#include #include +#include "Application.h" #include "Profile.h" -#include "DataServerClient.h" +#include "Util.h" Profile::Profile(const QString &username) : - _username(username), + _username(), _uuid(), _lastDomain(), _lastPosition(0.0, 0.0, 0.0), _lastOrientationSend(0), _faceModelURL() { - if (!_username.isEmpty()) { + if (!username.isEmpty()) { + setUsername(username); + // we've been given a new username, ask the data-server for profile - DataServerClient::getClientValueForKey(DataServerKey::UUID); - DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL); - DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL); + DataServerClient::getClientValueForKey(DataServerKey::UUID, this); + DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL, this); + DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL, this); // send our current domain server to the data-server updateDomain(NodeList::getInstance()->getDomainHostname()); @@ -43,6 +47,8 @@ QString Profile::getUserString() const { void Profile::setUUID(const QUuid& uuid) { _uuid = uuid; + DataServerClient::setClientIdentifier(_username); + // when the UUID is changed we need set it appropriately on our avatar instance Application::getInstance()->getAvatar()->setUUID(_uuid); } @@ -132,10 +138,19 @@ void Profile::saveData(QSettings* settings) { void Profile::loadData(QSettings* settings) { settings->beginGroup("Profile"); - _username = settings->value("username").toString(); + setUsername(settings->value("username").toString()); this->setUUID(settings->value("UUID").toUuid()); _faceModelURL = settings->value("faceModelURL").toUrl(); _skeletonModelURL = settings->value("skeletonModelURL").toUrl(); settings->endGroup(); } + +void Profile::processDataServerResponse(const QUuid& userUUID, const QStringList& keyList, const QStringList& valueList) { + qDebug() << "Called with" << keyList << valueList; +} + +void Profile::setUsername(const QString& username) { + _username = username; + DataServerClient::setClientIdentifier(_username); +} diff --git a/interface/src/avatar/Profile.h b/interface/src/avatar/Profile.h index 06f8de94a3..9d301157fe 100644 --- a/interface/src/avatar/Profile.h +++ b/interface/src/avatar/Profile.h @@ -18,7 +18,9 @@ #include #include -class Profile { +#include "DataServerClient.h" + +class Profile : public DataServerCallbackObject { public: Profile(const QString& username); @@ -44,7 +46,12 @@ public: void saveData(QSettings* settings); void loadData(QSettings* settings); + + void processDataServerResponse(const QUuid& userUUID, const QStringList& keyList, const QStringList& valueList); private: + + void setUsername(const QString& username); + QString _username; QUuid _uuid; QString _lastDomain; From 8930cf889b19b812238e5d0475bec4121d3c3733 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 20 Jan 2014 14:48:14 -0800 Subject: [PATCH 02/17] bump data server packet version to 1 --- libraries/shared/src/PacketHeaders.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index 0d468f0e1d..eb4d6dfed9 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -55,7 +55,13 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) { case PACKET_TYPE_PING_REPLY: return 1; - + + case PACKET_TYPE_DATA_SERVER_GET: + case PACKET_TYPE_DATA_SERVER_PUT: + case PACKET_TYPE_DATA_SERVER_SEND: + case PACKET_TYPE_DATA_SERVER_CONFIRM: + return 1; + default: return 0; } From 657589fa64361c02c56260f26def802ab7d3f758 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 20 Jan 2014 14:52:45 -0800 Subject: [PATCH 03/17] add sequence number handling to the data-server --- data-server/src/DataServer.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/data-server/src/DataServer.cpp b/data-server/src/DataServer.cpp index 7dbfdd2c07..166c0c3255 100644 --- a/data-server/src/DataServer.cpp +++ b/data-server/src/DataServer.cpp @@ -64,6 +64,10 @@ void DataServer::readPendingDatagrams() { packetVersionMatch(packetData)) { int readBytes = numBytesForPacketHeader(packetData); + + // pull the sequence number used for this packet + quint16 sequenceNumber = *reinterpret_cast(packetData + readBytes); + readBytes += sizeof(sequenceNumber); // pull the UUID that we will need as part of the key QString uuidString(reinterpret_cast(packetData + readBytes)); @@ -80,7 +84,6 @@ void DataServer::readPendingDatagrams() { redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(username)); if (reply->type == REDIS_REPLY_STRING) { - parsedUUID = QUuid(QString(reply->str)); } @@ -120,7 +123,6 @@ void DataServer::readPendingDatagrams() { if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) { // if redis stored the value successfully reply back with a confirm - // which is the sent packet with the header replaced packetData[0] = PACKET_TYPE_DATA_SERVER_CONFIRM; _socket.writeDatagram(reinterpret_cast(packetData), receivedBytes, @@ -135,6 +137,9 @@ void DataServer::readPendingDatagrams() { numSendPacketBytes += populateTypeAndVersion(packetData, PACKET_TYPE_DATA_SERVER_SEND); + memcpy(packetData + numSendPacketBytes, &sequenceNumber, sizeof(sequenceNumber)); + numSendPacketBytes += sizeof(sequenceNumber); + const char MULTI_KEY_VALUE_SEPARATOR = '|'; if (strcmp((char*) packetData + readBytes, "uuid") != 0) { From cf40fb0ef8f21f76994def1952a46c832a1f8b27 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 20 Jan 2014 16:55:14 -0800 Subject: [PATCH 04/17] repairs to new Qt'ed DataServer and DataServerClient --- data-server/src/DataServer.cpp | 27 ++++++++++++++------------- data-server/src/main.cpp | 4 ++++ interface/src/DataServerClient.cpp | 16 ++++++---------- interface/src/DataServerClient.h | 2 +- interface/src/avatar/Profile.cpp | 2 +- interface/src/avatar/Profile.h | 2 +- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/data-server/src/DataServer.cpp b/data-server/src/DataServer.cpp index 166c0c3255..2916403c97 100644 --- a/data-server/src/DataServer.cpp +++ b/data-server/src/DataServer.cpp @@ -66,7 +66,8 @@ void DataServer::readPendingDatagrams() { int readBytes = numBytesForPacketHeader(packetData); // pull the sequence number used for this packet - quint16 sequenceNumber = *reinterpret_cast(packetData + readBytes); + quint8 sequenceNumber = 0; + memcpy(&sequenceNumber, packetData + readBytes, sizeof(sequenceNumber)); readBytes += sizeof(sequenceNumber); // pull the UUID that we will need as part of the key @@ -77,9 +78,8 @@ void DataServer::readPendingDatagrams() { // we failed to parse a UUID, this means the user has sent us a username QString username(reinterpret_cast(packetData + readBytes)); - readBytes += sizeof(username) + sizeof('\0'); - - + readBytes += username.size() + sizeof('\0'); + // ask redis for the UUID for this user redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(username)); @@ -113,7 +113,7 @@ void DataServer::readPendingDatagrams() { QString dataValue(reinterpret_cast(packetData + readBytes)); readBytes += dataValue.size() + sizeof('\0'); - qDebug("Sending command to redis: SET uuid:%s:%s %s\n", + qDebug("Sending command to redis: SET uuid:%s:%s %s", qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)), qPrintable(dataKey), qPrintable(dataValue)); @@ -133,19 +133,15 @@ void DataServer::readPendingDatagrams() { } else { // setup a send packet with the returned data // leverage the packetData sent by overwriting and appending - int numSendPacketBytes = 0; + int numSendPacketBytes = receivedBytes; - numSendPacketBytes += populateTypeAndVersion(packetData, PACKET_TYPE_DATA_SERVER_SEND); - - memcpy(packetData + numSendPacketBytes, &sequenceNumber, sizeof(sequenceNumber)); - numSendPacketBytes += sizeof(sequenceNumber); + packetData[0] = PACKET_TYPE_DATA_SERVER_SEND; const char MULTI_KEY_VALUE_SEPARATOR = '|'; if (strcmp((char*) packetData + readBytes, "uuid") != 0) { // the user has sent one or more keys - make the associated requests - for (int j = 0; j < numKeys; j++) { // pull the key that specifies the data the user is putting/getting, null terminate it @@ -160,7 +156,7 @@ void DataServer::readPendingDatagrams() { QString dataKey(QByteArray(reinterpret_cast(packetData + readBytes), numDataKeyBytes)); readBytes += dataKey.size() + sizeof('\0'); - qDebug("Sending command to redis: GET uuid:%s:%s\n", + qDebug("Sending command to redis: GET uuid:%s:%s", qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)), qPrintable(dataKey)); redisReply* reply = (redisReply*) redisCommand(_redis, "GET uuid:%s:%s", @@ -182,16 +178,21 @@ void DataServer::readPendingDatagrams() { freeReplyObject(reply); } + + // null terminate the packet we're sending back (erases the trailing separator) + packetData[(numSendPacketBytes - 1)] = '\0'; } else { // user is asking for a UUID matching username, copy the UUID we found QString uuidString = uuidStringWithoutCurlyBraces(parsedUUID); memcpy(packetData + numSendPacketBytes, uuidString.constData(), uuidString.size() + sizeof('\0')); - numSendPacketBytes = uuidString.size() + sizeof('\0'); + numSendPacketBytes += uuidString.size() + sizeof('\0'); } // reply back with the send packet _socket.writeDatagram(reinterpret_cast(packetData), numSendPacketBytes, senderAddress, senderPort); + + } } } diff --git a/data-server/src/main.cpp b/data-server/src/main.cpp index 8e71789032..dc99de70b8 100755 --- a/data-server/src/main.cpp +++ b/data-server/src/main.cpp @@ -12,12 +12,16 @@ #include "DataServer.h" +const char DATA_SERVER_LOGGING_TARGET_NAME[] = "data-server"; + int main(int argc, char* argv[]) { setvbuf(stdout, NULL, _IOLBF, 0); qInstallMessageHandler(Logging::verboseMessageHandler); + Logging::setTargetName(DATA_SERVER_LOGGING_TARGET_NAME); + DataServer dataServer(argc, argv); return dataServer.exec(); diff --git a/interface/src/DataServerClient.cpp b/interface/src/DataServerClient.cpp index af76d8eea2..ee2b294440 100644 --- a/interface/src/DataServerClient.cpp +++ b/interface/src/DataServerClient.cpp @@ -24,7 +24,7 @@ quint8 DataServerClient::_sequenceNumber = 0; const char MULTI_KEY_VALUE_SEPARATOR = '|'; -const char DATA_SERVER_HOSTNAME[] = "data.highfidelity.io"; +const char DATA_SERVER_HOSTNAME[] = "localhost"; const unsigned short DATA_SERVER_PORT = 3282; const HifiSockAddr& DataServerClient::dataServerSockAddr() { @@ -35,7 +35,7 @@ const HifiSockAddr& DataServerClient::dataServerSockAddr() { void DataServerClient::putValueForKey(const QString& key, const char* value) { if (!_clientIdentifier.isEmpty()) { - static unsigned char putPacket[MAX_PACKET_SIZE]; + unsigned char putPacket[MAX_PACKET_SIZE]; // setup the header for this packet int numPacketBytes = populateTypeAndVersion(putPacket, PACKET_TYPE_DATA_SERVER_PUT); @@ -88,7 +88,7 @@ void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QU void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, DataServerCallbackObject* callbackObject) { if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) { - static unsigned char getPacket[MAX_PACKET_SIZE]; + unsigned char getPacket[MAX_PACKET_SIZE]; // setup the header for this packet int numPacketBytes = populateTypeAndVersion(getPacket, PACKET_TYPE_DATA_SERVER_GET); @@ -146,19 +146,15 @@ void DataServerClient::processSendFromDataServer(unsigned char* packetData, int DataServerCallbackObject* callbackObject = _callbackObjects.take(_sequenceNumber); char* userStringPosition = (char*) packetData + numHeaderBytes + sizeof(_sequenceNumber); + QString userString(userStringPosition); - QString userString(QByteArray(userStringPosition, strlen(userStringPosition))); - - QUuid userUUID(userString); - - char* keysPosition = (char*) packetData + numHeaderBytes + strlen(userStringPosition) - + sizeof('\0') + sizeof(unsigned char); + char* keysPosition = userStringPosition + userString.size() + sizeof('\0') + sizeof(unsigned char); char* valuesPosition = keysPosition + strlen(keysPosition) + sizeof('\0'); QStringList keyList = QString(keysPosition).split(MULTI_KEY_VALUE_SEPARATOR); QStringList valueList = QString(valuesPosition).split(MULTI_KEY_VALUE_SEPARATOR); - callbackObject->processDataServerResponse(userUUID, keyList, valueList); + callbackObject->processDataServerResponse(userString, keyList, valueList); } diff --git a/interface/src/DataServerClient.h b/interface/src/DataServerClient.h index 0b4819d2e9..a25bd3faa2 100644 --- a/interface/src/DataServerClient.h +++ b/interface/src/DataServerClient.h @@ -19,7 +19,7 @@ class DataServerCallbackObject { public: - virtual void processDataServerResponse(const QUuid& userUUID, const QStringList& keyList, const QStringList& valueList) = 0; + virtual void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList) = 0; }; class DataServerClient { diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index 68c38dd022..f455a9fdb6 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -146,7 +146,7 @@ void Profile::loadData(QSettings* settings) { settings->endGroup(); } -void Profile::processDataServerResponse(const QUuid& userUUID, const QStringList& keyList, const QStringList& valueList) { +void Profile::processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList) { qDebug() << "Called with" << keyList << valueList; } diff --git a/interface/src/avatar/Profile.h b/interface/src/avatar/Profile.h index 9d301157fe..225022b99d 100644 --- a/interface/src/avatar/Profile.h +++ b/interface/src/avatar/Profile.h @@ -47,7 +47,7 @@ public: void saveData(QSettings* settings); void loadData(QSettings* settings); - void processDataServerResponse(const QUuid& userUUID, const QStringList& keyList, const QStringList& valueList); + void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList); private: void setUsername(const QString& username); From 0ac5ec0690629672dac1129c07785fb933eb8d28 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 20 Jan 2014 17:05:31 -0800 Subject: [PATCH 05/17] hook old data server functionality through profile --- interface/src/avatar/Profile.cpp | 75 +++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index f455a9fdb6..ebe04ef476 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -147,7 +147,80 @@ void Profile::loadData(QSettings* settings) { } void Profile::processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList) { - qDebug() << "Called with" << keyList << valueList; + for (int i = 0; i < keyList.size(); i++) { + if (valueList[i] != " ") { + if (keyList[i] == DataServerKey::FaceMeshURL) { + if (userString == _username || userString == uuidStringWithoutCurlyBraces(_uuid)) { + qDebug("Changing user's face model URL to %s", valueList[i].toLocal8Bit().constData()); + Application::getInstance()->getProfile()->setFaceModelURL(QUrl(valueList[i])); + } else { + // mesh URL for a UUID, find avatar in our list + + foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { + Avatar* avatar = (Avatar *) node->getLinkedData(); + + if (avatar->getUUID() == QUuid(userString)) { + QMetaObject::invokeMethod(&avatar->getHead().getFaceModel(), + "setURL", Q_ARG(QUrl, QUrl(valueList[i]))); + } + } + } + } + } else if (keyList[i] == DataServerKey::SkeletonURL) { + if (userString == _username || userString == uuidStringWithoutCurlyBraces(_uuid)) { + qDebug("Changing user's skeleton URL to %s", valueList[i].toLocal8Bit().constData()); + Application::getInstance()->getProfile()->setSkeletonModelURL(QUrl(valueList[i])); + } else { + // skeleton URL for a UUID, find avatar in our list + foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { + Avatar* avatar = (Avatar *) node->getLinkedData(); + + if (avatar->getUUID() == QUuid(userString)) { + QMetaObject::invokeMethod(&avatar->getSkeletonModel(), "setURL", + Q_ARG(QUrl, QUrl(valueList[i]))); + } + } + } + } + } else if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position && + keyList[i + 2] == DataServerKey::Orientation && valueList[i] != " " && + valueList[i + 1] != " " && valueList[i + 2] != " ") { + + QStringList coordinateItems = valueList[i + 1].split(','); + QStringList orientationItems = valueList[i + 2].split(','); + + if (coordinateItems.size() == 3 && orientationItems.size() == 3) { + + // send a node kill request, indicating to other clients that they should play the "disappeared" effect + NodeList::getInstance()->sendKillNode(&NODE_TYPE_AVATAR_MIXER, 1); + + qDebug() << "Changing domain to" << valueList[i].toLocal8Bit().constData() << + ", position to" << valueList[i + 1].toLocal8Bit().constData() << + ", and orientation to" << valueList[i + 2].toLocal8Bit().constData() << + "to go to" << userString; + + NodeList::getInstance()->setDomainHostname(valueList[i]); + // orient the user to face the target + glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), + orientationItems[1].toFloat(), orientationItems[2].toFloat()))) * + glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f); + Application::getInstance()->getAvatar()->setOrientation(newOrientation); + + // move the user a couple units away + const float DISTANCE_TO_USER = 2.0f; + glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), + coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; + Application::getInstance()->getAvatar()->setPosition(newPosition); + } + + } else if (keyList[i] == DataServerKey::UUID) { + // this is the user's UUID - set it on the profile + _uuid = QUuid(valueList[i]); + } + } + } } void Profile::setUsername(const QString& username) { From 80fcc8743747ad7b466ffbc26b3e703e0586130a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 20 Jan 2014 17:09:03 -0800 Subject: [PATCH 06/17] some spacing fixes in Profile --- interface/src/avatar/Profile.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index ebe04ef476..d32f0f018e 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -204,14 +204,17 @@ void Profile::processDataServerResponse(const QString& userString, const QString NodeList::getInstance()->setDomainHostname(valueList[i]); // orient the user to face the target glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), - orientationItems[1].toFloat(), orientationItems[2].toFloat()))) * - glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f); - Application::getInstance()->getAvatar()->setOrientation(newOrientation); + orientationItems[1].toFloat(), + orientationItems[2].toFloat()))) * + glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f); + Application::getInstance()->getAvatar() + ->setOrientation(newOrientation); // move the user a couple units away const float DISTANCE_TO_USER = 2.0f; glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), - coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; + coordinateItems[2].toFloat() + ) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; Application::getInstance()->getAvatar()->setPosition(newPosition); } From d9ae27953bc3c006a149b667d1eb09a1ac1b1489 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 20 Jan 2014 17:12:52 -0800 Subject: [PATCH 07/17] move DataServerClient to shared for use in other targets --- .../shared}/src/DataServerClient.cpp | 94 ++----------------- .../shared}/src/DataServerClient.h | 2 +- 2 files changed, 9 insertions(+), 87 deletions(-) rename {interface => libraries/shared}/src/DataServerClient.cpp (60%) rename {interface => libraries/shared}/src/DataServerClient.h (98%) diff --git a/interface/src/DataServerClient.cpp b/libraries/shared/src/DataServerClient.cpp similarity index 60% rename from interface/src/DataServerClient.cpp rename to libraries/shared/src/DataServerClient.cpp index ee2b294440..40c75444f6 100644 --- a/interface/src/DataServerClient.cpp +++ b/libraries/shared/src/DataServerClient.cpp @@ -9,11 +9,9 @@ #include #include -#include -#include -#include - -#include "avatar/Profile.h" +#include "NodeList.h" +#include "PacketHeaders.h" +#include "UUID.h" #include "DataServerClient.h" @@ -140,12 +138,14 @@ void DataServerClient::processSendFromDataServer(unsigned char* packetData, int // pull the user string from the packet so we know who to associate this with int numHeaderBytes = numBytesForPacketHeader(packetData); - _sequenceNumber = *(packetData + numHeaderBytes); + quint8 sequenceNumber = *(packetData + numHeaderBytes); if (_callbackObjects.find(_sequenceNumber) != _callbackObjects.end()) { - DataServerCallbackObject* callbackObject = _callbackObjects.take(_sequenceNumber); + // remove the packet from our two maps, it's matched + DataServerCallbackObject* callbackObject = _callbackObjects.take(sequenceNumber); + _unmatchedPackets.remove(sequenceNumber); - char* userStringPosition = (char*) packetData + numHeaderBytes + sizeof(_sequenceNumber); + char* userStringPosition = (char*) packetData + numHeaderBytes + sizeof(sequenceNumber); QString userString(userStringPosition); char* keysPosition = userStringPosition + userString.size() + sizeof('\0') + sizeof(unsigned char); @@ -156,84 +156,6 @@ void DataServerClient::processSendFromDataServer(unsigned char* packetData, int callbackObject->processDataServerResponse(userString, keyList, valueList); } - - - // user string was UUID, find matching avatar and associate data -// for (int i = 0; i < keyList.size(); i++) { -// if (valueList[i] != " ") { -// if (keyList[i] == DataServerKey::FaceMeshURL) { - -// if (userUUID.isNull() || userUUID == Application::getInstance()->getProfile()->getUUID()) { -// qDebug("Changing user's face model URL to %s\n", valueList[i].toLocal8Bit().constData()); -// Application::getInstance()->getProfile()->setFaceModelURL(QUrl(valueList[i])); -// } else { -// // mesh URL for a UUID, find avatar in our list -// -// foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { -// if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { -// Avatar* avatar = (Avatar *) node->getLinkedData(); -// -// if (avatar->getUUID() == userUUID) { -// QMetaObject::invokeMethod(&avatar->getHead().getFaceModel(), -// "setURL", Q_ARG(QUrl, QUrl(valueList[i]))); -// } -// } -// } -// } -// } else if (keyList[i] == DataServerKey::SkeletonURL) { - -// if (userUUID.isNull() || userUUID == Application::getInstance()->getProfile()->getUUID()) { -// qDebug("Changing user's skeleton URL to %s\n", valueList[i].toLocal8Bit().constData()); -// Application::getInstance()->getProfile()->setSkeletonModelURL(QUrl(valueList[i])); -// } else { -// // skeleton URL for a UUID, find avatar in our list -// foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { -// if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { -// Avatar* avatar = (Avatar *) node->getLinkedData(); -// -// if (avatar->getUUID() == userUUID) { -// QMetaObject::invokeMethod(&avatar->getSkeletonModel(), "setURL", -// Q_ARG(QUrl, QUrl(valueList[i]))); -// } -// } -// } -// } -// } else if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position && -// keyList[i + 2] == DataServerKey::Orientation && valueList[i] != " " && -// valueList[i + 1] != " " && valueList[i + 2] != " ") { -// -// QStringList coordinateItems = valueList[i + 1].split(','); -// QStringList orientationItems = valueList[i + 2].split(','); - -// if (coordinateItems.size() == 3 && orientationItems.size() == 3) { -// -// // send a node kill request, indicating to other clients that they should play the "disappeared" effect -// NodeList::getInstance()->sendKillNode(&NODE_TYPE_AVATAR_MIXER, 1); -// -// qDebug() << "Changing domain to" << valueList[i].toLocal8Bit().constData() << -// ", position to" << valueList[i + 1].toLocal8Bit().constData() << -// ", and orientation to" << valueList[i + 2].toLocal8Bit().constData() << -// "to go to" << userString << "\n"; -// -// NodeList::getInstance()->setDomainHostname(valueList[i]); -// -// // orient the user to face the target -// glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), -// orientationItems[1].toFloat(), orientationItems[2].toFloat()))) * -// glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f); -// Application::getInstance()->getAvatar()->setOrientation(newOrientation); -// -// // move the user a couple units away -// const float DISTANCE_TO_USER = 2.0f; -// glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), -// coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; -// Application::getInstance()->getAvatar()->setPosition(newPosition); -// } - -// } else if (keyList[i] == DataServerKey::UUID) { - // this is the user's UUID - set it on the profile -// Application::getInstance()->getProfile()->setUUID(valueList[i]); - } void DataServerClient::processMessageFromDataServer(unsigned char* packetData, int numPacketBytes) { diff --git a/interface/src/DataServerClient.h b/libraries/shared/src/DataServerClient.h similarity index 98% rename from interface/src/DataServerClient.h rename to libraries/shared/src/DataServerClient.h index a25bd3faa2..0ebf90f26b 100644 --- a/interface/src/DataServerClient.h +++ b/libraries/shared/src/DataServerClient.h @@ -15,7 +15,7 @@ #include #include -#include +#include "HifiSockAddr.h" class DataServerCallbackObject { public: From 340ae8ebd1f3f73fe9471da35686f4d917e03227 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 20 Jan 2014 17:17:26 -0800 Subject: [PATCH 08/17] column width repair --- libraries/shared/src/DataServerClient.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/DataServerClient.h b/libraries/shared/src/DataServerClient.h index 0ebf90f26b..734d42ed59 100644 --- a/libraries/shared/src/DataServerClient.h +++ b/libraries/shared/src/DataServerClient.h @@ -30,7 +30,8 @@ public: static void getValueForKeyAndUUID(const QString& key, const QUuid& uuid, DataServerCallbackObject* callbackObject); static void getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject); - static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, DataServerCallbackObject* callbackObject); + static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, + DataServerCallbackObject* callbackObject); static void processMessageFromDataServer(unsigned char* packetData, int numPacketBytes); From 3613773b7993cd53b5fedab6bc64ec22dc0b2411 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 21 Jan 2014 09:55:06 -0800 Subject: [PATCH 09/17] cleanup DSC api, add scripting interface --- interface/src/Application.cpp | 4 +- interface/src/Menu.cpp | 12 +- interface/src/avatar/Profile.cpp | 17 ++- .../src/DataServerScriptingInterface.cpp | 21 ++++ .../src/DataServerScriptingInterface.h | 26 +++++ libraries/shared/src/DataServerClient.cpp | 103 +++++++++--------- libraries/shared/src/DataServerClient.h | 15 ++- 7 files changed, 121 insertions(+), 77 deletions(-) create mode 100644 libraries/script-engine/src/DataServerScriptingInterface.cpp create mode 100644 libraries/script-engine/src/DataServerScriptingInterface.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4b8343e72f..8b0fcc9b09 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1854,8 +1854,8 @@ void Application::init() { if (!_profile.getUsername().isEmpty()) { // we have a username for this avatar, ask the data-server for the mesh URL for this avatar - DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL, &_profile); - DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL, &_profile); + DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL, _profile.getUsername(), &_profile); + DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL, _profile.getUsername(), &_profile); } // Set up VoxelSystem after loading preferences so we can get the desired max voxel count diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8c33ead8a5..a4fcd975ca 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -830,8 +830,9 @@ void Menu::editPreferences() { applicationInstance->getProfile()->setFaceModelURL(faceModelURL); // send the new face mesh URL to the data-server (if we have a client UUID) - DataServerClient::putValueForKey(DataServerKey::FaceMeshURL, - faceModelURL.toString().toLocal8Bit().constData()); + DataServerClient::putValueForKeyAndUsername(DataServerKey::FaceMeshURL, + faceModelURL.toString().toLocal8Bit().constData(), + applicationInstance->getProfile()->getUsername()); } QUrl skeletonModelURL(skeletonURLEdit->text()); @@ -841,8 +842,9 @@ void Menu::editPreferences() { applicationInstance->getProfile()->setSkeletonModelURL(skeletonModelURL); // send the new skeleton model URL to the data-server (if we have a client UUID) - DataServerClient::putValueForKey(DataServerKey::SkeletonURL, - skeletonModelURL.toString().toLocal8Bit().constData()); + DataServerClient::putValueForKeyAndUsername(DataServerKey::SkeletonURL, + skeletonModelURL.toString().toLocal8Bit().constData(), + applicationInstance->getProfile()->getUsername()); } applicationInstance->getAvatar()->getHead().setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum()); @@ -961,7 +963,7 @@ void Menu::goToUser() { int dialogReturn = userDialog.exec(); if (dialogReturn == QDialog::Accepted && !userDialog.textValue().isEmpty()) { // there's a username entered by the user, make a request to the data-server - DataServerClient::getValuesForKeysAndUserString( + DataServerClient::getValuesForKeysAndUsername( QStringList() << DataServerKey::Domain << DataServerKey::Position << DataServerKey::Orientation, userDialog.textValue(), Application::getInstance()->getProfile()); } diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index d32f0f018e..f701505e73 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -27,9 +27,9 @@ Profile::Profile(const QString &username) : setUsername(username); // we've been given a new username, ask the data-server for profile - DataServerClient::getClientValueForKey(DataServerKey::UUID, this); - DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL, this); - DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL, this); + DataServerClient::getClientValueForKey(DataServerKey::UUID, username, this); + DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL, username, this); + DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL, username, this); // send our current domain server to the data-server updateDomain(NodeList::getInstance()->getDomainHostname()); @@ -47,8 +47,6 @@ QString Profile::getUserString() const { void Profile::setUUID(const QUuid& uuid) { _uuid = uuid; - DataServerClient::setClientIdentifier(_username); - // when the UUID is changed we need set it appropriately on our avatar instance Application::getInstance()->getAvatar()->setUUID(_uuid); } @@ -72,7 +70,7 @@ void Profile::updateDomain(const QString& domain) { _lastDomain = domain; // send the changed domain to the data-server - DataServerClient::putValueForKey(DataServerKey::Domain, domain.toLocal8Bit().constData()); + DataServerClient::putValueForKeyAndUsername(DataServerKey::Domain, domain, _username); } } @@ -101,7 +99,8 @@ void Profile::updatePosition(const glm::vec3 position) { gettimeofday(&lastPositionSend, NULL); // send the changed position to the data-server - DataServerClient::putValueForKey(DataServerKey::Position, createByteArray(position).constData()); + DataServerClient::putValueForKeyAndUsername(DataServerKey::Position, + QString(createByteArray(position)), _username); } } } @@ -117,7 +116,8 @@ void Profile::updateOrientation(const glm::quat& orientation) { uint64_t now = usecTimestampNow(); if (now - _lastOrientationSend >= DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS && glm::distance(_lastOrientation, eulerAngles) >= DATA_SERVER_ORIENTATION_CHANGE_THRESHOLD_DEGREES) { - DataServerClient::putValueForKey(DataServerKey::Orientation, createByteArray(eulerAngles).constData()); + DataServerClient::putValueForKeyAndUsername(DataServerKey::Orientation, QString(createByteArray(eulerAngles)), + _username); _lastOrientation = eulerAngles; _lastOrientationSend = now; @@ -228,5 +228,4 @@ void Profile::processDataServerResponse(const QString& userString, const QString void Profile::setUsername(const QString& username) { _username = username; - DataServerClient::setClientIdentifier(_username); } diff --git a/libraries/script-engine/src/DataServerScriptingInterface.cpp b/libraries/script-engine/src/DataServerScriptingInterface.cpp new file mode 100644 index 0000000000..236f46e54c --- /dev/null +++ b/libraries/script-engine/src/DataServerScriptingInterface.cpp @@ -0,0 +1,21 @@ +// +// DataServerScriptingInterface.cpp +// hifi +// +// Created by Stephen Birarda on 1/20/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include + +#include "DataServerScriptingInterface.h" + +DataServerScriptingInterface::DataServerScriptingInterface() : + _uuid(QUuid::createUuid()) +{ + +} + +void DataServerScriptingInterface::setValueForKey(const QString& key, const QString& value) { + DataServerClient::putValueForKeyAndUUID(key, value, _uuid); +} \ No newline at end of file diff --git a/libraries/script-engine/src/DataServerScriptingInterface.h b/libraries/script-engine/src/DataServerScriptingInterface.h new file mode 100644 index 0000000000..1cba837b2c --- /dev/null +++ b/libraries/script-engine/src/DataServerScriptingInterface.h @@ -0,0 +1,26 @@ +// +// DataServerScriptingInterface.h +// hifi +// +// Created by Stephen Birarda on 1/20/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__DataServerScriptingInterface__ +#define __hifi__DataServerScriptingInterface__ + +#include +#include +#include + +class DataServerScriptingInterface : public QObject { + Q_OBJECT +public: + DataServerScriptingInterface(); +public slots: + void setValueForKey(const QString& key, const QString& value); +private: + QUuid _uuid; +}; + +#endif /* defined(__hifi__DataServerScriptingInterface__) */ diff --git a/libraries/shared/src/DataServerClient.cpp b/libraries/shared/src/DataServerClient.cpp index 40c75444f6..b16739c9bf 100644 --- a/libraries/shared/src/DataServerClient.cpp +++ b/libraries/shared/src/DataServerClient.cpp @@ -17,7 +17,6 @@ QMap DataServerClient::_unmatchedPackets; QMap DataServerClient::_callbackObjects; -QString DataServerClient::_clientIdentifier; quint8 DataServerClient::_sequenceNumber = 0; const char MULTI_KEY_VALUE_SEPARATOR = '|'; @@ -30,47 +29,48 @@ const HifiSockAddr& DataServerClient::dataServerSockAddr() { return dsSockAddr; } -void DataServerClient::putValueForKey(const QString& key, const char* value) { - if (!_clientIdentifier.isEmpty()) { - - unsigned char putPacket[MAX_PACKET_SIZE]; - - // setup the header for this packet - int numPacketBytes = populateTypeAndVersion(putPacket, PACKET_TYPE_DATA_SERVER_PUT); - - // pack the sequence number - memcpy(putPacket + numPacketBytes, &_sequenceNumber, sizeof(_sequenceNumber)); - numPacketBytes += sizeof(_sequenceNumber); - - // pack the client UUID, null terminated - memcpy(putPacket + numPacketBytes, qPrintable(_clientIdentifier), _clientIdentifier.toLocal8Bit().size()); - numPacketBytes += _clientIdentifier.toLocal8Bit().size(); - putPacket[numPacketBytes++] = '\0'; +void DataServerClient::putValueForKeyAndUsername(const QString& key, const QString& value, const QString& clientIdentifier) { + unsigned char putPacket[MAX_PACKET_SIZE]; + + // setup the header for this packet + int numPacketBytes = populateTypeAndVersion(putPacket, PACKET_TYPE_DATA_SERVER_PUT); + + // pack the sequence number + memcpy(putPacket + numPacketBytes, &_sequenceNumber, sizeof(_sequenceNumber)); + numPacketBytes += sizeof(_sequenceNumber); + + // pack the client UUID, null terminated + memcpy(putPacket + numPacketBytes, qPrintable(clientIdentifier), clientIdentifier.toLocal8Bit().size()); + numPacketBytes += clientIdentifier.toLocal8Bit().size(); + putPacket[numPacketBytes++] = '\0'; + + // pack a 1 to designate that we are putting a single value + putPacket[numPacketBytes++] = 1; + + // pack the key, null terminated + strcpy((char*) putPacket + numPacketBytes, qPrintable(key)); + numPacketBytes += key.size(); + putPacket[numPacketBytes++] = '\0'; + + // pack the value, null terminated + strcpy((char*) putPacket + numPacketBytes, qPrintable(value)); + numPacketBytes += value.size(); + putPacket[numPacketBytes++] = '\0'; + + // add the putPacket to our vector of unconfirmed packets, will be deleted once put is confirmed + _unmatchedPackets.insert(_sequenceNumber, QByteArray((char*) putPacket, numPacketBytes)); + + // send this put request to the data server + NodeList::getInstance()->getNodeSocket().writeDatagram((char*) putPacket, numPacketBytes, + dataServerSockAddr().getAddress(), + dataServerSockAddr().getPort()); + + // push the sequence number forwards + _sequenceNumber++; +} - // pack a 1 to designate that we are putting a single value - putPacket[numPacketBytes++] = 1; - - // pack the key, null terminated - strcpy((char*) putPacket + numPacketBytes, key.toLocal8Bit().constData()); - numPacketBytes += key.size(); - putPacket[numPacketBytes++] = '\0'; - - // pack the value, null terminated - strcpy((char*) putPacket + numPacketBytes, value); - numPacketBytes += strlen(value); - putPacket[numPacketBytes++] = '\0'; - - // add the putPacket to our vector of unconfirmed packets, will be deleted once put is confirmed - _unmatchedPackets.insert(_sequenceNumber, QByteArray((char*) putPacket, numPacketBytes)); - - // send this put request to the data server - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) putPacket, numPacketBytes, - dataServerSockAddr().getAddress(), - dataServerSockAddr().getPort()); - - // push the sequence number forwards - _sequenceNumber++; - } +void DataServerClient::putValueForKeyAndUUID(const QString& key, const QString& value, const QUuid& uuid) { + putValueForKeyAndUsername(key, value, uuidStringWithoutCurlyBraces(uuid)); } void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uuid, DataServerCallbackObject* callbackObject) { @@ -79,13 +79,13 @@ void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uu void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject) { if (!uuid.isNull()) { - getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid), callbackObject); + getValuesForKeysAndUsername(keys, uuidStringWithoutCurlyBraces(uuid), callbackObject); } } -void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, - DataServerCallbackObject* callbackObject) { - if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) { +void DataServerClient::getValuesForKeysAndUsername(const QStringList& keys, const QString& clientIdentifier, + DataServerCallbackObject* callbackObject) { + if (!clientIdentifier.isEmpty() && keys.size() <= UCHAR_MAX) { unsigned char getPacket[MAX_PACKET_SIZE]; // setup the header for this packet @@ -96,8 +96,8 @@ void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, co numPacketBytes += sizeof(_sequenceNumber); // pack the user string (could be username or UUID string), null-terminate - memcpy(getPacket + numPacketBytes, userString.toLocal8Bit().constData(), userString.toLocal8Bit().size()); - numPacketBytes += userString.toLocal8Bit().size(); + memcpy(getPacket + numPacketBytes, clientIdentifier.toLocal8Bit().constData(), clientIdentifier.size()); + numPacketBytes += clientIdentifier.size(); getPacket[numPacketBytes++] = '\0'; // pack one byte to designate the number of keys @@ -122,12 +122,9 @@ void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, co } } -void DataServerClient::getClientValueForKey(const QString& key, DataServerCallbackObject* callbackObject) { - if (!_clientIdentifier.isEmpty()) { - getValuesForKeysAndUserString(QStringList(key), _clientIdentifier, callbackObject); - } else { - qDebug() << "There is no client identifier set for the DataServerClient."; - } +void DataServerClient::getClientValueForKey(const QString& key, const QString& clientIdentifier, + DataServerCallbackObject* callbackObject) { + getValuesForKeysAndUsername(QStringList(key), clientIdentifier, callbackObject); } void DataServerClient::processConfirmFromDataServer(unsigned char* packetData) { diff --git a/libraries/shared/src/DataServerClient.h b/libraries/shared/src/DataServerClient.h index 734d42ed59..11ceb8c42c 100644 --- a/libraries/shared/src/DataServerClient.h +++ b/libraries/shared/src/DataServerClient.h @@ -25,26 +25,25 @@ public: class DataServerClient { public: static const HifiSockAddr& dataServerSockAddr(); - static void putValueForKey(const QString& key, const char* value); - static void getClientValueForKey(const QString& key, DataServerCallbackObject* callbackObject); + static void putValueForKeyAndUsername(const QString& key, const QString& value, const QString& clientIdentifier); + static void putValueForKeyAndUUID(const QString& key, const QString& value, const QUuid& uuid); + + static void getClientValueForKey(const QString& key, const QString& _clientIdentifer, + DataServerCallbackObject* callbackObject); static void getValueForKeyAndUUID(const QString& key, const QUuid& uuid, DataServerCallbackObject* callbackObject); static void getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject); - static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, - DataServerCallbackObject* callbackObject); + static void getValuesForKeysAndUsername(const QStringList& keys, const QString& clientIdentifier, + DataServerCallbackObject* callbackObject); static void processMessageFromDataServer(unsigned char* packetData, int numPacketBytes); static void resendUnmatchedPackets(); - - static void setClientIdentifier(const QString& clientIdentifier) { _clientIdentifier = clientIdentifier; } private: - static void processConfirmFromDataServer(unsigned char* packetData); static void processSendFromDataServer(unsigned char* packetData, int numPacketBytes); static void removeMatchedPacketFromMap(unsigned char* packetData); - static QString _clientIdentifier; static QMap _unmatchedPackets; static QMap _callbackObjects; static quint8 _sequenceNumber; From 1423f91717394720364d3c89b0e0e808e3c9e50e Mon Sep 17 00:00:00 2001 From: Lucas Crisman Date: Tue, 21 Jan 2014 15:48:44 -0300 Subject: [PATCH 10/17] Allow domain-server to read configuration from file --- domain-server/src/DomainServer.cpp | 92 +++++++++++++++++++++++++++--- domain-server/src/DomainServer.h | 3 + 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 28edf6b6a1..3e60d1509c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -22,6 +23,10 @@ const int RESTART_HOLD_TIME_MSECS = 5 * 1000; +const char* VOXEL_SERVER_CONFIG = "voxelServerConfig"; +const char* PARTICLE_SERVER_CONFIG = "particleServerConfig"; +const char* METAVOXEL_SERVER_CONFIG = "metavoxelServerConfig"; + void signalhandler(int sig){ if (sig == SIGINT) { qApp->quit(); @@ -47,17 +52,22 @@ DomainServer::DomainServer(int argc, char* argv[]) : const char* customPortString = getCmdOption(argc, (const char**) argv, CUSTOM_PORT_OPTION); unsigned short domainServerPort = customPortString ? atoi(customPortString) : DEFAULT_DOMAIN_SERVER_PORT; + const char CONFIG_FILE_OPTION[] = "-c"; + const char* configFilePath = getCmdOption(argc, (const char**) argv, CONFIG_FILE_OPTION); + + if (!readConfigFile(configFilePath)) { + QByteArray voxelConfigOption = QString("--%1").arg(VOXEL_SERVER_CONFIG).toLocal8Bit(); + _voxelServerConfig = getCmdOption(argc, (const char**) argv, voxelConfigOption.constData()); + + QByteArray particleConfigOption = QString("--%1").arg(PARTICLE_SERVER_CONFIG).toLocal8Bit(); + _particleServerConfig = getCmdOption(argc, (const char**) argv, particleConfigOption.constData()); + + QByteArray metavoxelConfigOption = QString("--%1").arg(METAVOXEL_SERVER_CONFIG).toLocal8Bit(); + _metavoxelServerConfig = getCmdOption(argc, (const char**) argv, metavoxelConfigOption.constData()); + } + NodeList* nodeList = NodeList::createInstance(NODE_TYPE_DOMAIN, domainServerPort); - const char VOXEL_CONFIG_OPTION[] = "--voxelServerConfig"; - _voxelServerConfig = getCmdOption(argc, (const char**) argv, VOXEL_CONFIG_OPTION); - - const char PARTICLE_CONFIG_OPTION[] = "--particleServerConfig"; - _particleServerConfig = getCmdOption(argc, (const char**) argv, PARTICLE_CONFIG_OPTION); - - const char METAVOXEL_CONFIG_OPTION[] = "--metavoxelServerConfig"; - _metavoxelServerConfig = getCmdOption(argc, (const char**)argv, METAVOXEL_CONFIG_OPTION); - connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), this, SLOT(nodeKilled(SharedNodePointer))); if (!_staticAssignmentFile.exists() || _voxelServerConfig) { @@ -275,6 +285,70 @@ QJsonObject jsonObjectForNode(Node* node) { return nodeJson; } +// Attempts to read configuration from specified path +// returns true on success, false otherwise +bool DomainServer::readConfigFile(const char* path) { + if (!path) { + // config file not specified + return false; + } + + if (!QFile::exists(path)) { + qWarning("Specified configuration file does not exist!\n"); + return false; + } + + QFile configFile(path); + if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("Can't open specified configuration file!\n"); + return false; + } else { + qDebug("Reading configuration from %s\n", path); + } + QTextStream configStream(&configFile); + QByteArray configStringByteArray = configStream.readAll().toUtf8(); + QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object(); + configFile.close(); + + QString voxelServerConfig = readServerAssignmentConfig(configDocObject, VOXEL_SERVER_CONFIG); + _voxelServerConfig = new char[strlen(voxelServerConfig.toLocal8Bit().constData()) +1]; + _voxelServerConfig = strcpy((char *) _voxelServerConfig, voxelServerConfig.toLocal8Bit().constData() + '\0'); + + QString particleServerConfig = readServerAssignmentConfig(configDocObject, PARTICLE_SERVER_CONFIG); + _particleServerConfig = new char[strlen(particleServerConfig.toLocal8Bit().constData()) +1]; + _particleServerConfig = strcpy((char *) _particleServerConfig, particleServerConfig.toLocal8Bit().constData() + '\0'); + + QString metavoxelServerConfig = readServerAssignmentConfig(configDocObject, METAVOXEL_SERVER_CONFIG); + _metavoxelServerConfig = new char[strlen(metavoxelServerConfig.toLocal8Bit().constData()) +1]; + _metavoxelServerConfig = strcpy((char *) _metavoxelServerConfig, metavoxelServerConfig.toLocal8Bit().constData() + '\0'); + + return true; +} + +// find assignment configurations on the specified node name and json object +// returns a string in the form of its equivalent cmd line params +QString DomainServer::readServerAssignmentConfig(QJsonObject jsonObject, const char* nodeName) { + QJsonArray nodeArray = jsonObject[nodeName].toArray(); + + QStringList serverConfig; + foreach (const QJsonValue & childValue, nodeArray) { + QString cmdParams; + QJsonObject childObject = childValue.toObject(); + QStringList keys = childObject.keys(); + for (int i = 0; i < keys.size(); i++) { + QString key = keys[i]; + QString value = childObject[key].toString(); + // both cmd line params and json keys are the same + cmdParams += QString("--%1 %2 ").arg(key, value); + } + serverConfig << cmdParams; + } + + // according to split() calls from DomainServer::prepopulateStaticAssignmentFile + // we shold simply join them with semicolons + return serverConfig.join(';'); +} + bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) { const QString JSON_MIME_TYPE = "application/json"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c9c4f0e2d7..fed5aaaa43 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -35,6 +35,9 @@ public slots: void nodeKilled(SharedNodePointer node); private: + bool readConfigFile(const char* path); + QString readServerAssignmentConfig(QJsonObject jsonObj, const char* nodeName); + void prepopulateStaticAssignmentFile(); Assignment* matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType); Assignment* deployableAssignmentForRequest(Assignment& requestAssignment); From e08f4d2eba25c2e52a1e69f4f5d6265fa54712cc Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 21 Jan 2014 13:13:57 -0800 Subject: [PATCH 11/17] more DataServerClient API tweaks --- interface/src/Application.cpp | 4 ++-- interface/src/Menu.cpp | 14 ++++++------- interface/src/avatar/Profile.cpp | 16 +++++++-------- libraries/shared/src/DataServerClient.cpp | 24 +++++++++++------------ libraries/shared/src/DataServerClient.h | 10 +++++----- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8b0fcc9b09..ac243bfa3e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1854,8 +1854,8 @@ void Application::init() { if (!_profile.getUsername().isEmpty()) { // we have a username for this avatar, ask the data-server for the mesh URL for this avatar - DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL, _profile.getUsername(), &_profile); - DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL, _profile.getUsername(), &_profile); + DataServerClient::getValueForKeyAndUserString(DataServerKey::FaceMeshURL, _profile.getUserString(), &_profile); + DataServerClient::getValueForKeyAndUserString(DataServerKey::SkeletonURL, _profile.getUserString(), &_profile); } // Set up VoxelSystem after loading preferences so we can get the desired max voxel count diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a4fcd975ca..3e70426d09 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -830,9 +830,9 @@ void Menu::editPreferences() { applicationInstance->getProfile()->setFaceModelURL(faceModelURL); // send the new face mesh URL to the data-server (if we have a client UUID) - DataServerClient::putValueForKeyAndUsername(DataServerKey::FaceMeshURL, - faceModelURL.toString().toLocal8Bit().constData(), - applicationInstance->getProfile()->getUsername()); + DataServerClient::putValueForKeyAndUserString(DataServerKey::FaceMeshURL, + faceModelURL.toString().toLocal8Bit().constData(), + applicationInstance->getProfile()->getUserString()); } QUrl skeletonModelURL(skeletonURLEdit->text()); @@ -842,9 +842,9 @@ void Menu::editPreferences() { applicationInstance->getProfile()->setSkeletonModelURL(skeletonModelURL); // send the new skeleton model URL to the data-server (if we have a client UUID) - DataServerClient::putValueForKeyAndUsername(DataServerKey::SkeletonURL, - skeletonModelURL.toString().toLocal8Bit().constData(), - applicationInstance->getProfile()->getUsername()); + DataServerClient::putValueForKeyAndUserString(DataServerKey::SkeletonURL, + skeletonModelURL.toString().toLocal8Bit().constData(), + applicationInstance->getProfile()->getUserString()); } applicationInstance->getAvatar()->getHead().setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum()); @@ -963,7 +963,7 @@ void Menu::goToUser() { int dialogReturn = userDialog.exec(); if (dialogReturn == QDialog::Accepted && !userDialog.textValue().isEmpty()) { // there's a username entered by the user, make a request to the data-server - DataServerClient::getValuesForKeysAndUsername( + DataServerClient::getValuesForKeysAndUserString( QStringList() << DataServerKey::Domain << DataServerKey::Position << DataServerKey::Orientation, userDialog.textValue(), Application::getInstance()->getProfile()); } diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index f701505e73..82427babc0 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -27,9 +27,9 @@ Profile::Profile(const QString &username) : setUsername(username); // we've been given a new username, ask the data-server for profile - DataServerClient::getClientValueForKey(DataServerKey::UUID, username, this); - DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL, username, this); - DataServerClient::getClientValueForKey(DataServerKey::SkeletonURL, username, this); + DataServerClient::getValueForKeyAndUserString(DataServerKey::UUID, getUserString(), this); + DataServerClient::getValueForKeyAndUserString(DataServerKey::FaceMeshURL, getUserString(), this); + DataServerClient::getValueForKeyAndUserString(DataServerKey::SkeletonURL, getUserString(), this); // send our current domain server to the data-server updateDomain(NodeList::getInstance()->getDomainHostname()); @@ -70,7 +70,7 @@ void Profile::updateDomain(const QString& domain) { _lastDomain = domain; // send the changed domain to the data-server - DataServerClient::putValueForKeyAndUsername(DataServerKey::Domain, domain, _username); + DataServerClient::putValueForKeyAndUserString(DataServerKey::Domain, domain, getUserString()); } } @@ -99,8 +99,8 @@ void Profile::updatePosition(const glm::vec3 position) { gettimeofday(&lastPositionSend, NULL); // send the changed position to the data-server - DataServerClient::putValueForKeyAndUsername(DataServerKey::Position, - QString(createByteArray(position)), _username); + DataServerClient::putValueForKeyAndUserString(DataServerKey::Position, + QString(createByteArray(position)), getUserString()); } } } @@ -116,8 +116,8 @@ void Profile::updateOrientation(const glm::quat& orientation) { uint64_t now = usecTimestampNow(); if (now - _lastOrientationSend >= DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS && glm::distance(_lastOrientation, eulerAngles) >= DATA_SERVER_ORIENTATION_CHANGE_THRESHOLD_DEGREES) { - DataServerClient::putValueForKeyAndUsername(DataServerKey::Orientation, QString(createByteArray(eulerAngles)), - _username); + DataServerClient::putValueForKeyAndUserString(DataServerKey::Orientation, QString(createByteArray(eulerAngles)), + getUserString()); _lastOrientation = eulerAngles; _lastOrientationSend = now; diff --git a/libraries/shared/src/DataServerClient.cpp b/libraries/shared/src/DataServerClient.cpp index b16739c9bf..2eb8fcc1f0 100644 --- a/libraries/shared/src/DataServerClient.cpp +++ b/libraries/shared/src/DataServerClient.cpp @@ -29,7 +29,7 @@ const HifiSockAddr& DataServerClient::dataServerSockAddr() { return dsSockAddr; } -void DataServerClient::putValueForKeyAndUsername(const QString& key, const QString& value, const QString& clientIdentifier) { +void DataServerClient::putValueForKeyAndUserString(const QString& key, const QString& value, const QString& userString) { unsigned char putPacket[MAX_PACKET_SIZE]; // setup the header for this packet @@ -40,8 +40,8 @@ void DataServerClient::putValueForKeyAndUsername(const QString& key, const QStri numPacketBytes += sizeof(_sequenceNumber); // pack the client UUID, null terminated - memcpy(putPacket + numPacketBytes, qPrintable(clientIdentifier), clientIdentifier.toLocal8Bit().size()); - numPacketBytes += clientIdentifier.toLocal8Bit().size(); + memcpy(putPacket + numPacketBytes, qPrintable(userString), userString.size()); + numPacketBytes += userString.size(); putPacket[numPacketBytes++] = '\0'; // pack a 1 to designate that we are putting a single value @@ -70,7 +70,7 @@ void DataServerClient::putValueForKeyAndUsername(const QString& key, const QStri } void DataServerClient::putValueForKeyAndUUID(const QString& key, const QString& value, const QUuid& uuid) { - putValueForKeyAndUsername(key, value, uuidStringWithoutCurlyBraces(uuid)); + putValueForKeyAndUserString(key, value, uuidStringWithoutCurlyBraces(uuid)); } void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uuid, DataServerCallbackObject* callbackObject) { @@ -79,13 +79,13 @@ void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uu void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject) { if (!uuid.isNull()) { - getValuesForKeysAndUsername(keys, uuidStringWithoutCurlyBraces(uuid), callbackObject); + getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid), callbackObject); } } -void DataServerClient::getValuesForKeysAndUsername(const QStringList& keys, const QString& clientIdentifier, +void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, DataServerCallbackObject* callbackObject) { - if (!clientIdentifier.isEmpty() && keys.size() <= UCHAR_MAX) { + if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) { unsigned char getPacket[MAX_PACKET_SIZE]; // setup the header for this packet @@ -96,8 +96,8 @@ void DataServerClient::getValuesForKeysAndUsername(const QStringList& keys, cons numPacketBytes += sizeof(_sequenceNumber); // pack the user string (could be username or UUID string), null-terminate - memcpy(getPacket + numPacketBytes, clientIdentifier.toLocal8Bit().constData(), clientIdentifier.size()); - numPacketBytes += clientIdentifier.size(); + memcpy(getPacket + numPacketBytes, qPrintable(userString), userString.size()); + numPacketBytes += userString.size(); getPacket[numPacketBytes++] = '\0'; // pack one byte to designate the number of keys @@ -122,9 +122,9 @@ void DataServerClient::getValuesForKeysAndUsername(const QStringList& keys, cons } } -void DataServerClient::getClientValueForKey(const QString& key, const QString& clientIdentifier, - DataServerCallbackObject* callbackObject) { - getValuesForKeysAndUsername(QStringList(key), clientIdentifier, callbackObject); +void DataServerClient::getValueForKeyAndUserString(const QString& key, const QString& userString, + DataServerCallbackObject* callbackObject) { + getValuesForKeysAndUserString(QStringList(key), userString, callbackObject); } void DataServerClient::processConfirmFromDataServer(unsigned char* packetData) { diff --git a/libraries/shared/src/DataServerClient.h b/libraries/shared/src/DataServerClient.h index 11ceb8c42c..0ce9355f4b 100644 --- a/libraries/shared/src/DataServerClient.h +++ b/libraries/shared/src/DataServerClient.h @@ -26,15 +26,15 @@ class DataServerClient { public: static const HifiSockAddr& dataServerSockAddr(); - static void putValueForKeyAndUsername(const QString& key, const QString& value, const QString& clientIdentifier); + static void putValueForKeyAndUserString(const QString& key, const QString& value, const QString& userString); static void putValueForKeyAndUUID(const QString& key, const QString& value, const QUuid& uuid); - static void getClientValueForKey(const QString& key, const QString& _clientIdentifer, - DataServerCallbackObject* callbackObject); + static void getValueForKeyAndUserString(const QString& key, const QString& userString, + DataServerCallbackObject* callbackObject); static void getValueForKeyAndUUID(const QString& key, const QUuid& uuid, DataServerCallbackObject* callbackObject); static void getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject); - static void getValuesForKeysAndUsername(const QStringList& keys, const QString& clientIdentifier, - DataServerCallbackObject* callbackObject); + static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString, + DataServerCallbackObject* callbackObject); static void processMessageFromDataServer(unsigned char* packetData, int numPacketBytes); From aa9f7ff31db9111b2647a95e8abaf68ef42da2cf Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 21 Jan 2014 13:21:03 -0800 Subject: [PATCH 12/17] hook the DataServerScriptingInterface to the ScriptEngine --- libraries/script-engine/src/DataServerScriptingInterface.h | 2 ++ libraries/script-engine/src/ScriptEngine.cpp | 6 ++++++ libraries/script-engine/src/ScriptEngine.h | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/libraries/script-engine/src/DataServerScriptingInterface.h b/libraries/script-engine/src/DataServerScriptingInterface.h index 1cba837b2c..4a50bc087a 100644 --- a/libraries/script-engine/src/DataServerScriptingInterface.h +++ b/libraries/script-engine/src/DataServerScriptingInterface.h @@ -17,6 +17,8 @@ class DataServerScriptingInterface : public QObject { Q_OBJECT public: DataServerScriptingInterface(); + + void refreshUUID() { _uuid = QUuid::createUuid(); } public slots: void setValueForKey(const QString& key, const QString& value); private: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6b85cf33ad..2c08bd9b08 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -119,6 +119,8 @@ void ScriptEngine::init() { QScriptValue audioScriptingInterfaceValue = _engine.newQObject(&_audioScriptingInterface); _engine.globalObject().setProperty("Audio", audioScriptingInterfaceValue); + + registerGlobalObject("Data", &_dataServerScriptingInterface); if (_controllerScriptingInterface) { QScriptValue controllerScripterValue = _engine.newQObject(_controllerScriptingInterface); @@ -140,6 +142,10 @@ void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { _engine.globalObject().setProperty(name, value); } +void ScriptEngine::preEvaluateReset() { + _dataServerScriptingInterface.refreshUUID(); +} + void ScriptEngine::evaluate() { if (!_isInitialized) { init(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 06597f82a8..ab38b48bbf 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -22,6 +22,7 @@ class ParticlesScriptingInterface; #include "AbstractControllerScriptingInterface.h" +#include "DataServerScriptingInterface.h" const QString NO_SCRIPT(""); @@ -61,6 +62,8 @@ signals: void finished(); protected: + void preEvaluateReset(); + QString _scriptContents; bool _isFinished; bool _isRunning; @@ -72,6 +75,7 @@ private: static ParticlesScriptingInterface _particlesScriptingInterface; AbstractControllerScriptingInterface* _controllerScriptingInterface; AudioScriptingInterface _audioScriptingInterface; + DataServerScriptingInterface _dataServerScriptingInterface; bool _wantMenuItems; QString _scriptMenuName; AbstractMenuInterface* _menu; From 417aa7ad59db9187a2b15d2ea0460539eef2d1ff Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 21 Jan 2014 15:59:55 -0800 Subject: [PATCH 13/17] re-expose an Avatar to the ScriptEngine --- assignment-client/src/Agent.cpp | 11 +++++- .../src/DataServerScriptingInterface.h | 2 ++ libraries/script-engine/src/ScriptEngine.cpp | 35 ++++++++++++++++++- libraries/script-engine/src/ScriptEngine.h | 14 ++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index ca89d3998f..d27845f337 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -56,7 +56,7 @@ void Agent::run() { // other node types, but for them to get access to those node types, we have to add them here. It seems like // NodeList should support adding types of interest const NODE_TYPE AGENT_NODE_TYPES_OF_INTEREST[] = { NODE_TYPE_VOXEL_SERVER, NODE_TYPE_PARTICLE_SERVER, - NODE_TYPE_AUDIO_MIXER }; + NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER }; nodeList->setNodeTypesOfInterest(AGENT_NODE_TYPES_OF_INTEREST, sizeof(AGENT_NODE_TYPES_OF_INTEREST)); @@ -93,6 +93,15 @@ void Agent::run() { QTimer* pingNodesTimer = new QTimer(this); connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes())); pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000); + + // setup an Avatar for the script to use + AvatarData scriptedAvatar; + + // match the scripted avatar's UUID to the DataServerScriptingInterface UUID + scriptedAvatar.setUUID(_scriptEngine.getDataServerScriptingInterface().getUUID()); + + // give this AvatarData object to the script engine + _scriptEngine.setAvatarData(&scriptedAvatar); _scriptEngine.setScriptContents(scriptContents); _scriptEngine.run(); diff --git a/libraries/script-engine/src/DataServerScriptingInterface.h b/libraries/script-engine/src/DataServerScriptingInterface.h index 4a50bc087a..8bd3eea565 100644 --- a/libraries/script-engine/src/DataServerScriptingInterface.h +++ b/libraries/script-engine/src/DataServerScriptingInterface.h @@ -19,6 +19,8 @@ public: DataServerScriptingInterface(); void refreshUUID() { _uuid = QUuid::createUuid(); } + const QUuid& getUUID() const { return _uuid; } + public slots: void setValueForKey(const QString& key, const QString& value); private: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2c08bd9b08..1b2f121b5b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -40,7 +40,9 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const char* scriptMenuName, AbstractMenuInterface* menu, - AbstractControllerScriptingInterface* controllerScriptingInterface) { + AbstractControllerScriptingInterface* controllerScriptingInterface) : + _avatarData(NULL) +{ _scriptContents = scriptContents; _isFinished = false; _isRunning = false; @@ -65,6 +67,15 @@ ScriptEngine::~ScriptEngine() { //printf("ScriptEngine::~ScriptEngine()...\n"); } +void ScriptEngine::setAvatarData(AvatarData* avatarData) { + _avatarData = avatarData; + + // remove the old Avatar property, if it exists + _engine.globalObject().setProperty("Avatar", QScriptValue()); + + // give the script engine the new Avatar script property + registerGlobalObject("Avatar", _avatarData); +} void ScriptEngine::setupMenuItems() { if (_menu && _wantMenuItems) { @@ -178,6 +189,8 @@ void ScriptEngine::run() { gettimeofday(&startTime, NULL); int thisFrame = 0; + + NodeList* nodeList = NodeList::getInstance(); while (!_isFinished) { int usecToSleep = usecTimestamp(&startTime) + (thisFrame++ * VISUAL_DATA_CALLBACK_USECS) - usecTimestampNow(); @@ -221,6 +234,26 @@ void ScriptEngine::run() { _particlesScriptingInterface.getParticlePacketSender()->process(); } } + + if (_isAvatar && _avatarData) { + static unsigned char avatarPacket[MAX_PACKET_SIZE]; + static int numAvatarHeaderBytes = 0; + + if (numAvatarHeaderBytes == 0) { + // pack the avatar header bytes the first time + // unlike the _avatar.getBroadcastData these won't change + numAvatarHeaderBytes = populateTypeAndVersion(avatarPacket, PACKET_TYPE_HEAD_DATA); + + // pack the owner UUID for this script + QByteArray ownerUUID = nodeList->getOwnerUUID().toRfc4122(); + memcpy(avatarPacket, ownerUUID.constData(), ownerUUID.size()); + numAvatarHeaderBytes += ownerUUID.size(); + } + + int numAvatarPacketBytes = _avatarData->getBroadcastData(avatarPacket + numAvatarHeaderBytes) + numAvatarHeaderBytes; + + nodeList->broadcastToNodes(avatarPacket, numAvatarPacketBytes, &NODE_TYPE_AVATAR_MIXER, 1); + } if (willSendVisualDataCallBack) { emit willSendVisualDataCallback(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index ab38b48bbf..9f1fbeacae 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -19,6 +19,8 @@ #include #include +#include + class ParticlesScriptingInterface; #include "AbstractControllerScriptingInterface.h" @@ -28,6 +30,8 @@ const QString NO_SCRIPT(""); class ScriptEngine : public QObject { Q_OBJECT + + Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar) public: ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, const char* scriptMenuName = NULL, AbstractMenuInterface* menu = NULL, @@ -40,6 +44,9 @@ public: /// Access the ParticlesScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; } + + /// Access the DataServerScriptingInterface for access to its underlying UUID + const DataServerScriptingInterface& getDataServerScriptingInterface() { return _dataServerScriptingInterface; } /// sets the script contents, will return false if failed, will fail if script is already running bool setScriptContents(const QString& scriptContents); @@ -48,6 +55,11 @@ public: void cleanMenuItems(); void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name + + void setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; } + bool isAvatar() const { return _isAvatar; } + + void setAvatarData(AvatarData* avatarData); public slots: void init(); @@ -69,6 +81,7 @@ protected: bool _isRunning; bool _isInitialized; QScriptEngine _engine; + bool _isAvatar; private: static VoxelsScriptingInterface _voxelsScriptingInterface; @@ -76,6 +89,7 @@ private: AbstractControllerScriptingInterface* _controllerScriptingInterface; AudioScriptingInterface _audioScriptingInterface; DataServerScriptingInterface _dataServerScriptingInterface; + AvatarData* _avatarData; bool _wantMenuItems; QString _scriptMenuName; AbstractMenuInterface* _menu; From 8e5d4b69f47412650410bf6168cf2316352a448a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 21 Jan 2014 16:14:33 -0800 Subject: [PATCH 14/17] fix packet offset for owner UUID --- libraries/script-engine/src/ScriptEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 7d0d68c0c6..c56bb1ada7 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -250,7 +250,7 @@ void ScriptEngine::run() { // pack the owner UUID for this script QByteArray ownerUUID = nodeList->getOwnerUUID().toRfc4122(); - memcpy(avatarPacket, ownerUUID.constData(), ownerUUID.size()); + memcpy(avatarPacket + numAvatarHeaderBytes, ownerUUID.constData(), ownerUUID.size()); numAvatarHeaderBytes += ownerUUID.size(); } From 105fb42b340b358452b14fbccab6132f28dc9002 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 21 Jan 2014 16:46:04 -0800 Subject: [PATCH 15/17] rewrite mesh and skeleton requests from data-server --- interface/src/Application.cpp | 56 ++--------------------- interface/src/Application.h | 1 - interface/src/avatar/Avatar.cpp | 27 +++++------ interface/src/avatar/Avatar.h | 1 - libraries/shared/src/DataServerClient.cpp | 19 +++++--- libraries/shared/src/PacketHeaders.cpp | 3 -- libraries/shared/src/PacketHeaders.h | 1 - 7 files changed, 27 insertions(+), 81 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5c943f5945..8e1ba5afa1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1324,6 +1324,9 @@ void Application::timer() { // ask the node list to check in with the domain server NodeList::getInstance()->sendDomainServerCheckIn(); + + // send unmatched DataServerClient packets + DataServerClient::resendUnmatchedPackets(); // give the MyAvatar object position, orientation to the Profile so it can propagate to the data-server _profile.updatePosition(_myAvatar.getPosition()); @@ -1416,51 +1419,6 @@ void Application::terminate() { } } -static Avatar* processAvatarMessageHeader(unsigned char*& packetData, size_t& dataBytes) { - // record the packet for stats-tracking - Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AVATARS).updateValue(dataBytes); - SharedNodePointer avatarMixerNode = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER); - if (avatarMixerNode) { - avatarMixerNode->recordBytesReceived(dataBytes); - } - - // skip the header - int numBytesPacketHeader = numBytesForPacketHeader(packetData); - packetData += numBytesPacketHeader; - dataBytes -= numBytesPacketHeader; - - // read the node id - QUuid nodeUUID = QUuid::fromRfc4122(QByteArray((char*) packetData, NUM_BYTES_RFC4122_UUID)); - - packetData += NUM_BYTES_RFC4122_UUID; - dataBytes -= NUM_BYTES_RFC4122_UUID; - - // make sure the node exists - SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(nodeUUID); - if (!node || !node->getLinkedData()) { - return NULL; - } - Avatar* avatar = static_cast(node->getLinkedData()); - return avatar->isInitialized() ? avatar : NULL; -} - -void Application::processAvatarURLsMessage(unsigned char* packetData, size_t dataBytes) { - Avatar* avatar = processAvatarMessageHeader(packetData, dataBytes); - if (!avatar) { - return; - } - // PER Note: message is no longer processed but used to trigger - // Dataserver lookup - redesign this to instantly ask the - // dataserver on first receipt of other avatar UUID, and also - // don't ask over and over again. Instead use this message to - // Tell the other avatars that your dataserver data has - // changed. - - // use this timing to as the data-server for an updated mesh for this avatar (if we have UUID) - DataServerClient::getValuesForKeysAndUUID(QStringList() << DataServerKey::FaceMeshURL << DataServerKey::SkeletonURL, - avatar->getUUID(), &_profile); -} - void Application::checkBandwidthMeterClick() { // ... to be called upon button release @@ -2554,11 +2512,6 @@ void Application::updateAvatar(float deltaTime) { controlledBroadcastToNodes(broadcastString, endOfBroadcastStringWrite - broadcastString, nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); - const float AVATAR_URLS_SEND_INTERVAL = 1.0f; - if (shouldDo(AVATAR_URLS_SEND_INTERVAL, deltaTime)) { - QUrl empty; - Avatar::sendAvatarURLsMessage(empty); - } // Update _viewFrustum with latest camera and view frustum data... // NOTE: we get this from the view frustum, to make it simpler, since the // loadViewFrumstum() method will get the correct details from the camera @@ -4225,9 +4178,6 @@ void Application::processDatagrams() { bytesReceived); getInstance()->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(bytesReceived); break; - case PACKET_TYPE_AVATAR_URLS: - processAvatarURLsMessage(_incomingPacket, bytesReceived); - break; case PACKET_TYPE_DATA_SERVER_GET: case PACKET_TYPE_DATA_SERVER_PUT: case PACKET_TYPE_DATA_SERVER_SEND: diff --git a/interface/src/Application.h b/interface/src/Application.h index 242b75c0ba..63a3209a9f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -267,7 +267,6 @@ private: void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true); static bool sendVoxelsOperation(OctreeElement* node, void* extraData); - void processAvatarURLsMessage(unsigned char* packetData, size_t dataBytes); static void sendPingPackets(); void initDisplay(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 6db0a15300..30de5b1e2a 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -58,21 +58,6 @@ const int NUM_BODY_CONE_SIDES = 9; const float CHAT_MESSAGE_SCALE = 0.0015f; const float CHAT_MESSAGE_HEIGHT = 0.1f; -void Avatar::sendAvatarURLsMessage(const QUrl& voxelURL) { - QByteArray message; - - char packetHeader[MAX_PACKET_HEADER_BYTES]; - int numBytesPacketHeader = populateTypeAndVersion((unsigned char*) packetHeader, PACKET_TYPE_AVATAR_URLS); - - message.append(packetHeader, numBytesPacketHeader); - message.append(NodeList::getInstance()->getOwnerUUID().toRfc4122()); - - QDataStream out(&message, QIODevice::WriteOnly | QIODevice::Append); - out << voxelURL; - - Application::controlledBroadcastToNodes((unsigned char*)message.data(), message.size(), &NODE_TYPE_AVATAR_MIXER, 1); -} - Avatar::Avatar(Node* owningNode) : AvatarData(owningNode), _head(this), @@ -359,9 +344,21 @@ bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadi int Avatar::parseData(unsigned char* sourceBuffer, int numBytes) { // change in position implies movement glm::vec3 oldPosition = _position; + + // change in UUID requires mesh and skeleton request to data-server + + QUuid oldUuid = _uuid; + int bytesRead = AvatarData::parseData(sourceBuffer, numBytes); + const float MOVE_DISTANCE_THRESHOLD = 0.001f; _moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD; + + if (oldUuid != _uuid && !_uuid.isNull()) { + DataServerClient::getValuesForKeysAndUUID(QStringList() << DataServerKey::FaceMeshURL << DataServerKey::SkeletonURL, + _uuid, Application::getInstance()->getProfile()); + } + return bytesRead; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 3f1da6bc31..725aa9d5cf 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -69,7 +69,6 @@ class Avatar : public AvatarData { Q_OBJECT public: - static void sendAvatarURLsMessage(const QUrl& voxelURL); Avatar(Node* owningNode = NULL); ~Avatar(); diff --git a/libraries/shared/src/DataServerClient.cpp b/libraries/shared/src/DataServerClient.cpp index 2eb8fcc1f0..a7a213f42c 100644 --- a/libraries/shared/src/DataServerClient.cpp +++ b/libraries/shared/src/DataServerClient.cpp @@ -77,7 +77,8 @@ void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uu getValuesForKeysAndUUID(QStringList(key), uuid, callbackObject); } -void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject) { +void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, + DataServerCallbackObject* callbackObject) { if (!uuid.isNull()) { getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid), callbackObject); } @@ -137,7 +138,7 @@ void DataServerClient::processSendFromDataServer(unsigned char* packetData, int quint8 sequenceNumber = *(packetData + numHeaderBytes); - if (_callbackObjects.find(_sequenceNumber) != _callbackObjects.end()) { + if (_callbackObjects.find(sequenceNumber) != _callbackObjects.end()) { // remove the packet from our two maps, it's matched DataServerCallbackObject* callbackObject = _callbackObjects.take(sequenceNumber); _unmatchedPackets.remove(sequenceNumber); @@ -176,10 +177,14 @@ void DataServerClient::removeMatchedPacketFromMap(unsigned char* packetData) { } void DataServerClient::resendUnmatchedPackets() { - foreach (const QByteArray& packet, _unmatchedPackets) { - // send the unmatched packet to the data server - NodeList::getInstance()->getNodeSocket().writeDatagram(packet.data(), packet.size(), - dataServerSockAddr().getAddress(), - dataServerSockAddr().getPort()); + if (_unmatchedPackets.size() > 0) { + qDebug() << "Resending" << _unmatchedPackets.size() << "packets to the data server."; + + foreach (const QByteArray& packet, _unmatchedPackets) { + // send the unmatched packet to the data server + NodeList::getInstance()->getNodeSocket().writeDatagram(packet.data(), packet.size(), + dataServerSockAddr().getAddress(), + dataServerSockAddr().getPort()); + } } } diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index f2af6cd81c..83dbf4654f 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -21,9 +21,6 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) { case PACKET_TYPE_HEAD_DATA: return 15; - - case PACKET_TYPE_AVATAR_URLS: - return 2; case PACKET_TYPE_OCTREE_STATS: return 2; diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index f4fe5cd0a8..5d1c657d6b 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -25,7 +25,6 @@ const PACKET_TYPE PACKET_TYPE_MIXED_AUDIO = 'A'; const PACKET_TYPE PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO = 'M'; const PACKET_TYPE PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO = 'm'; const PACKET_TYPE PACKET_TYPE_BULK_AVATAR_DATA = 'X'; -const PACKET_TYPE PACKET_TYPE_AVATAR_URLS = 'U'; const PACKET_TYPE PACKET_TYPE_TRANSMITTER_DATA_V2 = 'T'; const PACKET_TYPE PACKET_TYPE_ENVIRONMENT_DATA = 'e'; const PACKET_TYPE PACKET_TYPE_DOMAIN_LIST_REQUEST = 'L'; From f9b38af96fd9229d23df2c424a381cc93d380bf0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 21 Jan 2014 16:55:57 -0800 Subject: [PATCH 16/17] remove processing of Avatar URLs from AvatarMixer --- assignment-client/src/avatars/AvatarMixer.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index e5e3ea2fcc..56464d2415 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -137,19 +137,6 @@ void AvatarMixer::processDatagram(const QByteArray& dataByteArray, const HifiSoc } } case PACKET_TYPE_KILL_NODE: - case PACKET_TYPE_AVATAR_URLS: { - QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader((unsigned char*) dataByteArray.data()), - NUM_BYTES_RFC4122_UUID)); - // let everyone else know about the update - foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (node->getActiveSocket() && node->getUUID() != nodeUUID) { - nodeList->getNodeSocket().writeDatagram(dataByteArray, - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); - } - } - // let node kills fall through to default behavior - } default: // hand this off to the NodeList nodeList->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size()); From 8df40b5d6014bb7643169040f551960b082abc3c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 21 Jan 2014 17:07:40 -0800 Subject: [PATCH 17/17] clearly init the skeleton URL in profile --- interface/src/avatar/Profile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index 82427babc0..09baac13a9 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -21,7 +21,8 @@ Profile::Profile(const QString &username) : _lastDomain(), _lastPosition(0.0, 0.0, 0.0), _lastOrientationSend(0), - _faceModelURL() + _faceModelURL(), + _skeletonModelURL() { if (!username.isEmpty()) { setUsername(username);