From 5ae055a0e33d4b3fe4e1ecaf58f5cb8833f07b97 Mon Sep 17 00:00:00 2001 From: jfreegman Date: Sat, 17 Feb 2024 12:41:25 -0500 Subject: [PATCH] feat: re-colourize chat history on load This is only a partial implementation. We only colourize normal messages at present. In order to do other types of messages (actions, connection status, mod events etc.) we'll need to change the log format to give us more hints This commit also removes seconds from the default log timestamp for consistency witht he UI default, and adds the missing config example field for custom log timestamp formats. --- doc/toxic.conf.5 | 6 ++--- doc/toxic.conf.5.asc | 2 +- misc/toxic.conf.example | 4 +++ src/chat.c | 14 ++++++++--- src/conference.c | 8 +++++- src/groupchats.c | 5 +++- src/line_info.c | 53 +++++++++++++++++++++++++++++++++++++++- src/line_info.h | 11 ++++++++- src/log.c | 54 ++++++++++++++++++++++++++++++++++++++--- src/log.h | 2 +- src/message_queue.c | 4 +-- src/settings.c | 2 +- src/settings.h | 2 +- 13 files changed, 148 insertions(+), 19 deletions(-) diff --git a/doc/toxic.conf.5 b/doc/toxic.conf.5 index b07676bd9..d8583070a 100644 --- a/doc/toxic.conf.5 +++ b/doc/toxic.conf.5 @@ -2,12 +2,12 @@ .\" Title: toxic.conf .\" Author: [see the "AUTHORS" section] .\" Generator: DocBook XSL Stylesheets vsnapshot -.\" Date: 2024-02-07 +.\" Date: 2024-02-17 .\" Manual: Toxic Manual .\" Source: toxic __VERSION__ .\" Language: English .\" -.TH "TOXIC\&.CONF" "5" "2024\-02\-07" "toxic __VERSION__" "Toxic Manual" +.TH "TOXIC\&.CONF" "5" "2024\-02\-17" "toxic __VERSION__" "Toxic Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -79,7 +79,7 @@ Time format string for the interface enclosed by double quotes\&. See .PP \fBlog_timestamp_format\fR .RS 4 -Time format string for logging enclosed by double quotes\&. See +Time format string for logging enclosed by double quotes\&. (Note: the time portion of the format string must be enclosed by square brackets in order to be parsed by toxic)\&. See \fBdate\fR(1) .RE .PP diff --git a/doc/toxic.conf.5.asc b/doc/toxic.conf.5.asc index 971cd6c23..8bab2614d 100644 --- a/doc/toxic.conf.5.asc +++ b/doc/toxic.conf.5.asc @@ -51,7 +51,7 @@ OPTIONS See *date*(1) *log_timestamp_format*;; - Time format string for logging enclosed by double quotes. + Time format string for logging enclosed by double quotes. (Note: the time portion of the format string must be enclosed by square brackets in order to be parsed by toxic). See *date*(1) *alerts*;; diff --git a/misc/toxic.conf.example b/misc/toxic.conf.example index 7014f32fb..1c1b9cc20 100644 --- a/misc/toxic.conf.example +++ b/misc/toxic.conf.example @@ -51,6 +51,10 @@ ui = { // Timestamp format string according to date/strftime format. Overrides time_format setting timestamp_format="%H:%M"; + // Timestamp format string for raw logs according to date/strftime format. (Note: the time + // of the format string must be enclosed in square brackets in order to be parsed by toxic). + log_timestamp_format="%Y/%m/%d [%H:%M]" + // true to show you when others are typing a message in 1-on-1 chats show_typing_other=true; diff --git a/src/chat.c b/src/chat.c index ff31eb2c9..85b01ff7b 100644 --- a/src/chat.c +++ b/src/chat.c @@ -21,7 +21,7 @@ */ #ifndef _GNU_SOURCE -#define _GNU_SOURCE /* needed for wcswidth() */ +#define _GNU_SOURCE /* needed for strcasestr() and wcswidth() */ #endif #include "chat.h" @@ -1647,18 +1647,26 @@ static void chat_onDraw(ToxWindow *self, Toxic *toxic) static void chat_init_log(ToxWindow *self, Toxic *toxic, const char *self_nick) { + Tox *tox = toxic->tox; + ChatContext *ctx = self->chatwin; const Client_Config *c_config = toxic->c_config; char myid[TOX_ADDRESS_SIZE]; - tox_self_get_address(toxic->tox, (uint8_t *) myid); + tox_self_get_address(tox, (uint8_t *) myid); + + char self_name[TOX_MAX_NAME_LENGTH + 1]; + tox_self_get_name(tox, (uint8_t *) self_name); + + const size_t len = tox_self_get_name_size(tox); + self_name[len] = '\0'; if (log_init(ctx->log, c_config, self_nick, myid, Friends.list[self->num].pub_key, LOG_TYPE_CHAT) != 0) { line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to initialize chat log."); return; } - if (load_chat_history(ctx->log, self, c_config) != 0) { + if (load_chat_history(ctx->log, self, c_config, self_name) != 0) { line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to load chat history."); } diff --git a/src/conference.c b/src/conference.c index 64fce3058..3024a4979 100644 --- a/src/conference.c +++ b/src/conference.c @@ -179,6 +179,12 @@ static void init_conference_logging(ToxWindow *self, Toxic *toxic, uint32_t conf char my_id[TOX_ADDRESS_SIZE]; tox_self_get_address(tox, (uint8_t *) my_id); + char self_name[TOX_MAX_NAME_LENGTH + 1]; + tox_self_get_name(tox, (uint8_t *) self_name); + + const size_t len = tox_self_get_name_size(tox); + self_name[len] = '\0'; + char conference_id[TOX_CONFERENCE_ID_SIZE]; tox_conference_get_id(tox, conferencenum, (uint8_t *) conference_id); @@ -187,7 +193,7 @@ static void init_conference_logging(ToxWindow *self, Toxic *toxic, uint32_t conf return; } - if (load_chat_history(ctx->log, self, c_config) != 0) { + if (load_chat_history(ctx->log, self, c_config, self_name) != 0) { line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to load chat history."); } diff --git a/src/groupchats.c b/src/groupchats.c index 19cdad72f..972c18bc5 100644 --- a/src/groupchats.c +++ b/src/groupchats.c @@ -338,6 +338,9 @@ static void init_groupchat_log(ToxWindow *self, Toxic *toxic, uint32_t groupnumb char my_id[TOX_ADDRESS_SIZE]; tox_self_get_address(tox, (uint8_t *) my_id); + char self_nick[TOX_MAX_NAME_LENGTH + 1]; + get_group_self_nick_truncate(tox, self_nick, groupnumber); + char chat_id[TOX_GROUP_CHAT_ID_SIZE]; Tox_Err_Group_State_Queries err; @@ -353,7 +356,7 @@ static void init_groupchat_log(ToxWindow *self, Toxic *toxic, uint32_t groupnumb return; } - if (load_chat_history(ctx->log, self, c_config) != 0) { + if (load_chat_history(ctx->log, self, c_config, self_nick) != 0) { line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "Failed to load chat history."); } diff --git a/src/line_info.c b/src/line_info.c index 6a42ded67..784fabed4 100644 --- a/src/line_info.c +++ b/src/line_info.c @@ -510,7 +510,58 @@ int line_info_add(ToxWindow *self, const Client_Config *c_config, bool show_time line_info_init_line(self, new_line); - hst->queue[hst->queue_size++] = new_line; + hst->queue[hst->queue_size] = new_line; + ++hst->queue_size; + + return new_line->id; +} + +int line_info_load_history(ToxWindow *self, const Client_Config *c_config, const char *timestamp, + const char *name, int colour, const char *message) +{ + if (self == NULL) { + return -1; + } + + struct history *hst = self->chatwin->hst; + + if (hst->queue_size >= MAX_LINE_INFO_QUEUE) { + return -1; + } + + struct line_info *new_line = calloc(1, sizeof(struct line_info)); + + if (new_line == NULL) { + return -1; + } + + int len = 1 + strlen(c_config->line_normal) + 3; + + const uint16_t msg_width = line_info_add_msg(new_line->msg, sizeof(new_line->msg) / sizeof(wchar_t), message); + len += msg_width; + + if (c_config->timestamps == TIMESTAMPS_ON) { + snprintf(new_line->timestr, sizeof(new_line->timestr), "%s", timestamp); + } + + len += strlen(new_line->timestr) + 1; + + snprintf(new_line->name1, sizeof(new_line->name1), "%s", name); + len += strlen(new_line->name1); + + new_line->id = (hst->line_end->id + 1 + hst->queue_size) % INT_MAX; + new_line->len = len; + new_line->msg_width = msg_width; + new_line->type = IN_MSG; + new_line->bold = false; + new_line->colour = colour; + new_line->noread_flag = false; + new_line->timestamp = get_unix_time(); + + line_info_init_line(self, new_line); + + hst->queue[hst->queue_size] = new_line; + ++hst->queue_size; return new_line->id; } diff --git a/src/line_info.h b/src/line_info.h index fbed7eb99..0bc426157 100644 --- a/src/line_info.h +++ b/src/line_info.h @@ -83,13 +83,22 @@ struct history { /* creates new line_info line and puts it in the queue. * - * Returns the id of the new line. + * Returns the ID of the new line on success. * Returns -1 on failure. */ __attribute__((format(printf, 9, 10))) int line_info_add(ToxWindow *self, const Client_Config *c_config, bool show_timestamp, const char *name1, const char *name2, LINE_TYPE type, uint8_t bold, uint8_t colour, const char *msg, ...); +/* + * Similar to line_info_add() but uses lines from history. + * + * Returns the ID of the new line on success. + * Returns -1 on failure. + */ +int line_info_load_history(ToxWindow *self, const Client_Config *c_config, const char *timestamp, + const char *name, int colour, const char *message); + /* Prints a section of history starting at line_start */ void line_info_print(ToxWindow *self, const Client_Config *c_config); diff --git a/src/log.c b/src/log.c index 51f1e764d..c2da99a79 100644 --- a/src/log.c +++ b/src/log.c @@ -152,7 +152,7 @@ void write_to_log(struct chatlog *log, const Client_Config *c_config, const char const char *t = c_config->log_timestamp_format; char s[MAX_STR_SIZE]; - format_time_str(s, MAX_STR_SIZE, t, get_time()); + get_time_str(s, sizeof(s), t); fprintf(log->file, "%s %s %s\n", s, name_frmt, msg); if (timed_out(log->lastwrite, LOG_FLUSH_LIMIT)) { @@ -231,12 +231,60 @@ int log_init(struct chatlog *log, const Client_Config *c_config, const char *nam return 0; } +static void load_line(ToxWindow *self, const Client_Config *c_config, const char *line, const char *self_name) +{ + const size_t line_length = strlen(line); + const int start_ts = char_find(0, line, '[') + 1; + const int end_ts = char_find(0, line, ']'); + const int ts_len = end_ts - start_ts; + const int start_idx = (line_length > start_ts) ? start_ts - 1 : 0; + + char timestamp[TIME_STR_SIZE]; + + // sanity check + if (ts_len <= 0 || ts_len >= sizeof(timestamp) || start_ts < 0 || start_ts + ts_len >= line_length + || start_ts >= line_length || end_ts <= 0 || end_ts >= line_length) { + return; + } + + memcpy(timestamp, &line[start_ts], ts_len); + timestamp[ts_len] = '\0'; + + const int end_name = char_find(end_ts, line, ':'); + + if (end_name + 2 >= line_length || end_name <= 0) { + goto on_error; + } + + char name[TOXIC_MAX_NAME_LENGTH + 1]; + const char *message = &line[end_name + 2]; + + const int start_name = end_ts + 1; + const int name_len = end_name - start_name - 1; + + if (start_name + 1 >= line_length || name_len >= sizeof(name) || start_name + 1 + name_len >= line_length + || name_len <= 0) { + goto on_error; + } + + memcpy(name, &line[start_name + 1], name_len); + name[name_len] = '\0'; + + const int colour = strcmp(self_name, name) != 0 ? CYAN : GREEN; + + line_info_load_history(self, c_config, timestamp, name, colour, message); + return; + +on_error: + line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "%s", &line[start_idx]); +} + /* Loads chat log history and prints it to `self` window. * * Return 0 on success or if log file doesn't exist. * Return -1 on failure. */ -int load_chat_history(struct chatlog *log, ToxWindow *self, const Client_Config *c_config) +int load_chat_history(struct chatlog *log, ToxWindow *self, const Client_Config *c_config, const char *self_name) { if (log == NULL) { return -1; @@ -303,7 +351,7 @@ int load_chat_history(struct chatlog *log, ToxWindow *self, const Client_Config } while (line != NULL && count--) { - line_info_add(self, c_config, false, NULL, NULL, SYS_MSG, 0, 0, "%s", line); + load_line(self, c_config, line, self_name); line = strtok_r(NULL, "\n", &tmp); } diff --git a/src/log.h b/src/log.h index 5d9a4e947..a58d411ac 100644 --- a/src/log.h +++ b/src/log.h @@ -65,7 +65,7 @@ void log_disable(struct chatlog *log); * Return 0 on success or if log file doesn't exist. * Return -1 on failure. */ -int load_chat_history(struct chatlog *log, ToxWindow *self, const Client_Config *c_config); +int load_chat_history(struct chatlog *log, ToxWindow *self, const Client_Config *c_config, const char *self_name); /* Renames chatlog file `src` to `dest`. * diff --git a/src/message_queue.c b/src/message_queue.c index 460f15a39..e36a75afd 100644 --- a/src/message_queue.c +++ b/src/message_queue.c @@ -110,10 +110,10 @@ void cqueue_remove(ToxWindow *self, Toxic *toxic, uint32_t receipt) } if (log->log_on) { - char selfname[TOX_MAX_NAME_LENGTH]; + char selfname[TOX_MAX_NAME_LENGTH + 1]; tox_self_get_name(tox, (uint8_t *) selfname); - size_t len = tox_self_get_name_size(tox); + const size_t len = tox_self_get_name_size(tox); selfname[len] = 0; write_to_log(log, c_config, msg->message, selfname, msg->type == OUT_ACTION); diff --git a/src/settings.c b/src/settings.c index 41cc3e182..6f5ce45eb 100644 --- a/src/settings.c +++ b/src/settings.c @@ -662,7 +662,7 @@ int settings_load_main(Client_Config *s, const Run_Options *run_opts) if (config_setting_lookup_int(setting, ui_strings.time_format, &time)) { if (time == 12) { snprintf(s->timestamp_format, sizeof(s->timestamp_format), "%s", "%I:%M %p"); - snprintf(s->log_timestamp_format, sizeof(s->log_timestamp_format), "%s", "%Y/%m/%d [%I:%M:%S %p]"); + snprintf(s->log_timestamp_format, sizeof(s->log_timestamp_format), "%s", "%Y/%m/%d [%I:%M %p]"); } } diff --git a/src/settings.h b/src/settings.h index 762ba931c..34637c1a0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -143,7 +143,7 @@ enum settings_values { #define LINE_NORMAL "-" #define LINE_SPECIAL ">" #define TIMESTAMP_DEFAULT "%H:%M" -#define LOG_TIMESTAMP_DEFAULT "%Y/%m/%d [%H:%M:%S]" +#define LOG_TIMESTAMP_DEFAULT "%Y/%m/%d [%H:%M]" #define MPLEX_AWAY_NOTE "Away from keyboard, be back soon!" typedef struct Windows Windows;