From 5b9bd603eaddd581fd63588bbc54fe376ad26ead Mon Sep 17 00:00:00 2001 From: Jfreegman Date: Sun, 7 Sep 2014 02:43:53 -0400 Subject: [PATCH] implement read receipts --- src/chat.c | 15 +++++--- src/groupchat.c | 2 +- src/line_info.c | 36 ++++++++++++++++--- src/line_info.h | 8 +++-- src/message_queue.c | 84 +++++++++++++++++++++++++++++++++++++-------- src/message_queue.h | 15 ++++---- src/toxic.c | 5 +-- src/toxic.h | 1 + src/windows.c | 9 +++++ src/windows.h | 1 + 10 files changed, 141 insertions(+), 35 deletions(-) diff --git a/src/chat.c b/src/chat.c index 54aa4d1..4ad6a44 100644 --- a/src/chat.c +++ b/src/chat.c @@ -240,7 +240,7 @@ static void chat_onAction(ToxWindow *self, Tox *m, int32_t num, const char *acti char timefrmt[TIME_STR_SIZE]; get_time_str(timefrmt, sizeof(timefrmt)); - line_info_add(self, timefrmt, nick, NULL, ACTION, 0, 0, "%s", action); + line_info_add(self, timefrmt, nick, NULL, IN_ACTION, 0, 0, "%s", action); write_to_log(action, nick, ctx->log, true); if (self->active_box != -1) @@ -288,6 +288,12 @@ static void chat_onStatusMessageChange(ToxWindow *self, int32_t num, const char statusbar->statusmsg_len = strlen(statusbar->statusmsg); } +static void chat_onReadReceipt(ToxWindow *self, Tox *m, int32_t num, uint32_t receipt) +{ + struct chat_queue *q = self->chatwin->cqueue; + cqueue_remove(self, q, receipt); +} + static void chat_onFileSendRequest(ToxWindow *self, Tox *m, int32_t num, uint8_t filenum, uint64_t filesize, const char *pathname, uint16_t path_len) { @@ -831,9 +837,9 @@ static void send_action(ToxWindow *self, ChatContext *ctx, Tox *m, char *action) char timefrmt[TIME_STR_SIZE]; get_time_str(timefrmt, sizeof(timefrmt)); - line_info_add(self, timefrmt, selfname, NULL, ACTION, 0, 0, "%s", action); + line_info_add(self, timefrmt, selfname, NULL, OUT_ACTION, 0, 0, "%s", action); write_to_log(action, selfname, ctx->log, true); - cqueue_add(ctx->cqueue, action, strlen(action), QACTION, ctx->hst->line_end->id); + cqueue_add(ctx->cqueue, action, strlen(action), OUT_ACTION, ctx->hst->line_end->id + 1); } static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) @@ -915,7 +921,7 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) line_info_add(self, timefrmt, selfname, NULL, OUT_MSG, 0, 0, "%s", line); write_to_log(line, selfname, ctx->log, false); - cqueue_add(ctx->cqueue, line, strlen(line), QMESSAGE, ctx->hst->line_end->id); + cqueue_add(ctx->cqueue, line, strlen(line), OUT_MSG, ctx->hst->line_end->id + 1); } wclear(ctx->linewin); @@ -1120,6 +1126,7 @@ ToxWindow new_chat(Tox *m, int32_t friendnum) ret.onFileSendRequest = &chat_onFileSendRequest; ret.onFileControl = &chat_onFileControl; ret.onFileData = &chat_onFileData; + ret.onReadReceipt = &chat_onReadReceipt; #ifdef _AUDIO ret.onInvite = &chat_onInvite; diff --git a/src/groupchat.c b/src/groupchat.c index 10ede90..44568e2 100644 --- a/src/groupchat.c +++ b/src/groupchat.c @@ -197,7 +197,7 @@ static void groupchat_onGroupAction(ToxWindow *self, Tox *m, int groupnum, int p char timefrmt[TIME_STR_SIZE]; get_time_str(timefrmt, sizeof(timefrmt)); - line_info_add(self, timefrmt, nick, NULL, ACTION, 0, 0, "%s", action); + line_info_add(self, timefrmt, nick, NULL, GROUP_ACTION, 0, 0, "%s", action); write_to_log(action, nick, ctx->log, true); } diff --git a/src/line_info.c b/src/line_info.c index 54f2950..54113ee 100644 --- a/src/line_info.c +++ b/src/line_info.c @@ -153,7 +153,16 @@ void line_info_add(ToxWindow *self, char *tmstmp, char *name1, char *name2, uint /* for type-specific formatting in print function */ switch (type) { - case ACTION: + case OUT_ACTION: + case IN_ACTION: + case GROUP_ACTION: + len += 5; + break; + + case OUT_MSG: + len += 4; + break; + case CONNECTION: len += 3; break; @@ -277,6 +286,7 @@ void line_info_print(ToxWindow *self) switch (type) { case OUT_MSG: + case OUT_MSG_READ: case IN_MSG: wattron(win, COLOR_PAIR(BLUE)); wprintw(win, "%s", line->timestamp); @@ -296,22 +306,39 @@ void line_info_print(ToxWindow *self) if (line->msg[0] == '>') wattron(win, COLOR_PAIR(GREEN)); - wprintw(win, "%s\n", line->msg); + wprintw(win, "%s", line->msg); if (line->msg[0] == '>') wattroff(win, COLOR_PAIR(GREEN)); + if (type == OUT_MSG) { /* sent message with no recieve receipt */ + wattron(win, COLOR_PAIR(RED)); + wprintw(win, " x", line->msg); + wattroff(win, COLOR_PAIR(RED)); + } + + wprintw(win, "\n", line->msg); break; - case ACTION: + case GROUP_ACTION: + case OUT_ACTION_READ: + case OUT_ACTION: + case IN_ACTION: wattron(win, COLOR_PAIR(BLUE)); wprintw(win, "%s", line->timestamp); wattroff(win, COLOR_PAIR(BLUE)); wattron(win, COLOR_PAIR(YELLOW)); - wprintw(win, "* %s %s\n", line->name1, line->msg); + wprintw(win, "* %s %s", line->name1, line->msg); wattroff(win, COLOR_PAIR(YELLOW)); + if (type == OUT_ACTION) { /* sent action with no recieve receipt */ + wattron(win, COLOR_PAIR(RED)); + wprintw(win, " x", line->msg); + wattroff(win, COLOR_PAIR(RED)); + } + + wprintw(win, "\n", line->msg); break; case SYS_MSG: @@ -390,6 +417,7 @@ void line_info_print(ToxWindow *self) line_info_print(self); } +/* puts msg in specified line_info msg buffer */ void line_info_set(ToxWindow *self, uint32_t id, char *msg) { struct line_info *line = self->chatwin->hst->line_end; diff --git a/src/line_info.h b/src/line_info.h index 0d26d3e..516930e 100644 --- a/src/line_info.h +++ b/src/line_info.h @@ -34,8 +34,12 @@ enum { SYS_MSG, IN_MSG, OUT_MSG, + OUT_MSG_READ, /* for sent messages that have received a read reply. don't set this with line_info_add */ + IN_ACTION, + OUT_ACTION, + OUT_ACTION_READ, /* same as OUT_MSG_READ but for actions */ + GROUP_ACTION, PROMPT, - ACTION, CONNECTION, NAME_CHANGE, } LINE_TYPE; @@ -49,7 +53,7 @@ struct line_info { uint8_t bold; uint8_t colour; uint32_t id; - uint16_t len; /* combined len of all strings */ + uint16_t len; /* combined len of entire line */ uint8_t newlines; struct line_info *prev; diff --git a/src/message_queue.c b/src/message_queue.c index 085bc61..47d89d1 100644 --- a/src/message_queue.c +++ b/src/message_queue.c @@ -25,6 +25,8 @@ #include "toxic.h" #include "windows.h" #include "message_queue.h" +#include "misc_tools.h" +#include "line_info.h" void cqueue_cleanup(struct chat_queue *q) { @@ -41,7 +43,7 @@ void cqueue_cleanup(struct chat_queue *q) void cqueue_add(struct chat_queue *q, const char *msg, int len, uint8_t type, uint32_t line_id) { - struct cqueue_msg *new_m = malloc(sizeof(struct cqueue_msg)); + struct cqueue_msg *new_m = calloc(1, sizeof(struct cqueue_msg)); if (new_m == NULL) exit_toxic_err("failed in cqueue_message", FATALERR_MEMORY); @@ -50,38 +52,90 @@ void cqueue_add(struct chat_queue *q, const char *msg, int len, uint8_t type, ui new_m->len = len; new_m->type = type; new_m->line_id = line_id; - new_m->next = NULL; - if (q->root == NULL) + if (q->root == NULL) { + new_m->prev = NULL; q->root = new_m; - else + } else { + new_m->prev = q->end; q->end->next = new_m; + } q->end = new_m; } -static void cqueue_remove(struct chat_queue *q) +/* update line to show receipt was received after queue removal */ +static void cqueue_mark_read(ToxWindow *self, uint32_t id, uint8_t type) { - struct cqueue_msg *new_root = q->root->next; - free(q->root); - q->root = new_root; + struct line_info *line = self->chatwin->hst->line_end; + + while (line) { + if (line->id == id) { + line->type = type == OUT_ACTION ? OUT_ACTION_READ : OUT_MSG_READ; + line->len -= 2; /* removes " x" */ + return; + } + + line = line->prev; + } } -void cqueue_try_send(Tox *m, struct chat_queue *q) +/* removes the message with the same receipt number from queue and updates line to show the message was received*/ +void cqueue_remove(ToxWindow *self, struct chat_queue *q, uint32_t receipt) { + struct cqueue_msg *q_msg = q->root; + + while (q_msg) { + struct cqueue_msg *next = q_msg->next; + + if (q_msg->receipt == receipt) { + uint32_t line_id = q_msg->line_id; + uint8_t type = q_msg->type; + + if (q_msg->prev == NULL) { + if (next) + next->prev = NULL; + + free(q->root); + q->root = next; + } else { + q_msg->prev->next = next; + free(q_msg); + } + + cqueue_mark_read(self, line_id, type); + return; + } + + q_msg = next; + } +} + +#define CQUEUE_TRY_SEND_INTERVAL 5 + +/* Tries to send oldest message in queue. If fails, tries again in CQUEUE_TRY_SEND_INTERVAL seconds */ +void cqueue_try_send(ToxWindow *self, Tox *m) +{ + struct chat_queue *q = self->chatwin->cqueue; + if (q->root == NULL) return; struct cqueue_msg *q_msg = q->root; - uint32_t receipt; + uint64_t curtime = get_unix_time(); - if (q_msg->type == QMESSAGE) + /* allow some time for a laggy read receipt before resending + TODO: timeout should be removed when receipts are fixed in core) */ + if (q_msg->receipt != 0 && !timed_out(q_msg->last_send_try, curtime, CQUEUE_TRY_SEND_INTERVAL)) + return; + + uint32_t receipt = 0; + + if (q_msg->type == OUT_MSG) receipt = tox_send_message(m, q->friendnum, (uint8_t *) q_msg->message, q_msg->len); else receipt = tox_send_action(m, q->friendnum, (uint8_t *) q_msg->message, q_msg->len); - if (receipt == 0) - return; - - cqueue_remove(q); + q->root->last_send_try = curtime; + q->root->receipt = receipt; } diff --git a/src/message_queue.h b/src/message_queue.h index b8c9b23..44c9e5f 100644 --- a/src/message_queue.h +++ b/src/message_queue.h @@ -20,17 +20,14 @@ * */ -enum { - QMESSAGE, - QACTION, -} MESSAGE_TYPE; - struct cqueue_msg { char message[MAX_STR_SIZE]; int len; int line_id; uint8_t type; uint32_t receipt; + uint64_t last_send_try; + struct cqueue_msg *prev; struct cqueue_msg *next; }; @@ -42,5 +39,9 @@ struct chat_queue { void cqueue_cleanup(struct chat_queue *q); void cqueue_add(struct chat_queue *q, const char *msg, int len, uint8_t type, uint32_t line_id); -void cqueue_check(Tox *m); -void cqueue_try_send(Tox *m, struct chat_queue *q); + +/* Tries to send oldest message in queue once every CQUEUE_TRY_SEND_INTERVAL seconds */ +void cqueue_try_send(ToxWindow *self, Tox *m); + +/* removes the message with the same receipt number from queue and updates line to show the message was received*/ +void cqueue_remove(ToxWindow *self, struct chat_queue *q, uint32_t receipt); diff --git a/src/toxic.c b/src/toxic.c index 0bfd738..40ab000 100644 --- a/src/toxic.c +++ b/src/toxic.c @@ -285,6 +285,7 @@ static Tox *init_tox(void) tox_callback_file_send_request(m, on_file_sendrequest, NULL); tox_callback_file_control(m, on_file_control, NULL); tox_callback_file_data(m, on_file_data, NULL); + tox_callback_read_receipt(m, on_read_receipt, NULL); #ifdef __linux__ tox_set_name(m, (uint8_t *) "Cool dude", strlen("Cool dude")); @@ -587,11 +588,11 @@ void *thread_cqueue(void *data) ToxWindow *toxwin = get_window_ptr(i); if (toxwin != NULL && toxwin->is_chat && tox_get_friend_connection_status(m, toxwin->num) == 1) - cqueue_try_send(m, toxwin->chatwin->cqueue); + cqueue_try_send(toxwin, m); } pthread_mutex_unlock(&Winthread.lock); - usleep(100000); /* 0.1 second */ + usleep(50000); } } diff --git a/src/toxic.h b/src/toxic.h index 317c0d0..7ee2c8e 100644 --- a/src/toxic.h +++ b/src/toxic.h @@ -108,5 +108,6 @@ void on_file_control(Tox *m, int32_t friendnumber, uint8_t receive_send, uint8_t const uint8_t *data, uint16_t length, void *userdata); void on_file_data(Tox *m, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length, void *userdata); void on_typing_change(Tox *m, int32_t friendnumber, uint8_t is_typing, void *userdata); +void on_read_receipt(Tox *m, int32_t, uint32_t, void *userdata); #endif /* #define _toxic_h */ diff --git a/src/windows.c b/src/windows.c index d53af5e..9370090 100644 --- a/src/windows.c +++ b/src/windows.c @@ -219,6 +219,15 @@ void on_file_data(Tox *m, int32_t friendnumber, uint8_t filenumber, const uint8_ } } +void on_read_receipt(Tox *m, int32_t friendnumber, uint32_t receipt, void *userdata) +{ + int i; + + for (i = 0; i < MAX_WINDOWS_NUM; ++i) { + if (windows[i].onReadReceipt != NULL) + windows[i].onReadReceipt(&windows[i], m, friendnumber, receipt); + } +} /* CALLBACKS END */ int add_window(Tox *m, ToxWindow w) diff --git a/src/windows.h b/src/windows.h index 8ba2fd9..5daee31 100644 --- a/src/windows.h +++ b/src/windows.h @@ -119,6 +119,7 @@ struct ToxWindow { void(*onFileControl)(ToxWindow *, Tox *, int32_t, uint8_t, uint8_t, uint8_t, const char *, uint16_t); void(*onFileData)(ToxWindow *, Tox *, int32_t, uint8_t, const char *, uint16_t); void(*onTypingChange)(ToxWindow *, Tox *, int32_t, uint8_t); + void(*onReadReceipt)(ToxWindow *, Tox *, int32_t, uint32_t); #ifdef _AUDIO