From 5502975505737f93b19a4cbbcc890dee3ac898e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sun, 19 Apr 2015 02:12:59 +0200 Subject: [PATCH] degesch: prepare for user/channel buffers That is, prepare all the required data structures and their relations. I'm not particularly good at this, so let's just hope it will work out. --- degesch.c | 221 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 194 insertions(+), 27 deletions(-) diff --git a/degesch.c b/degesch.c index 79fecfd..364fb69 100644 --- a/degesch.c +++ b/degesch.c @@ -91,7 +91,7 @@ static struct config_item g_config_table[] = // --- Application data -------------------------------------------------------- // All text stored in our data structures is encoded in UTF-8. -// Or at least should be. +// Or at least should be. The exception is IRC identifiers. /// Shorthand to set an error and return failure from the function #define FAIL(...) \ @@ -100,27 +100,163 @@ static struct config_item g_config_table[] = return false; \ BLOCK_END +static void user_unref (void *p); +static void channel_unref (void *p); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -struct nick_info +struct user_channel { - char *nickname; ///< Literal nickname - char mode_char; ///< Op/voice/... character - bool away; ///< User is away + LIST_HEADER (struct user_channel) - // XXX: maybe a good candidate for deduplication (away status) + struct channel *channel; ///< Reference to channel }; -static void -nick_info_destroy (void *p) +static struct user_channel * +user_channel_new (void) { - struct nick_info *self = p; - free (self->nickname); + struct user_channel *self = xcalloc (1, sizeof *self); + return self; +} + +static void +user_channel_destroy (void *p) +{ + struct user_channel *self = p; + channel_unref (self->channel); free (self); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// We keep references to user information in channels and buffers, +// as well as in the name lookup table. + +struct user +{ + size_t ref_count; ///< Reference count + + // TODO: eventually a reference to the server + + char *nickname; ///< Literal nickname + // TODO: write code to poll for the away status + bool away; ///< User is away + + struct user_channel *channels; ///< Channels the user is on +}; + +static struct user * +user_new (void) +{ + struct user *self = xcalloc (1, sizeof *self); + self->ref_count = 1; + return self; +} + +static void +user_destroy (struct user *self) +{ + free (self->nickname); + LIST_FOR_EACH (struct user_channel, iter, self->channels) + user_channel_destroy (iter); + free (self); +} + +static struct user * +user_ref (struct user *self) +{ + self->ref_count++; + return self; +} + +static void +user_unref (void *p) +{ + struct user *self = p; + if (!--self->ref_count) + user_destroy (self); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +struct channel_user +{ + LIST_HEADER (struct channel_user) + + struct user *user; ///< Reference to user + char mode_char; ///< Op/voice/... character, or zero +}; + +static struct channel_user * +channel_user_new (void) +{ + struct channel_user *self = xcalloc (1, sizeof *self); + return self; +} + +static void +channel_user_destroy (void *p) +{ + struct channel_user *self = p; + user_unref (self->user); + free (self); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// We keep references to channels in their users and buffers, +// as well as in the name lookup table. + +struct channel +{ + size_t ref_count; ///< Reference count + + // TODO: eventually a reference to the server + + char *name; ///< Channel name + char *mode; ///< Channel mode + char *topic; ///< Channel topic + + struct channel_user *users; ///< Channel users +}; + +static struct channel * +channel_new (void) +{ + struct channel *self = xcalloc (1, sizeof *self); + self->ref_count = 1; + return self; +} + +static void +channel_destroy (void *p) +{ + struct channel *self = p; + free (self->name); + free (self->mode); + free (self->topic); + LIST_FOR_EACH (struct channel_user, iter, self->users) + channel_user_destroy (iter); + free (self); +} + +static struct channel * +channel_ref (struct channel *self) +{ + self->ref_count++; + return self; +} + +static void +channel_unref (void *p) +{ + struct channel *self = p; + if (!--self->ref_count) + channel_destroy (self); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + enum buffer_line_flags { BUFFER_LINE_HIGHLIGHT = 1 << 0 ///< The user was highlighted by this @@ -202,20 +338,18 @@ struct buffer unsigned unseen_messages_count; ///< # messages since last visited - // Channel information: + // Origin information: - char *mode; ///< Channel mode - char *topic; ///< Channel topic - struct str_map nicks; ///< Maps nicks to "nick_info" + struct user *user; ///< Reference to user + struct channel *channel; ///< Reference to channel + + // TODO: eventually a reference to the server for server buffers }; static struct buffer * buffer_new (void) { struct buffer *self = xcalloc (1, sizeof *self); - str_map_init (&self->nicks); - self->nicks.key_xfrm = irc_strxfrm; - self->nicks.free = nick_info_destroy; return self; } @@ -225,9 +359,12 @@ buffer_destroy (struct buffer *self) free (self->name); // Can't really free "history" here free (self->saved_line); - free (self->mode); - free (self->topic); - str_map_free (&self->nicks); + LIST_FOR_EACH (struct buffer_line, iter, self->lines) + buffer_line_destroy (iter); + if (self->user) + user_unref (self->user); + if (self->channel) + channel_unref (self->channel); free (self); } @@ -260,7 +397,23 @@ struct app_context SSL_CTX *ssl_ctx; ///< SSL context SSL *ssl; ///< SSL connection - // TODO: channels? + // TODO: an output queue to prevent excess floods (this will be needed + // especially for away status polling) + + // XXX: there can be buffers for non-existent users + // TODO: user buffers rename on nick changes + // TODO: move entries in "irc_buffer_map" and "irc_users" when that happens + // TODO: user buffers may merge when an existing user renames to match + // the name of a buffer for a non-existent user + // TODO: initialize key_strxfrm according to server properties; + // note that collisions may arise on reconnecting + // TODO: when disconnected, get rid of all users everywhere; + // maybe also broadcast all buffers about the disconnection event + // TODO: when getting connected again, rejoin all current channels + + struct str_map irc_users; ///< IRC user data + struct str_map irc_channels; ///< IRC channel data + struct str_map irc_buffer_map; ///< Maps IRC identifiers to buffers // TODO: initialize and update these two values // TODO: probably issue a USERHOST message for ourselves after connecting @@ -288,6 +441,7 @@ struct app_context struct buffer *buffers_tail; ///< The tail of our buffers // XXX: when we go multiserver, there will be collisions + // TODO: make buffer names unique like weechat does struct str_map buffers_by_name; ///< Excludes GLOBAL and SERVER struct buffer *global_buffer; ///< The global buffer @@ -328,13 +482,22 @@ app_context_init (struct app_context *self) str_init (&self->read_buffer); self->irc_ready = false; + str_map_init (&self->irc_users); + self->irc_users.free = user_unref; + self->irc_users.key_xfrm = irc_strxfrm; + str_map_init (&self->irc_channels); + self->irc_channels.free = channel_unref; + self->irc_channels.key_xfrm = irc_strxfrm; + str_map_init (&self->irc_buffer_map); + self->irc_buffer_map.key_xfrm = irc_strxfrm; + + poller_init (&self->poller); + str_map_init (&self->buffers_by_name); self->buffers_by_name.key_xfrm = irc_strxfrm; self->last_displayed_msg_time = time (NULL); - poller_init (&self->poller); - char *encoding = nl_langinfo (CODESET); #ifdef __linux__ encoding = xstrdup_printf ("%s//TRANSLIT", encoding); @@ -370,15 +533,19 @@ app_context_free (struct app_context *self) if (self->ssl_ctx) SSL_CTX_free (self->ssl_ctx); + str_map_free (&self->irc_users); + str_map_free (&self->irc_channels); + str_map_free (&self->irc_buffer_map); + free (self->irc_nickname); free (self->irc_user_mode); + poller_free (&self->poller); + LIST_FOR_EACH (struct buffer, iter, self->buffers) buffer_destroy (iter); str_map_free (&self->buffers_by_name); - poller_free (&self->poller); - iconv_close (self->latin1_to_utf8); iconv_close (self->term_from_utf8); iconv_close (self->term_to_utf8); @@ -1373,8 +1540,8 @@ make_prompt (struct app_context *ctx, struct str *output) str_append_printf (output, "%d:%s", buffer_get_index (ctx, buffer), buffer->name); - if (buffer->type == BUFFER_CHANNEL && *buffer->mode) - str_append_printf (output, "(%s)", buffer->mode); + if (buffer->type == BUFFER_CHANNEL && *buffer->channel->mode) + str_append_printf (output, "(%s)", buffer->channel->mode); if (buffer != ctx->global_buffer) {